package provider

import (
	"crypto/ed25519"
	"crypto/rand"
	"strings"
	"testing"

	"go.yaml.in/yaml/v3"

	"github.com/tidbcloud/AgentTeam/desktop/internal/crypto"
)

func generateTestKeys(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
	t.Helper()
	pub, pri, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("generate ed25519 key pair: %v", err)
	}
	return pub, pri
}

func initTestKeys(t *testing.T) {
	t.Helper()
	pub, pri := generateTestKeys(t)
	if err := crypto.SetKeys(pub, pri); err != nil {
		t.Fatalf("set crypto keys: %v", err)
	}
}

func TestProvider_MarshalYAML_EncryptsAPIKey(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	p := Provider{
		ID:     "test-provider",
		Name:   "Test",
		APIKey: "sk-super-secret-key-12345",
	}

	data, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("MarshalYAML: %v", err)
	}

	yamlStr := string(data)
	if strings.Contains(yamlStr, "sk-super-secret-key-12345") {
		t.Fatal("API key should not appear in plaintext in YAML output")
	}
}

func TestProvider_MarshalYAML_DoesNotModifyOriginal(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	p := Provider{
		ID:     "test-provider",
		APIKey: "sk-original-key",
	}

	_, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("MarshalYAML: %v", err)
	}

	if p.APIKey != "sk-original-key" {
		t.Fatalf("original APIKey modified: got %q, want %q", p.APIKey, "sk-original-key")
	}
}

func TestProvider_UnmarshalYAML_DecryptsAPIKey(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	original := Provider{
		ID:       "test-provider",
		Name:     "Test",
		APIKey:   "sk-my-precious-key",
		WireAPIs: []string{WireAPIAnthropic},
	}

	data, err := yaml.Marshal(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var decoded Provider
	if err := yaml.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("UnmarshalYAML: %v", err)
	}

	if decoded.APIKey != "sk-my-precious-key" {
		t.Fatalf("APIKey = %q, want %q", decoded.APIKey, "sk-my-precious-key")
	}
	if decoded.ID != "test-provider" {
		t.Fatalf("ID = %q, want %q", decoded.ID, "test-provider")
	}
	if len(decoded.WireAPIs) != 1 || decoded.WireAPIs[0] != WireAPIAnthropic {
		t.Fatalf("WireAPIs = %v, want [%q]", decoded.WireAPIs, WireAPIAnthropic)
	}
}

