// Package server 提供缓存清理处理器功能的测试。
//
// 该文件测试 PurgeHandler 模块的各项功能，包括：
//   - 路径配置（默认和自定义）
//   - localhost 特殊处理和 CIDR 解析
//   - IP 白名单访问控制
//   - Token 认证
//   - 请求处理流程（POST/方法检查）
//   - 请求体解析
//   - sendError 方法
//   - purgeByPath/purgeByPattern 方法（nil server）
//
// 作者：xfy
package server

import (
	"net"
	"strings"
	"testing"

	"github.com/valyala/fasthttp"
	"rua.plus/lolly/internal/cache"
	"rua.plus/lolly/internal/config"
	"rua.plus/lolly/internal/utils"
)

func TestPurgeHandler_Path(t *testing.T) {
	tests := []struct {
		name     string
		cfgPath  string
		wantPath string
	}{
		{
			name:     "default path",
			cfgPath:  "",
			wantPath: "/_cache/purge",
		},
		{
			name:     "custom path",
			cfgPath:  "/api/purge",
			wantPath: "/api/purge",
		},
		{
			name:     "custom path with version prefix",
			cfgPath:  "/api/v1/cache/purge",
			wantPath: "/api/v1/cache/purge",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &config.CacheAPIConfig{
				Path:  tt.cfgPath,
				Allow: []string{},
			}

			h, err := NewPurgeHandler(nil, cfg)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if h.Path() != tt.wantPath {
				t.Errorf("expected path %s, got %s", tt.wantPath, h.Path())
			}
		})
	}
}

func TestPurgeHandler_NewPurgeHandler(t *testing.T) {
	t.Run("localhost special handling", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{"localhost"},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(h.allowed) != 2 {
			t.Fatalf("expected 2 allowed networks for localhost, got %d", len(h.allowed))
		}

		// 验证包含 127.0.0.1/32
		_, v4Net, _ := net.ParseCIDR("127.0.0.1/32")
		if v4Net == nil {
			t.Fatal("failed to parse 127.0.0.1/32")
		}
		foundV4 := false
		for _, n := range h.allowed {
			if n.String() == v4Net.String() {
				foundV4 = true
				break
			}
		}
		if !foundV4 {
			t.Error("expected 127.0.0.1/32 in allowed networks")
		}

		// 验证包含 ::1/128
		_, v6Net, _ := net.ParseCIDR("::1/128")
		if v6Net == nil {
			t.Fatal("failed to parse ::1/128")
		}
		foundV6 := false
		for _, n := range h.allowed {
			if n.String() == v6Net.String() {
				foundV6 = true
				break
			}
		}
		if !foundV6 {
			t.Error("expected ::1/128 in allowed networks")
		}
	})

	t.Run("CIDR parsing", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{"10.0.0.0/8", "172.16.0.0/12"},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(h.allowed) != 2 {
			t.Errorf("expected 2 allowed networks, got %d", len(h.allowed))
		}
	})

	t.Run("single IP parsed as CIDR", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{"192.168.1.100"},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(h.allowed) != 1 {
			t.Fatalf("expected 1 allowed network, got %d", len(h.allowed))
		}

		// 单 IP 应转换为 /32 CIDR
		if h.allowed[0].String() != "192.168.1.100/32" {
			t.Errorf("expected 192.168.1.100/32, got %s", h.allowed[0].String())
		}
	})

	t.Run("invalid IP returns error", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{"not-an-ip"},
		}

		_, err := NewPurgeHandler(nil, cfg)
		if err == nil {
			t.Error("expected error for invalid IP, got nil")
		}
	})
}

func TestPurgeHandler_checkAccess(t *testing.T) {
	tests := []struct {
		name       string
		allow      []string
		clientIP   string
		wantAccess bool
	}{
		{
			name:       "no allow list - open access",
			allow:      []string{},
			clientIP:   "1.2.3.4",
			wantAccess: true,
		},
		{
			name:       "CIDR match",
			allow:      []string{"192.168.0.0/16"},
			clientIP:   "192.168.1.100",
			wantAccess: true,
		},
		{
			name:       "CIDR no match",
			allow:      []string{"10.0.0.0/8"},
			clientIP:   "192.168.1.100",
			wantAccess: false,
		},
		{
			name:       "single IP match",
			allow:      []string{"127.0.0.1"},
			clientIP:   "127.0.0.1",
			wantAccess: true,
		},
		{
			name:       "single IP no match",
			allow:      []string{"127.0.0.1"},
			clientIP:   "127.0.0.2",
			wantAccess: false,
		},
		{
			name:       "localhost allows 127.0.0.1",
			allow:      []string{"localhost"},
			clientIP:   "127.0.0.1",
			wantAccess: true,
		},
		{
			name:       "localhost allows ::1",
			allow:      []string{"localhost"},
			clientIP:   "::1",
			wantAccess: true,
		},
		{
			name:       "localhost denies other IP",
			allow:      []string{"localhost"},
			clientIP:   "10.0.0.1",
			wantAccess: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &config.CacheAPIConfig{
				Path:  "/_cache/purge",
				Allow: tt.allow,
			}

			h, err := NewPurgeHandler(nil, cfg)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if len(h.allowed) == 0 {
				// 无白名单时应允许所有访问
				if !utils.CheckIPAccess(nil, h.allowed) {
					t.Error("expected access to be true when no allow list configured")
				}
				return
			}

			// 直接测试 IP 是否在 allowed 列表中
			ip := net.ParseIP(tt.clientIP)
			if ip == nil {
				t.Fatalf("failed to parse client IP: %s", tt.clientIP)
			}

			found := false
			for _, network := range h.allowed {
				if network.Contains(ip) {
					found = true
					break
				}
			}

			if found != tt.wantAccess {
				t.Errorf("expected access %v, got %v", tt.wantAccess, found)
			}
		})
	}
}

