package profile

import (
	"crypto/ed25519"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"

	"go.yaml.in/yaml/v3"

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

func TestDefaultConfig(t *testing.T) {
	cfg := DefaultConfig()
	if cfg.Host != DefaultHost {
		t.Errorf("Host = %q, want %q", cfg.Host, DefaultHost)
	}
	if cfg.Port != DefaultPort {
		t.Errorf("Port = %d, want %d", cfg.Port, DefaultPort)
	}
	if cfg.Language != "zh-CN" {
		t.Errorf("Language = %q, want %q", cfg.Language, "zh-CN")
	}
	if cfg.Log.File != "agentteam.log" {
		t.Errorf("Log.File = %q, want %q", cfg.Log.File, "agentteam.log")
	}
}

func TestNormalizeLanguageSupportsJapanese(t *testing.T) {
	if got := NormalizeLanguage("ja-JP"); got != "ja" {
		t.Fatalf("NormalizeLanguage(ja-JP) = %q, want ja", got)
	}
}

func TestLogFile(t *testing.T) {
	t.Run("relative file under configured dir", func(t *testing.T) {
		cfg := &Config{Log: Log{Dir: filepath.Join("base", "logs"), File: "agentteam.log"}}
		want := filepath.Join("base", "logs", "agentteam.log")
		if got := cfg.LogFile(); got != want {
			t.Errorf("LogFile() = %q, want %q", got, want)
		}
	})

	t.Run("absolute file is returned as-is", func(t *testing.T) {
		abs := filepath.Join(t.TempDir(), "custom.log")
		cfg := &Config{Log: Log{Dir: filepath.Join("base", "logs"), File: abs}}
		if got := cfg.LogFile(); got != abs {
			t.Errorf("LogFile() = %q, want %q", got, abs)
		}
	})
}

func TestLoadOrDefaults_NonExistent(t *testing.T) {
	cfg, err := LoadOrDefaults("/nonexistent/config.yaml")
	if err != nil {
		t.Fatalf("LoadOrDefaults error = %v", err)
	}
	if cfg == nil {
		t.Fatal("LoadOrDefaults returned nil config")
	}
	if cfg.Host != DefaultHost {
		t.Errorf("Host = %q, want %q", cfg.Host, DefaultHost)
	}
}

func TestLoad(t *testing.T) {
	t.Run("non-existent returns defaults", func(t *testing.T) {
		cfg, err := Load("/nonexistent/config.yaml")
		if err != nil {
			t.Fatalf("Load error = %v", err)
		}
		if cfg.Port != DefaultPort {
			t.Errorf("Port = %d, want %d", cfg.Port, DefaultPort)
		}
	})

	t.Run("valid config", func(t *testing.T) {
		dir := t.TempDir()
		path := filepath.Join(dir, "config.yaml")

		config := Config{
			Host:     "0.0.0.0",
			Port:     9090,
			Language: "en",
		}
		data, _ := yaml.Marshal(config)
		if err := os.WriteFile(path, data, 0644); err != nil {
			t.Fatalf("WriteFile error = %v", err)
		}

		cfg, err := Load(path)
		if err != nil {
			t.Fatalf("Load error = %v", err)
		}
		if cfg.Host != "0.0.0.0" {
			t.Errorf("Host = %q, want %q", cfg.Host, "0.0.0.0")
		}
		if cfg.Port != 9090 {
			t.Errorf("Port = %d, want 9090", cfg.Port)
		}
		if cfg.Language != "en" {
			t.Errorf("Language = %q, want %q", cfg.Language, "en")
		}
	})
}

func TestSave(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "config.yaml")

	cfg := &Config{
		Host:     "0.0.0.0",
		Port:     9090,
		Language: "en",
	}

	if err := Save(cfg, path); err != nil {
		t.Fatalf("Save error = %v", err)
	}

	loaded, err := Load(path)
	if err != nil {
		t.Fatalf("Load error = %v", err)
	}
	if loaded.Host != "0.0.0.0" {
		t.Errorf("Host = %q, want %q", loaded.Host, "0.0.0.0")
	}
	if loaded.Port != 9090 {
		t.Errorf("Port = %d, want 9090", loaded.Port)
	}
}