func TestProvider_Roundtrip_PreservesAllFields(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	original := Provider{
		ID:       "full-provider",
		Name:     "Full Provider",
		BaseURL:  "https://api.openai.com/v1",
		APIKey:   "sk-test-api-key-abc123",
		Models:   []string{"gpt-4-turbo"},
		Headers:  map[string]string{"X-Custom": "header-value"},
		Source:   "manual",
		WireAPIs: []string{WireAPIResponses},
		EnvKey:   "OPENAI_API_KEY",
	}

	data, err := yaml.Marshal(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var decoded Provider
	if err := yaml.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if decoded.ID != original.ID {
		t.Errorf("ID = %q, want %q", decoded.ID, original.ID)
	}
	if decoded.Name != original.Name {
		t.Errorf("Name = %q, want %q", decoded.Name, original.Name)
	}
	if decoded.BaseURL != original.BaseURL {
		t.Errorf("BaseURL = %q, want %q", decoded.BaseURL, original.BaseURL)
	}
	if decoded.APIKey != original.APIKey {
		t.Errorf("APIKey = %q, want %q", decoded.APIKey, original.APIKey)
	}
	if len(decoded.Models) != len(original.Models) {
		t.Errorf("Models length = %d, want %d", len(decoded.Models), len(original.Models))
	}
	if len(decoded.Models) > 0 && decoded.Models[0] != original.Models[0] {
		t.Errorf("Models[0] = %q, want %q", decoded.Models[0], original.Models[0])
	}
	if len(decoded.WireAPIs) != len(original.WireAPIs) {
		t.Errorf("WireAPIs length = %d, want %d", len(decoded.WireAPIs), len(original.WireAPIs))
	} else if len(decoded.WireAPIs) > 0 && decoded.WireAPIs[0] != original.WireAPIs[0] {
		t.Errorf("WireAPIs[0] = %q, want %q", decoded.WireAPIs[0], original.WireAPIs[0])
	}
	if decoded.EnvKey != original.EnvKey {
		t.Errorf("EnvKey = %q, want %q", decoded.EnvKey, original.EnvKey)
	}
	if len(decoded.Headers) != len(original.Headers) {
		t.Errorf("Headers length = %d, want %d", len(decoded.Headers), len(original.Headers))
	}
	if decoded.Headers["X-Custom"] != original.Headers["X-Custom"] {
		t.Errorf("Headers[X-Custom] = %q, want %q", decoded.Headers["X-Custom"], original.Headers["X-Custom"])
	}
}

func TestProvider_UnmarshalYAML_PlaintextFallback(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	yamlInput := `
id: legacy-provider
name: Legacy Provider
api_key: sk-plaintext-legacy-key
enabled: true
`

	var p Provider
	if err := yaml.Unmarshal([]byte(yamlInput), &p); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if p.APIKey != "sk-plaintext-legacy-key" {
		t.Fatalf("APIKey = %q, want %q (plaintext should be preserved)", p.APIKey, "sk-plaintext-legacy-key")
	}
}

func TestProvider_MarshalYAML_EmptyAPIKey(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	p := Provider{
		ID:     "no-key-provider",
		APIKey: "",
	}

	data, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	if strings.Contains(string(data), "api_key:") {
		t.Fatal("empty API key should be omitted from YAML")
	}

	var decoded Provider
	if err := yaml.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if decoded.APIKey != "" {
		t.Fatalf("APIKey = %q, want empty", decoded.APIKey)
	}
}

func TestProvider_MarshalYAML_NoKeysSet(t *testing.T) {
	crypto.ResetKeys()

	p := Provider{
		ID:     "test-provider",
		APIKey: "sk-plaintext-when-no-keys",
	}

	data, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	if !strings.Contains(string(data), "sk-plaintext-when-no-keys") {
		t.Fatal("API key should appear in plaintext when no encryption keys are set")
	}
}

func TestProvider_UnmarshalYAML_NoKeysSet(t *testing.T) {
	crypto.ResetKeys()

	yamlInput := `
id: test-provider
api_key: sk-plaintext-key
enabled: true
`

	var p Provider
	if err := yaml.Unmarshal([]byte(yamlInput), &p); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if p.APIKey != "sk-plaintext-key" {
		t.Fatalf("APIKey = %q, want %q", p.APIKey, "sk-plaintext-key")
	}
}

func TestProvider_MarshalYAML_WrongKeyCannotDecrypt(t *testing.T) {
	pub1, pri1 := generateTestKeys(t)
	pub2, pri2 := generateTestKeys(t)

	if err := crypto.SetKeys(pub1, pri1); err != nil {
		t.Fatal(err)
	}

	p := Provider{
		ID:     "test-provider",
		APIKey: "sk-secret-key",
	}

	data, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	if err := crypto.SetKeys(pub2, pri2); err != nil {
		t.Fatal(err)
	}

	var decoded Provider
	if err := yaml.Unmarshal(data, &decoded); err == nil {
		t.Fatal("expected unmarshal with wrong key to fail")
	}

	if decoded.APIKey == "sk-secret-key" {
		t.Fatal("API key should NOT be decrypted with wrong key")
	}

	yamlStr := string(data)
	if strings.Contains(yamlStr, "sk-secret-key") {
		t.Fatal("plaintext key should not appear in YAML")
	}
}

func TestProvider_Roundtrip_MultipleProviders(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	type providersFile struct {
		Providers []Provider `yaml:"providers"`
	}

	original := providersFile{
		Providers: []Provider{
			{ID: "openai-provider", Name: "OpenAI", APIKey: "sk-openai-key-123", WireAPIs: []string{WireAIOpenAI}},
			{ID: "anthropic-provider", Name: "Anthropic", APIKey: "sk-ant-key-456", WireAPIs: []string{WireAPIAnthropic}},
			{ID: "custom-provider", Name: "Custom", APIKey: "sk-custom-key-789", WireAPIs: []string{WireAIOpenAI}},
		},
	}

	data, err := yaml.Marshal(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	yamlStr := string(data)
	if strings.Contains(yamlStr, "sk-openai-key-123") {
		t.Fatal("openai key should be encrypted")
	}
	if strings.Contains(yamlStr, "sk-ant-key-456") {
		t.Fatal("anthropic key should be encrypted")
	}
	if strings.Contains(yamlStr, "sk-custom-key-789") {
		t.Fatal("custom key should be encrypted")
	}

	var decoded providersFile
	if err := yaml.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if len(decoded.Providers) != 3 {
		t.Fatalf("providers count = %d, want 3", len(decoded.Providers))
	}

	expectedKeys := map[string]string{
		"openai-provider":    "sk-openai-key-123",
		"anthropic-provider": "sk-ant-key-456",
		"custom-provider":    "sk-custom-key-789",
	}

	for _, p := range decoded.Providers {
		wantKey, ok := expectedKeys[p.ID]
		if !ok {
			t.Errorf("unexpected provider %q", p.ID)
			continue
		}
		if p.APIKey != wantKey {
			t.Errorf("provider %q: APIKey = %q, want %q", p.ID, p.APIKey, wantKey)
		}
	}
}

func TestProvider_Roundtrip_DoubleMarshal(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	p := Provider{
		ID:     "test-provider",
		APIKey: "sk-double-marshal-test",
	}

	data1, err := yaml.Marshal(p)
	if err != nil {
		t.Fatalf("first marshal: %v", err)
	}

	var p2 Provider
	if err := yaml.Unmarshal(data1, &p2); err != nil {
		t.Fatalf("first unmarshal: %v", err)
	}

	if p2.APIKey != "sk-double-marshal-test" {
		t.Fatalf("first roundtrip: APIKey = %q", p2.APIKey)
	}

	data2, err := yaml.Marshal(p2)
	if err != nil {
		t.Fatalf("second marshal: %v", err)
	}

	var p3 Provider
	if err := yaml.Unmarshal(data2, &p3); err != nil {
		t.Fatalf("second unmarshal: %v", err)
	}

	if p3.APIKey != "sk-double-marshal-test" {
		t.Fatalf("second roundtrip: APIKey = %q", p3.APIKey)
	}
}

func TestProvider_EncryptDifferentEachTime(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	p := Provider{
		ID:     "test-provider",
		APIKey: "sk-same-key",
	}

	data1, _ := yaml.Marshal(p)
	data2, _ := yaml.Marshal(p)

	if string(data1) == string(data2) {
		t.Fatal("marshal should produce different ciphertext each time (random nonce)")
	}
}

func TestProvider_SpecialCharsInAPIKey(t *testing.T) {
	initTestKeys(t)
	defer crypto.ResetKeys()

	keys := []string{
		"sk-proj-abc123_DEF-456/ghi=789+jkl",
		"key\twith\nspecial\rchars",
		`{"api_key": "sk-test-123"}`,
		strings.Repeat("x", 500),
	}

	for _, key := range keys {
		t.Run(key[:20], func(t *testing.T) {
			p := Provider{
				ID:     "test-provider",
				APIKey: key,
			}

			data, err := yaml.Marshal(p)
			if err != nil {
				t.Fatalf("marshal: %v", err)
			}

			var decoded Provider
			if err := yaml.Unmarshal(data, &decoded); err != nil {
				t.Fatalf("unmarshal: %v", err)
			}

			if decoded.APIKey != key {
				t.Fatalf("APIKey mismatch: got %q, want %q", decoded.APIKey, key)
			}
		})
	}
}

func TestValidate(t *testing.T) {
	tests := []struct {
		name    string
		p       Provider
		wantErr bool
	}{
		{"valid", Provider{ID: "p1", WireAPIs: []string{WireAIOpenAI}}, false},
		{"empty ID", Provider{ID: "", WireAPIs: []string{WireAIOpenAI}}, true},
		{"whitespace ID", Provider{ID: "  ", WireAPIs: []string{WireAIOpenAI}}, true},
		{"invalid wire_api", Provider{ID: "p1", WireAPIs: []string{"invalid"}}, true},
		{"empty wire_api defaults", Provider{ID: "p1"}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := Validate(tt.p); (err != nil) != tt.wantErr {
				t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestNormalize(t *testing.T) {
	p := Normalize(Provider{
		ID:      "  p1  ",
		Name:    " My Provider ",
		BaseURL: " http://example.com ",
		APIKey:  " sk-123 ",
		Models:  []string{" gpt-4 "},
		Headers: nil,
	})

	if p.ID != "p1" {
		t.Errorf("ID = %q, want %q", p.ID, "p1")
	}
	if p.Name != "My Provider" {
		t.Errorf("Name = %q, want %q", p.Name, "My Provider")
	}
	if p.BaseURL != "http://example.com" {
		t.Errorf("BaseURL = %q, want %q", p.BaseURL, "http://example.com")
	}
	if p.APIKey != "sk-123" {
		t.Errorf("APIKey = %q, want %q", p.APIKey, "sk-123")
	}
	if len(p.Models) != 1 || p.Models[0] != " gpt-4 " {
		t.Errorf("Models[0] = %q, want %q", p.Models[0], " gpt-4 ")
	}
	if p.Headers == nil {
		t.Error("Headers should be initialized to empty map, not nil")
	}
	if len(p.WireAPIs) != 1 || p.WireAPIs[0] != WireAIOpenAI {
		t.Errorf("WireAPIs = %v, want [%q]", p.WireAPIs, WireAIOpenAI)
	}
}

func TestNormalize_PreservesHeaders(t *testing.T) {
	p := Normalize(Provider{
		ID: "p1",
		Headers: map[string]string{
			"X-Custom": "value",
		},
	})

	if len(p.Headers) != 1 {
		t.Errorf("Headers length = %d, want 1", len(p.Headers))
	}
	if p.Headers["X-Custom"] != "value" {
		t.Errorf("Headers[X-Custom] = %q, want %q", p.Headers["X-Custom"], "value")
	}
}

func TestAllWireAPIs(t *testing.T) {
	if len(AllWireAPIs) != 3 {
		t.Errorf("AllWireAPIs length = %d, want 3", len(AllWireAPIs))
	}
}