func TestPurgeHandler_checkAuth(t *testing.T) {
	t.Run("no auth configured", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "",
				Token: "",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		if !utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to pass when no auth configured")
		}
	})

	t.Run("auth type none", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "none",
				Token: "",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		if !utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to pass when type is none")
		}
	})

	t.Run("token auth - correct Bearer token", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "token",
				Token: "secret-token",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		ctx.Request.Header.Set("Authorization", "Bearer secret-token")

		if !utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to pass with correct Bearer token")
		}
	})

	t.Run("token auth - correct direct token", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "token",
				Token: "secret-token",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		ctx.Request.Header.Set("Authorization", "secret-token")

		if !utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to pass with correct direct token")
		}
	})

	t.Run("token auth - wrong token", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "token",
				Token: "secret-token",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		ctx.Request.Header.Set("Authorization", "Bearer wrong-token")

		if utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to fail with wrong token")
		}
	})

	t.Run("token auth - missing header", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "token",
				Token: "secret-token",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}

		if utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to fail when Authorization header is missing")
		}
	})

	t.Run("token auth - unknown type", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path: "/_cache/purge",
			Auth: config.CacheAPIAuthConfig{
				Type:  "basic",
				Token: "secret-token",
			},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		ctx := &fasthttp.RequestCtx{}
		ctx.Request.Header.Set("Authorization", "Bearer secret-token")

		if utils.CheckTokenAuth(ctx, h.auth) {
			t.Error("expected auth to fail for unknown auth type")
		}
	})
}

// TestPurgeHandler_ServeHTTP_MethodCheck 测试 ServeHTTP 的方法检查。
func TestPurgeHandler_ServeHTTP_MethodCheck(t *testing.T) {
	methods := []string{"GET", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}

	for _, method := range methods {
		t.Run(method, func(t *testing.T) {
			cfg := &config.CacheAPIConfig{
				Path:  "/_cache/purge",
				Allow: []string{},
			}

			h, err := NewPurgeHandler(nil, cfg)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			ctx := &fasthttp.RequestCtx{}
			ctx.Init(&fasthttp.Request{}, nil, nil)
			ctx.Request.Header.SetMethod(method)

			h.ServeHTTP(ctx)

			if ctx.Response.StatusCode() != fasthttp.StatusMethodNotAllowed {
				t.Errorf("expected status %d for method %s, got %d", fasthttp.StatusMethodNotAllowed, method, ctx.Response.StatusCode())
			}

			// 验证响应体包含错误信息
			if !strings.Contains(string(ctx.Response.Body()), "method not allowed") {
				t.Errorf("expected 'method not allowed' in response body, got: %s", string(ctx.Response.Body()))
			}
		})
	}
}

// TestPurgeHandler_ServeHTTP_RequestBodyParsing 测试请求体解析。
func TestPurgeHandler_ServeHTTP_RequestBodyParsing(t *testing.T) {
	tests := []struct {
		name       string
		body       string
		wantStatus int
	}{
		{
			name:       "invalid JSON",
			body:       "{invalid json}",
			wantStatus: fasthttp.StatusBadRequest,
		},
		{
			name:       "empty JSON",
			body:       "{}",
			wantStatus: fasthttp.StatusBadRequest,
		},
		{
			name:       "missing path and pattern",
			body:       `{"method": "GET"}`,
			wantStatus: fasthttp.StatusBadRequest,
		},
		{
			name:       "only path",
			body:       `{"path": "/test"}`,
			wantStatus: fasthttp.StatusOK,
		},
		{
			name:       "only pattern",
			body:       `{"pattern": "/api/*"}`,
			wantStatus: fasthttp.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &config.CacheAPIConfig{
				Path:  "/_cache/purge",
				Allow: []string{},
			}

			h, err := NewPurgeHandler(nil, cfg)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			ctx := &fasthttp.RequestCtx{}
			ctx.Init(&fasthttp.Request{}, nil, nil)
			ctx.Request.Header.SetMethod("POST")
			ctx.Request.SetBodyString(tt.body)

			h.ServeHTTP(ctx)

			if ctx.Response.StatusCode() != tt.wantStatus {
				t.Errorf("expected status %d, got %d", tt.wantStatus, ctx.Response.StatusCode())
			}
		})
	}
}