func TestConfigSaveAndLoadUsePrivateMode(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "config.yaml")
	cfg := &Config{Host: "127.0.0.1", Port: 8080}

	if err := Save(cfg, path); err != nil {
		t.Fatalf("Save error = %v", err)
	}
	info, err := os.Stat(path)
	if err != nil {
		t.Fatalf("Stat error = %v", err)
	}
	if got := info.Mode().Perm(); got != 0o600 {
		t.Fatalf("config mode after Save = %o, want 0600", got)
	}

	if err := os.Chmod(path, 0o644); err != nil {
		t.Fatalf("Chmod error = %v", err)
	}
	if _, err := Load(path); err != nil {
		t.Fatalf("Load error = %v", err)
	}
	info, err = os.Stat(path)
	if err != nil {
		t.Fatalf("Stat error = %v", err)
	}
	if got := info.Mode().Perm(); got != 0o600 {
		t.Fatalf("config mode after Load = %o, want 0600", got)
	}
}

func TestSavePreservesUnrelatedConfigFieldsFromConcurrentSaves(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "config.yaml")

	base := DefaultConfig()
	base.Port = 49152
	base.DefaultAgent = "agent-primary"
	base.UI.Theme = "dark"
	base.Security = Security{
		PublicKey:  Base64Bytes("public-key"),
		PrivateKey: Base64Bytes("private-key"),
	}
	if err := Save(base, path); err != nil {
		t.Fatalf("Save base error = %v", err)
	}

	staleSettings := DefaultConfig()
	staleSettings.UI.Language = "en"
	if err := Save(staleSettings, path); err != nil {
		t.Fatalf("Save stale settings error = %v", err)
	}

	loaded, err := Load(path)
	if err != nil {
		t.Fatalf("Load error = %v", err)
	}
	if got := string(loaded.Security.PublicKey); got != "public-key" {
		t.Fatalf("Security.PublicKey = %q, want preserved public-key", got)
	}
	if got := string(loaded.Security.PrivateKey); got != "private-key" {
		t.Fatalf("Security.PrivateKey = %q, want preserved private-key", got)
	}
	if loaded.Port != 49152 {
		t.Fatalf("Port = %d, want preserved selected port 49152", loaded.Port)
	}
	if loaded.DefaultAgent != "agent-primary" {
		t.Fatalf("DefaultAgent = %q, want preserved agent-primary", loaded.DefaultAgent)
	}
	if loaded.UI.Theme != "dark" {
		t.Fatalf("UI.Theme = %q, want preserved dark", loaded.UI.Theme)
	}
	if loaded.UI.Language != "en" {
		t.Fatalf("UI.Language = %q, want updated en", loaded.UI.Language)
	}
}

func TestSaveReplacesInvalidSecurityWithIncomingValidKeys(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "config.yaml")

	base := DefaultConfig()
	base.Security = Security{
		PublicKey:  Base64Bytes("bad-public-key"),
		PrivateKey: Base64Bytes("bad-private-key"),
	}
	if err := Save(base, path); err != nil {
		t.Fatalf("Save base error = %v", err)
	}

	repair := DefaultConfig()
	repair.Security = Security{
		PublicKey:  Base64Bytes(strings.Repeat("p", ed25519.PublicKeySize)),
		PrivateKey: Base64Bytes(strings.Repeat("s", ed25519.PrivateKeySize)),
	}
	if err := Save(repair, path); err != nil {
		t.Fatalf("Save repair error = %v", err)
	}

	loaded, err := Load(path)
	if err != nil {
		t.Fatalf("Load error = %v", err)
	}
	if got := len(loaded.Security.PublicKey); got != ed25519.PublicKeySize {
		t.Fatalf("Security.PublicKey size = %d, want %d", got, ed25519.PublicKeySize)
	}
	if got := string(loaded.Security.PublicKey); got != strings.Repeat("p", ed25519.PublicKeySize) {
		t.Fatalf("Security.PublicKey = %q, want repaired key", got)
	}
	if got := len(loaded.Security.PrivateKey); got != ed25519.PrivateKeySize {
		t.Fatalf("Security.PrivateKey size = %d, want %d", got, ed25519.PrivateKeySize)
	}
}

