//go:build unit

package service

import (
	"net/http"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

func headerOverrideTestAccount(platform, accountType string, credentials map[string]any) *Account {
	return &Account{
		Platform:    platform,
		Type:        accountType,
		Credentials: credentials,
	}
}

func TestIsHeaderOverrideEligible(t *testing.T) {
	tests := []struct {
		name     string
		platform string
		accType  string
		want     bool
	}{
		{"anthropic apikey", PlatformAnthropic, AccountTypeAPIKey, true},
		{"openai apikey", PlatformOpenAI, AccountTypeAPIKey, true},
		{"kimi apikey", PlatformKimi, AccountTypeAPIKey, true},
		{"zhipu apikey", PlatformZhipu, AccountTypeAPIKey, true},
		{"deepseek apikey", PlatformDeepseek, AccountTypeAPIKey, true},
		{"anthropic oauth", PlatformAnthropic, AccountTypeOAuth, false},
		{"openai oauth", PlatformOpenAI, AccountTypeOAuth, false},
		{"kimi oauth", PlatformKimi, AccountTypeOAuth, false},
		{"zhipu oauth", PlatformZhipu, AccountTypeOAuth, false},
		{"deepseek oauth", PlatformDeepseek, AccountTypeOAuth, false},
		{"gemini apikey", PlatformGemini, AccountTypeAPIKey, false},
		{"grok apikey", PlatformGrok, AccountTypeAPIKey, true},
		{"grok oauth", PlatformGrok, AccountTypeOAuth, true},
		{"antigravity apikey", PlatformAntigravity, AccountTypeAPIKey, false},
		{"anthropic bedrock", PlatformAnthropic, AccountTypeBedrock, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			acc := headerOverrideTestAccount(tt.platform, tt.accType, nil)
			require.Equal(t, tt.want, acc.IsHeaderOverrideEligible())
		})
	}

	var nilAccount *Account
	require.False(t, nilAccount.IsHeaderOverrideEligible())
	require.False(t, nilAccount.IsHeaderOverrideEnabled())
	require.Nil(t, nilAccount.GetHeaderOverrides())
}

func TestIsHeaderOverrideEnabled(t *testing.T) {
	acc := headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
	})
	require.True(t, acc.IsHeaderOverrideEnabled())

	// 未配置 / 非 bool / false 均视为未启用
	require.False(t, headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, nil).IsHeaderOverrideEnabled())
	require.False(t, headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: "true",
	}).IsHeaderOverrideEnabled())
	require.False(t, headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: false,
	}).IsHeaderOverrideEnabled())

	// 不符合平台/类型条件时即使配置了 true 也不启用
	require.False(t, headerOverrideTestAccount(PlatformAnthropic, AccountTypeOAuth, map[string]any{
		credKeyHeaderOverrideEnabled: true,
	}).IsHeaderOverrideEnabled())
	require.False(t, headerOverrideTestAccount(PlatformGemini, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
	}).IsHeaderOverrideEnabled())
}

func TestGetHeaderOverrides(t *testing.T) {
	acc := headerOverrideTestAccount(PlatformOpenAI, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides: map[string]any{
			"User-Agent":    "my-agent/1.0",  // 大写 key 归一化为小写
			" X-App ":       "cli",           // 名称去空白
			"x-empty":       "",              // 空 value（模板占位）跳过
			"authorization": "Bearer leaked", // 禁止覆写的头跳过
			"bad name":      "value",         // 非法 header 名跳过
			"x-padded":      "  padded  ",    // value 去空白
		},
	})
	overrides := acc.GetHeaderOverrides()
	require.Equal(t, map[string]string{
		"user-agent": "my-agent/1.0",
		"x-app":      "cli",
		"x-padded":   "padded",
	}, overrides)

	// 未启用时返回 nil
	disabled := headerOverrideTestAccount(PlatformOpenAI, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrides: map[string]any{"user-agent": "x"},
	})
	require.Nil(t, disabled.GetHeaderOverrides())

	// 启用但全部为空 value 时返回 nil
	empty := headerOverrideTestAccount(PlatformOpenAI, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides:       map[string]any{"user-agent": ""},
	})
	require.Nil(t, empty.GetHeaderOverrides())

	// 未经 Normalize 落库的超长数据 / WebSocket 握手头在应用时被防御性跳过
	oversizedValue := strings.Repeat("a", maxHeaderOverrideValueLength+1)
	defensive := headerOverrideTestAccount(PlatformOpenAI, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides: map[string]any{
			"x-big":                    oversizedValue,
			"sec-websocket-key":        "forged",
			"content-type":             "application/json", // 名单扩充前落库的数据也要被拦截
			"x-claude-code-session-id": "pinned-session",
			"x-ok":                     "ok",
		},
	})
	require.Equal(t, map[string]string{"x-ok": "ok"}, defensive.GetHeaderOverrides())
}