// TestPurgeHandler_PurgeResponse 测试 purge 响应格式。
func TestPurgeHandler_PurgeResponse(t *testing.T) {
	cfg := &config.CacheAPIConfig{
		Path:  "/_cache/purge",
		Allow: []string{},
	}

	h, err := NewPurgeHandler(nil, cfg)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	ctx := &fasthttp.RequestCtx{}
	ctx.Init(&fasthttp.Request{}, nil, nil)
	ctx.Request.Header.SetMethod("POST")
	ctx.Request.SetBodyString(`{"path": "/test"}`)

	h.ServeHTTP(ctx)

	// 验证响应
	if ctx.Response.StatusCode() != fasthttp.StatusOK {
		t.Errorf("expected status %d, got %d", fasthttp.StatusOK, ctx.Response.StatusCode())
	}

	// 验证响应体格式
	body := string(ctx.Response.Body())
	if !strings.Contains(body, `"deleted"`) {
		t.Errorf("expected 'deleted' field in response body, got: %s", body)
	}
}

// TestPurgeHandler_SendError 测试 sendError 方法的错误响应格式。
func TestPurgeHandler_SendError(t *testing.T) {
	tests := []struct {
		name     string
		status   int
		errMsg   string
		wantBody string
	}{
		{
			name:     "bad request",
			status:   fasthttp.StatusBadRequest,
			errMsg:   "invalid request",
			wantBody: `{"error":"invalid request"}`,
		},
		{
			name:     "forbidden",
			status:   fasthttp.StatusForbidden,
			errMsg:   "access denied",
			wantBody: `{"error":"access denied"}`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := &config.CacheAPIConfig{
				Path:  "/_cache/purge",
				Allow: []string{},
			}

			_, err := NewPurgeHandler(nil, cfg)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			ctx := &fasthttp.RequestCtx{}
			ctx.Init(&fasthttp.Request{}, nil, nil)

			utils.SendJSONError(ctx, tt.status, tt.errMsg)

			if ctx.Response.StatusCode() != tt.status {
				t.Errorf("expected status %d, got %d", tt.status, ctx.Response.StatusCode())
			}

			body := string(ctx.Response.Body())
			if !strings.Contains(body, tt.errMsg) {
				t.Errorf("expected '%s' in response body, got: %s", tt.errMsg, body)
			}

			// 验证内容类型
			contentType := string(ctx.Response.Header.ContentType())
			if contentType != "application/json; charset=utf-8" {
				t.Errorf("expected content-type 'application/json; charset=utf-8', got: %s", contentType)
			}
		})
	}
}

// TestPurgeHandler_CacheKeyWithMethod 测试带方法的缓存键。
func TestPurgeHandler_CacheKeyWithMethod(t *testing.T) {
	tests := []struct {
		path   string
		method string
	}{
		{"/test", "GET"},
		{"/test", "POST"},
		{"/api/users", "GET"},
		{"/api/users", "DELETE"},
	}

	for _, tt := range tests {
		t.Run(tt.path+"_"+tt.method, func(t *testing.T) {
			key := cache.HashPathWithMethod(tt.path, tt.method)
			if key == 0 {
				t.Error("expected non-zero hash key")
			}

			// 同一路径和方法应该产生相同的键
			key2 := cache.HashPathWithMethod(tt.path, tt.method)
			if key != key2 {
				t.Errorf("expected same key for same inputs, got %d and %d", key, key2)
			}

			// 同一路径不同方法应该产生不同的键
			key3 := cache.HashPathWithMethod(tt.path, "OTHER")
			if key == key3 {
				t.Errorf("expected different key for different method, got %d and %d", key, key3)
			}
		})
	}
}

// TestPurgeHandler_EmptyMethodDefaultsToGET 测试空方法默认为 GET。
func TestPurgeHandler_EmptyMethodDefaultsToGET(t *testing.T) {
	key1 := cache.HashPathWithMethod("/test", "")
	key2 := cache.HashPathWithMethod("/test", "GET")

	if key1 != key2 {
		t.Errorf("expected same key for empty and 'GET' method, got %d and %d", key1, key2)
	}
}