func TestSaveSerializesConcurrentConfigWrites(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "config.yaml")
	start := make(chan struct{})
	errs := make(chan error, 2)
	var wg sync.WaitGroup
	wg.Add(2)

	save := func(cfg *Config) {
		defer wg.Done()
		<-start
		errs <- Save(cfg, path)
	}

	securityConfig := DefaultConfig()
	securityConfig.Security = Security{
		PublicKey:  Base64Bytes("public-key"),
		PrivateKey: Base64Bytes("private-key"),
	}
	settingsConfig := DefaultConfig()
	settingsConfig.UI.Theme = "dark"

	go save(securityConfig)
	go save(settingsConfig)
	close(start)
	wg.Wait()
	close(errs)

	for err := range errs {
		if err != nil {
			t.Fatalf("concurrent Save error = %v", err)
		}
	}
	loaded, err := Load(path)
	if err != nil {
		t.Fatalf("Load error = %v", err)
	}
	if got := string(loaded.Security.PublicKey); got != "public-key" {
		t.Fatalf("Security.PublicKey = %q, want public-key", got)
	}
	if loaded.UI.Theme != "dark" {
		t.Fatalf("UI.Theme = %q, want dark", loaded.UI.Theme)
	}
}

func TestSensitiveYAMLFilesUsePrivateMode(t *testing.T) {
	for _, name := range []string{
		"config.yaml",
		"agents.yaml",
		"channels.yaml",
		"apps.yaml",
		"mcp-servers.yaml",
		"dbaas.yaml",
		"databases.yaml",
	} {
		t.Run(name, func(t *testing.T) {
			dir := t.TempDir()
			path := filepath.Join(dir, name)

			if err := saveYAMLFile(map[string]string{"secret": "value"}, path, nil); err != nil {
				t.Fatalf("saveYAMLFile error = %v", err)
			}
			info, err := os.Stat(path)
			if err != nil {
				t.Fatalf("Stat error = %v", err)
			}
			if got := info.Mode().Perm(); got != 0o600 {
				t.Fatalf("%s mode after save = %o, want 0600", name, got)
			}

			if err := os.Chmod(path, 0o644); err != nil {
				t.Fatalf("Chmod error = %v", err)
			}
			var data map[string]string
			if err := loadYAMLFile(path, &data); err != nil {
				t.Fatalf("loadYAMLFile error = %v", err)
			}
			info, err = os.Stat(path)
			if err != nil {
				t.Fatalf("Stat error = %v", err)
			}
			if got := info.Mode().Perm(); got != 0o600 {
				t.Fatalf("%s mode after load = %o, want 0600", name, got)
			}
		})
	}
}

func TestDashboardURL(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		cfg := &Config{Host: "127.0.0.1", Port: 8080}
		url := DashboardURL(cfg)
		if url != "http://127.0.0.1:8080/" {
			t.Errorf("DashboardURL = %q", url)
		}
	})

	t.Run("devel mode", func(t *testing.T) {
		t.Setenv(DevelEnvKey, "127.0.0.1:5173")
		cfg := &Config{Host: "127.0.0.1", Port: 8080}
		url := DashboardURL(cfg)
		if url != "http://127.0.0.1:5173/" {
			t.Errorf("DashboardURL = %q", url)
		}
	})
}

func TestEffectiveLanguagePrefersUILanguage(t *testing.T) {
	cfg := &Config{
		Language: "en",
		UI:       UI{Language: "zh"},
	}

	if got := EffectiveLanguage(cfg); got != "zh-CN" {
		t.Fatalf("EffectiveLanguage() = %q, want zh-CN", got)
	}
}

func TestIsVerbose_DefaultsFalse(t *testing.T) {
	if IsVerbose() {
		t.Error("IsVerbose should be false by default")
	}
}

func TestVerboseEnvKey(t *testing.T) {
	if VerboseEnvKey != "AGENTTEAM_VERBOSE" {
		t.Errorf("VerboseEnvKey = %q", VerboseEnvKey)
	}
}

func TestEnsureDir(t *testing.T) {
	dir := t.TempDir()
	oldConfigDir := configDir
	t.Cleanup(func() {
		configDir = oldConfigDir
	})
	configDir = filepath.Join(dir, "subdir", "config")

	if err := EnsureDir(); err != nil {
		t.Fatalf("EnsureDir error = %v", err)
	}

	info, err := os.Stat(configDir)
	if err != nil {
		t.Fatalf("Stat error = %v", err)
	}
	if !info.IsDir() {
		t.Error("EnsureDir should create a directory")
	}
}