func TestApplyHeaderOverrides(t *testing.T) {
	acc := headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides: map[string]any{
			"user-agent":     "override-agent/2.0",
			"anthropic-beta": "custom-beta-1",
			"x-custom":       "custom-value",
		},
	})

	h := http.Header{}
	// 模拟转发链路：canonical key 与 wire casing 原样 key 混合存在
	h.Set("User-Agent", "claude-cli/2.1.161 (external, cli)")
	h["anthropic-beta"] = []string{"claude-code-20250219,oauth-2025-04-20"} // 非 canonical 原样 key
	h.Set("Content-Type", "application/json")

	acc.ApplyHeaderOverrides(h)

	// user-agent 覆盖且只有一个值（已知头恢复 wire casing）
	require.Equal(t, []string{"override-agent/2.0"}, h["User-Agent"])
	// anthropic-beta：非 canonical 旧值被清除，写入 wire casing（小写）
	require.Equal(t, []string{"custom-beta-1"}, h["anthropic-beta"])
	require.Empty(t, h["Anthropic-Beta"])
	// 新增头（未知头以小写原样键写入，与转发链路 wire casing 约定一致）
	require.Equal(t, []string{"custom-value"}, h["x-custom"])
	require.Equal(t, "custom-value", getHeaderRaw(h, "x-custom"))
	// 未覆写的头不受影响
	require.Equal(t, "application/json", h.Get("Content-Type"))

	// 覆盖后不存在任何大小写重复
	count := 0
	for k := range h {
		if k == "anthropic-beta" || k == "Anthropic-Beta" {
			count++
		}
	}
	require.Equal(t, 1, count)
}

func TestApplyHeaderOverridesNoOpPaths(t *testing.T) {
	baseline := func() http.Header {
		h := http.Header{}
		h.Set("User-Agent", "orig")
		return h
	}

	// OAuth 账号：即使配置了覆写也不生效
	oauth := headerOverrideTestAccount(PlatformAnthropic, AccountTypeOAuth, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides:       map[string]any{"user-agent": "hacked"},
	})
	h := baseline()
	oauth.ApplyHeaderOverrides(h)
	require.Equal(t, "orig", h.Get("User-Agent"))

	// 未启用开关
	off := headerOverrideTestAccount(PlatformAnthropic, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrides: map[string]any{"user-agent": "hacked"},
	})
	h = baseline()
	off.ApplyHeaderOverrides(h)
	require.Equal(t, "orig", h.Get("User-Agent"))

	// 禁止覆写的头（authorization / x-api-key / host 等）不会被应用
	blocked := headerOverrideTestAccount(PlatformOpenAI, AccountTypeAPIKey, map[string]any{
		credKeyHeaderOverrideEnabled: true,
		credKeyHeaderOverrides: map[string]any{
			"Authorization":  "Bearer evil",
			"X-Api-Key":      "evil",
			"Host":           "evil.example.com",
			"Content-Length": "0",
		},
	})
	h = http.Header{}
	h.Set("Authorization", "Bearer real-key")
	blocked.ApplyHeaderOverrides(h)
	require.Equal(t, "Bearer real-key", h.Get("Authorization"))
	require.Empty(t, h.Get("X-Api-Key"))
	require.Empty(t, h.Get("Host"))

	// nil header 不 panic
	blocked.ApplyHeaderOverrides(nil)
}