// TestPurgeHandler_checkAccess_NilContext 测试 checkAccess 处理。
func TestPurgeHandler_checkAccess_NilContext(t *testing.T) {
	t.Run("empty allow list allows all", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Empty allow list should allow access (returns true even with nil context)
		if !utils.CheckIPAccess(nil, h.allowed) {
			t.Error("expected checkAccess to return true with empty allow list")
		}
	})
}

// TestPurgeHandler_ServeHTTP_WithAllowList 测试带白名单的请求处理。
func TestPurgeHandler_ServeHTTP_WithAllowList(t *testing.T) {
	cfg := &config.CacheAPIConfig{
		Path:  "/_cache/purge",
		Allow: []string{"192.168.0.0/16"},
	}

	h, err := NewPurgeHandler(nil, cfg)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// 测试 POST 请求（会尝试访问控制检查）
	ctx := &fasthttp.RequestCtx{}
	ctx.Init(&fasthttp.Request{}, nil, nil)
	ctx.Request.Header.SetMethod("POST")
	ctx.Request.SetBodyString(`{"path": "/test"}`)

	h.ServeHTTP(ctx)

	// 由于无法设置 RemoteIP，checkAccess 会返回 false
	// 所以应该返回 403
	if ctx.Response.StatusCode() != fasthttp.StatusForbidden {
		t.Logf("Status: %d, Body: %s", ctx.Response.StatusCode(), string(ctx.Response.Body()))
	}
}

// TestPurgeHandler_checkAccess_WithAllowedIP 测试 checkAccess 方法。
func TestPurgeHandler_checkAccess_WithAllowedIP(t *testing.T) {
	t.Run("with allow list and nil remote", func(t *testing.T) {
		cfg := &config.CacheAPIConfig{
			Path:  "/_cache/purge",
			Allow: []string{"192.168.0.0/16"},
		}

		h, err := NewPurgeHandler(nil, cfg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Create a valid context but with nil remote address
		ctx := &fasthttp.RequestCtx{}
		ctx.Init(&fasthttp.Request{}, nil, nil)

		// context with nil remote address - should return false (no client IP)
		if utils.CheckIPAccess(ctx, h.allowed) {
			t.Error("expected checkAccess to return false with no client IP")
		}
	})
}

// TestPurgeHandler_PurgeByPath_HashConsistency 测试哈希一致性。
func TestPurgeHandler_PurgeByPath_HashConsistency(t *testing.T) {
	// 验证相同路径和方法产生相同哈希
	path := "/api/users"
	method := "GET"

	hash1 := cache.HashPathWithMethod(path, method)
	hash2 := cache.HashPathWithMethod(path, method)

	if hash1 != hash2 {
		t.Errorf("hash not consistent: %d != %d", hash1, hash2)
	}

	// 验证不同路径产生不同哈希
	hash3 := cache.HashPathWithMethod("/api/posts", method)
	if hash1 == hash3 {
		t.Error("expected different hashes for different paths")
	}

	// 验证不同方法产生不同哈希
	hash4 := cache.HashPathWithMethod(path, "POST")
	if hash1 == hash4 {
		t.Error("expected different hashes for different methods")
	}
}

// TestPurgeHandler_PurgeByPattern_PatternMatching 测试模式匹配逻辑。
func TestPurgeHandler_PurgeByPattern_PatternMatching(t *testing.T) {
	tests := []struct {
		pattern string
		path    string
		want    bool
	}{
		// 通配符结尾 - 前缀匹配
		{"/api/*", "/api/users", true},
		{"/api/*", "/api/posts", true},
		{"/api/*", "/api/users/123", true}, // * 匹配剩余所有内容
		{"/api/*", "/other/path", false},

		// 单个 * 匹配所有
		{"*", "/api/users", true},
		{"*", "/any/path", true},

		// 中间通配符
		{"/api/*/users", "/api/v1/users", true},
		{"/api/*/users", "/api/v2/users", true},
		{"/api/*/users", "/api/users", true}, // 前缀和后缀都匹配
		{"/api/*/users", "/api/v1/posts", false},

		// 精确匹配
		{"/api/users", "/api/users", true},
		{"/api/users", "/api/posts", false},

		// 空模式
		{"", "", true},
		{"", "/api", false},

		// 目录前缀匹配（以 / 结尾）
		{"/api/", "/api/users", true},
		{"/api/", "/api/users/123", true},
		{"/api/", "/other/path", false},
	}

	for _, tt := range tests {
		t.Run(tt.pattern+"_"+tt.path, func(t *testing.T) {
			got := cache.MatchPattern(tt.pattern, tt.path)
			if got != tt.want {
				t.Errorf("MatchPattern(%q, %q) = %v, want %v", tt.pattern, tt.path, got, tt.want)
			}
		})
	}
}