func TestConstants(t *testing.T) {
	if AppName != "agentteam" {
		t.Errorf("AppName = %q", AppName)
	}
	if DefaultHost != "127.0.0.1" {
		t.Errorf("DefaultHost = %q", DefaultHost)
	}
	if DefaultPort != 0 {
		t.Errorf("DefaultPort = %d", DefaultPort)
	}
	if ConfigDirEnvKey != "AGENTTEAM_CONFIG_DIR" {
		t.Errorf("ConfigDirEnvKey = %q", ConfigDirEnvKey)
	}
	if VerboseEnvKey != "AGENTTEAM_VERBOSE" {
		t.Errorf("VerboseEnvKey = %q", VerboseEnvKey)
	}
	if DevelEnvKey != "AGENTTEAM_DEV" {
		t.Errorf("DevelEnvKey = %q", DevelEnvKey)
	}
}

func TestChannelConfig_RawMessage(t *testing.T) {
	c := AgentTeamServer{
		ID:   "test-id-123",
		Name: "My AgentTeam",
		Config: []byte(`server_url: "https://agentteam.example.com"
api_key: "abc123"
`),
	}

	if c.ID != "test-id-123" {
		t.Errorf("ID = %q, want %q", c.ID, "test-id-123")
	}
	if len(c.Config) == 0 {
		t.Error("Config should not be empty")
	}
}

func TestBase58Encode(t *testing.T) {
	tests := []struct {
		name    string
		input   []byte
		wantLen int
		noZero  bool
	}{
		{"zero bytes", []byte{0}, 1, false},
		{"single byte 1", []byte{1}, 1, true},
		{"short hash", []byte{0xff, 0xaa, 0x55}, 5, true},
		{"8-byte hash", []byte("abcdefgh"), 11, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := base58Encode(tt.input)
			if len(result) != tt.wantLen {
				t.Errorf("base58Encode length = %d, want %d", len(result), tt.wantLen)
			}
			if tt.noZero && strings.ContainsAny(result, "0OIli") {
				t.Errorf("base58Encode result %q contains ambiguous characters", result)
			}
		})
	}
}

func TestGenerateServerID(t *testing.T) {
	id := generateServerID()

	if !strings.HasPrefix(id, "srv_") {
		t.Fatalf("generateServerID = %q, expected prefix 'srv_'", id)
	}

	suffix := id[len("srv_"):]
	if len(suffix) != 12 {
		t.Errorf("suffix length = %d, want 12", len(suffix))
	}
}

func TestGenerateServerID_Unique(t *testing.T) {
	id1 := generateServerID()
	id2 := generateServerID()

	if id1 == id2 {
		t.Errorf("expected unique IDs, got same: %q", id1)
	}
}

func TestServerID_YAMLRoundTrip(t *testing.T) {
	ch := AgentTeamServer{
		ID:          "srv_custom",
		Name:        "My AgentTeam",
		AgentID:     "agent-1",
		Enabled:     true,
		ServerURL:   "https://agentteam.example.com",
		APIKey:      "secret",
		WorkspaceID: "wsp_1",
	}

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

	if !strings.Contains(string(data), "id: srv_custom") {
		t.Errorf("ID not found in YAML output:\n%s", string(data))
	}
	if strings.Contains(string(data), "type:") || strings.Contains(string(data), "config:") {
		t.Errorf("legacy fields should not be persisted in servers.yaml shape:\n%s", string(data))
	}

	var loaded AgentTeamServer
	if err := yaml.Unmarshal(data, &loaded); err != nil {
		t.Fatalf("Unmarshal error: %v", err)
	}

	if loaded.ID != ch.ID {
		t.Errorf("loaded ID = %q, want %q", loaded.ID, ch.ID)
	}
	if loaded.AgentID != ch.AgentID {
		t.Errorf("loaded AgentID = %q, want %q", loaded.AgentID, ch.AgentID)
	}
	if loaded.ServerURL != ch.ServerURL || loaded.APIKey != ch.APIKey || loaded.WorkspaceID != ch.WorkspaceID {
		t.Errorf("loaded server fields = (%q,%q,%q), want (%q,%q,%q)", loaded.ServerURL, loaded.APIKey, loaded.WorkspaceID, ch.ServerURL, ch.APIKey, ch.WorkspaceID)
	}
}