func TestNormalizeHeaderOverrideCredentials(t *testing.T) {
	t.Run("nil credentials no-op", func(t *testing.T) {
		require.NoError(t, NormalizeHeaderOverrideCredentials(nil))
	})

	t.Run("missing keys no-op", func(t *testing.T) {
		creds := map[string]any{"api_key": "sk-xxx"}
		require.NoError(t, NormalizeHeaderOverrideCredentials(creds))
		_, exists := creds[credKeyHeaderOverrides]
		require.False(t, exists)
	})

	t.Run("normalizes names and values", func(t *testing.T) {
		creds := map[string]any{
			credKeyHeaderOverrideEnabled: true,
			credKeyHeaderOverrides: map[string]any{
				" User-Agent ": " my-agent ",
				"X-App":        "",
				"":             "", // 完全空行被丢弃
			},
		}
		require.NoError(t, NormalizeHeaderOverrideCredentials(creds))
		require.Equal(t, map[string]any{
			"user-agent": "my-agent",
			"x-app":      "",
		}, creds[credKeyHeaderOverrides])
	})

	t.Run("accepts map[string]string input", func(t *testing.T) {
		creds := map[string]any{
			credKeyHeaderOverrides: map[string]string{"X-App": "cli"},
		}
		require.NoError(t, NormalizeHeaderOverrideCredentials(creds))
		require.Equal(t, map[string]any{"x-app": "cli"}, creds[credKeyHeaderOverrides])
	})

	t.Run("rejects non-bool enabled", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrideEnabled: "yes",
		})
		require.Error(t, err)
	})

	t.Run("rejects non-object overrides", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: []any{"user-agent"},
		})
		require.Error(t, err)
	})

	t.Run("rejects non-string value", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: map[string]any{"x-app": 123},
		})
		require.Error(t, err)
	})

	t.Run("rejects invalid header name", func(t *testing.T) {
		for _, name := range []string{"bad name", "bad:name", "bad\nname", "值"} {
			err := NormalizeHeaderOverrideCredentials(map[string]any{
				credKeyHeaderOverrides: map[string]any{name: "v"},
			})
			require.Error(t, err, "name %q should be rejected", name)
		}
	})

	t.Run("rejects empty name with value", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: map[string]any{"  ": "v"},
		})
		require.Error(t, err)
	})

	t.Run("rejects blocked headers", func(t *testing.T) {
		for _, name := range []string{
			"Authorization", "x-api-key", "Host", "content-length", "Transfer-Encoding",
			"connection", "accept-encoding", "Sec-WebSocket-Key", "session_id",
			"conversation_id", "x-codex-turn-state", "chatgpt-account-id",
			"Content-Type", "Cookie", "x-goog-api-key",
			"X-Claude-Code-Session-Id", "x-client-request-id",
		} {
			err := NormalizeHeaderOverrideCredentials(map[string]any{
				credKeyHeaderOverrides: map[string]any{name: "v"},
			})
			require.Error(t, err, "blocked header %q should be rejected", name)
		}
	})

	t.Run("allows tab inside value", func(t *testing.T) {
		creds := map[string]any{
			credKeyHeaderOverrides: map[string]any{"x-app": "a\tb"},
		}
		require.NoError(t, NormalizeHeaderOverrideCredentials(creds))
		require.Equal(t, map[string]any{"x-app": "a\tb"}, creds[credKeyHeaderOverrides])
	})

	t.Run("rejects invalid value", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: map[string]any{"x-app": "bad\nvalue"},
		})
		require.Error(t, err)
	})

	t.Run("rejects duplicate names case-insensitively", func(t *testing.T) {
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: map[string]any{
				"User-Agent": "a",
				"user-agent": "b",
			},
		})
		require.Error(t, err)
	})

	t.Run("rejects too many entries", func(t *testing.T) {
		entries := make(map[string]any, maxHeaderOverrideEntries+1)
		for i := 0; i <= maxHeaderOverrideEntries; i++ {
			entries["x-h-"+string(rune('a'+i%26))+string(rune('a'+(i/26)%26))+string(rune('a'+(i/676)%26))] = "v"
		}
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: entries,
		})
		require.Error(t, err)
	})

	t.Run("rejects oversized value", func(t *testing.T) {
		big := make([]byte, maxHeaderOverrideValueLength+1)
		for i := range big {
			big[i] = 'a'
		}
		err := NormalizeHeaderOverrideCredentials(map[string]any{
			credKeyHeaderOverrides: map[string]any{"x-app": string(big)},
		})
		require.Error(t, err)
	})
}