func TestAddChannel_GeneratesID(t *testing.T) {
	dir := t.TempDir()
	mgr := New(dir)

	ch := &AgentTeamServer{
		Name: "Test AgentTeam",
		Config: types.RawMessage(`server_url: "https://agentteam.example.com"
api_key: "secret_test"
`),
	}

	if err := mgr.Servers().Add(ch); err != nil {
		t.Fatalf("AddChannel error: %v", err)
	}

	if ch.ID == "" {
		t.Fatal("AddChannel should generate an ID")
	}

	if !strings.HasPrefix(ch.ID, "srv_") {
		t.Fatalf("ID = %q, expected prefix 'srv_'", ch.ID)
	}

	loaded, ok := mgr.Servers().Get(ch.ID)
	if !ok {
		t.Fatalf("AgentTeamServer %q not found after AddChannel", ch.ID)
	}
	if loaded.ID != ch.ID {
		t.Errorf("loaded ID = %q, want %q", loaded.ID, ch.ID)
	}
}

func TestAddChannel_IDPersisted(t *testing.T) {
	dir := t.TempDir()

	ch := &AgentTeamServer{
		Name: "Persist Test",
		Config: types.RawMessage(`server_url: "https://agentteam.example.com"
api_key: "secret_persist"
`),
	}

	mgr1 := New(dir)
	if err := mgr1.Servers().Add(ch); err != nil {
		t.Fatalf("AddChannel error: %v", err)
	}
	genID := ch.ID

	mgr2 := New(dir)
	channels := mgr2.Servers().All()
	if len(channels) != 1 {
		t.Fatalf("expected 1 channel, got %d", len(channels))
	}
	if channels[0].ID != genID {
		t.Errorf("reloaded ID = %q, want %q (ID should be stable after reload)", channels[0].ID, genID)
	}
}

func TestReload_BackfillsMissingID(t *testing.T) {
	dir := t.TempDir()

	channelsPath := filepath.Join(dir, "channels.yaml")
	content := `channels:
  - enabled: true
    name: Old AgentTeam
    config:
      server_url: "https://agentteam.example.com"
      api_key: "secret_old"
`
	if err := os.WriteFile(channelsPath, []byte(content), 0644); err != nil {
		t.Fatalf("WriteFile error: %v", err)
	}

	mgr := New(dir)
	channels := mgr.Servers().All()
	if len(channels) != 1 {
		t.Fatalf("expected 1 channel, got %d", len(channels))
	}
	if channels[0].ID == "" {
		t.Fatal("ID should be backfilled for channel without ID")
	}

	if !strings.HasPrefix(channels[0].ID, "srv_") {
		t.Fatalf("backfilled ID = %q, expected prefix 'srv_'", channels[0].ID)
	}

	data, err := os.ReadFile(filepath.Join(dir, "servers.yaml"))
	if err != nil {
		t.Fatalf("ReadFile error: %v", err)
	}
	if !strings.Contains(string(data), "id:") {
		t.Error("backfilled ID should be written to servers.yaml")
	}
	if _, err := os.Stat(channelsPath + ".bak"); err != nil {
		t.Fatalf("legacy channels.yaml should be backed up: %v", err)
	}
}

func TestReload_PreservesExistingID(t *testing.T) {
	dir := t.TempDir()

	channelsPath := filepath.Join(dir, "channels.yaml")
	content := `channels:
  - id: my-custom-id
    enabled: true
    name: Custom ID AgentTeam
    config:
      server_url: "https://agentteam.example.com"
      api_key: "secret_custom"
`
	if err := os.WriteFile(channelsPath, []byte(content), 0644); err != nil {
		t.Fatalf("WriteFile error: %v", err)
	}

	mgr := New(dir)
	channels := mgr.Servers().All()
	if len(channels) != 1 {
		t.Fatalf("expected 1 channel, got %d", len(channels))
	}
	if channels[0].ID != "my-custom-id" {
		t.Errorf("ID = %q, want %q (existing ID should be preserved)", channels[0].ID, "my-custom-id")
	}
}
