package agent

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"
	"go.yaml.in/yaml/v3"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
)

func TestValidateAgent(t *testing.T) {
	tests := []struct {
		name    string
		agent   Config
		wantErr bool
	}{
		{
			"valid",
			Config{ID: "dev", Runtime: "opencode"},
			false,
		},
		{
			"empty ID",
			Config{ID: "", Runtime: "opencode"},
			true,
		},
		{
			"whitespace ID",
			Config{ID: "  ", Runtime: "opencode"},
			true,
		},
		{
			"empty runtime",
			Config{ID: "dev", Runtime: ""},
			true,
		},
		{
			"whitespace runtime",
			Config{ID: "dev", Runtime: "  "},
			true,
		},
		{
			"valid timeout",
			Config{ID: "dev", Runtime: "opencode", Policy: Policy{Timeout: "30m"}},
			false,
		},
		{
			"too short timeout",
			Config{ID: "dev", Runtime: "opencode", Policy: Policy{Timeout: "x"}},
			true,
		},
		{
			"negative max concurrent sessions",
			Config{ID: "dev", Runtime: "opencode", Policy: Policy{MaxConcurrentSessions: -1}},
			true,
		},
		{
			"zero max concurrent sessions is valid",
			Config{ID: "dev", Runtime: "opencode", Policy: Policy{MaxConcurrentSessions: 0}},
			false,
		},
	}

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

func TestAgentSourceString(t *testing.T) {
	require.Equal(t, "local", NewLocalSource().String())
	require.Equal(t, "agentteam:agentteam-channel-1", NewAgentTeamSource("agentteam-channel-1").String())
}

func TestValidateType(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    runtimeconfig.RuntimeType
		wantErr bool
	}{
		{"opencode", "opencode", runtimeconfig.TypeOpenCode, false},
		{"claude", "claude", runtimeconfig.TypeClaudeCode, false},
		{"claude-code alias", "claude-code", runtimeconfig.TypeClaudeCode, false},
		{"codex", "codex", runtimeconfig.TypeCodex, false},
		{"builtin", "builtin", runtimeconfig.TypeBuiltin, false},
		{"codex-app-server", "codex-app-server", "", true},
		{"unknown", "unknown", "", true},
		{"empty", "", "", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := runtimeconfig.ValidateType(tt.input)
			if (err != nil) != tt.wantErr {
				t.Errorf("ValidateType(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
				return
			}
			if !tt.wantErr && got != tt.want {
				t.Errorf("ValidateType(%q) = %q, want %q", tt.input, got, tt.want)
			}
		})
	}
}

func TestValidateRuntime(t *testing.T) {
	tests := []struct {
		name    string
		rt      runtimeconfig.Runtime
		wantErr bool
	}{
		{"valid", runtimeconfig.Runtime{Name: "my-opencode", Type: runtimeconfig.TypeOpenCode}, false},
		{"empty name", runtimeconfig.Runtime{Type: runtimeconfig.TypeOpenCode}, true},
		{"whitespace name", runtimeconfig.Runtime{Name: "  ", Type: runtimeconfig.TypeOpenCode}, true},
		{"invalid type", runtimeconfig.Runtime{Name: "test", Type: "invalid"}, true},
		{"negative port", runtimeconfig.Runtime{Name: "test", Type: runtimeconfig.TypeOpenCode, Port: -1}, true},
		{"zero port valid", runtimeconfig.Runtime{Name: "test", Type: runtimeconfig.TypeOpenCode, Port: 0}, false},
	}

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

func TestNormalizeRuntime(t *testing.T) {
	tests := []struct {
		name  string
		input runtimeconfig.Runtime
		check func(runtimeconfig.Runtime) error
	}{
		{
			"opencode defaults",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeOpenCode},
			func(rt runtimeconfig.Runtime) error {
				if rt.Name != "opencode" {
					return fmt.Errorf("Name = %q, want %q", rt.Name, "opencode")
				}
				if rt.Binary != "opencode" {
					return fmt.Errorf("Binary = %q, want %q", rt.Binary, "opencode")
				}
				if len(rt.Args) != 1 || rt.Args[0] != "serve" {
					return fmt.Errorf("Args = %v, want [serve]", rt.Args)
				}
				if rt.Health.Path != "/global/health" {
					return fmt.Errorf("Health.Path = %q", rt.Health.Path)
				}
				return nil
			},
		},
		{
			"claude defaults",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeClaudeCode},
			func(rt runtimeconfig.Runtime) error {
				if rt.Binary != "claude" {
					return fmt.Errorf("Binary = %q, want %q", rt.Binary, "claude")
				}
				return nil
			},
		},
		{
			"builtin defaults to opencode",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeBuiltin},
			func(rt runtimeconfig.Runtime) error {
				if rt.Name != "builtin" {
					return fmt.Errorf("Name = %q, want %q", rt.Name, "builtin")
				}
				if rt.Binary != "opencode" {
					return fmt.Errorf("Binary = %q, want %q", rt.Binary, "opencode")
				}
				return nil
			},
		},
		{
			"custom name preserved",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeOpenCode, Name: "my-runtime"},
			func(rt runtimeconfig.Runtime) error {
				if rt.Name != "my-runtime" {
					return fmt.Errorf("Name = %q, want %q", rt.Name, "my-runtime")
				}
				return nil
			},
		},
		{
			"whitespace trimmed",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeOpenCode, Name: "  my-runtime  "},
			func(rt runtimeconfig.Runtime) error {
				if rt.Name != "my-runtime" {
					return fmt.Errorf("Name = %q, want %q", rt.Name, "my-runtime")
				}
				return nil
			},
		},
		{
			"builtin legacy binary defaults to opencode",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeBuiltin, Binary: "builtin"},
			func(rt runtimeconfig.Runtime) error {
				if rt.Binary != "opencode" {
					return fmt.Errorf("Binary = %q, want %q", rt.Binary, "opencode")
				}
				return nil
			},
		},
		{
			"builtin prior claude fallback binary defaults to opencode",
			runtimeconfig.Runtime{Type: runtimeconfig.TypeBuiltin, Binary: "claude"},
			func(rt runtimeconfig.Runtime) error {
				if rt.Binary != "opencode" {
					return fmt.Errorf("Binary = %q, want %q", rt.Binary, "opencode")
				}
				return nil
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := runtimeconfig.NormalizeRuntime(tt.input)
			if err != nil {
				t.Fatalf("NormalizeRuntime() error = %v", err)
			}
			if check := tt.check(got); check != nil {
				t.Error(check)
			}
		})
	}
}

func TestNormalizeRuntime_Invalid(t *testing.T) {
	_, err := runtimeconfig.NormalizeRuntime(runtimeconfig.Runtime{Type: "invalid"})
	if err == nil {
		t.Error("expected error for invalid runtime type")
	}
}

func TestDefaultBinary(t *testing.T) {
	tests := []struct {
		runtime runtimeconfig.RuntimeType
		want    string
	}{
		{runtimeconfig.TypeOpenCode, "opencode"},
		{runtimeconfig.TypeClaudeCode, "claude"},
		{runtimeconfig.TypeCodex, "codex"},
		{runtimeconfig.TypeKimi, "kimi"},
		{runtimeconfig.TypeBuiltin, "opencode"},
	}

	for _, tt := range tests {
		got := runtimeconfig.DefaultBinary(tt.runtime)
		if got != tt.want {
			t.Errorf("DefaultBinary(%s) = %q, want %q", tt.runtime, got, tt.want)
		}
	}
}

func TestAllRuntimeTypes(t *testing.T) {
	if len(runtimeconfig.AllRuntimeTypes) != 6 {
		t.Errorf("AllRuntimeTypes length = %d, want 6", len(runtimeconfig.AllRuntimeTypes))
	}
}

func TestConfigYAMLMarshalUsesUnprefixedLLMFields(t *testing.T) {
	cfg := Config{
		ID:      "agt_1",
		Name:    "assistant",
		Runtime: "codex",
		LLMMode: string(LLMModeInline),
		BaseURL: "https://proxy.example.com/v1",
		APIKey:  "sk-test",
		Headers: map[string]string{"x-test": "1"},
		WireAPI: "responses",
		Model:   "gpt-5.4",
	}

	data, err := yaml.Marshal(cfg)
	require.NoError(t, err)

	rendered := string(data)
	require.Contains(t, rendered, "mode: inline")
	require.Contains(t, rendered, "base_url: https://proxy.example.com/v1")
	require.Contains(t, rendered, "api_key: sk-test")
	require.Contains(t, rendered, "wire_api: responses")
	require.NotContains(t, rendered, "llm_mode:")
	require.NotContains(t, rendered, "llm_base_url:")
	require.NotContains(t, rendered, "llm_api_key:")
	require.NotContains(t, rendered, "llm_headers:")
	require.NotContains(t, rendered, "llm_wire_api:")
}

func TestConfigYAMLUnmarshalAcceptsLegacyPrefixedLLMFields(t *testing.T) {
	raw := []byte(`
id: agt_1
name: assistant
runtime: codex
llm_mode: inline
llm_base_url: https://proxy.example.com/v1
llm_api_key: sk-test
llm_headers:
  x-test: "1"
llm_wire_api: responses
model: gpt-5.4
`)

	var cfg Config
	err := yaml.Unmarshal(raw, &cfg)
	require.NoError(t, err)
	require.Equal(t, "agt_1", cfg.ID)
	require.Equal(t, "assistant", cfg.Name)
	require.Equal(t, "codex", cfg.Runtime)
	require.Equal(t, string(LLMModeInline), cfg.LLMMode)
	require.Equal(t, "https://proxy.example.com/v1", cfg.BaseURL)
	require.Equal(t, "sk-test", cfg.APIKey)
	require.Equal(t, map[string]string{"x-test": "1"}, cfg.Headers)
	require.Equal(t, "responses", cfg.WireAPI)
	require.Equal(t, "gpt-5.4", cfg.Model)
}

func TestRuntimePriority(t *testing.T) {
	if runtimeconfig.RuntimePriority(runtimeconfig.TypeBuiltin) != 0 {
		t.Errorf("builtin priority = %d, want 0", runtimeconfig.RuntimePriority(runtimeconfig.TypeBuiltin))
	}
	if runtimeconfig.RuntimePriority(runtimeconfig.TypeOpenCode) != 1 {
		t.Errorf("opencode priority = %d, want 1", runtimeconfig.RuntimePriority(runtimeconfig.TypeOpenCode))
	}
	if runtimeconfig.RuntimePriority(runtimeconfig.TypeClaudeCode) != 2 {
		t.Errorf("claude priority = %d, want 2", runtimeconfig.RuntimePriority(runtimeconfig.TypeClaudeCode))
	}
	if runtimeconfig.RuntimePriority(runtimeconfig.TypeCodex) != 3 {
		t.Errorf("codex priority = %d, want 3", runtimeconfig.RuntimePriority(runtimeconfig.TypeCodex))
	}
	if runtimeconfig.RuntimePriority(runtimeconfig.TypeKimi) != 4 {
		t.Errorf("kimi priority = %d, want 4", runtimeconfig.RuntimePriority(runtimeconfig.TypeKimi))
	}
	if runtimeconfig.RuntimePriority(runtimeconfig.TypePi) != 5 {
		t.Errorf("pi priority = %d, want 5", runtimeconfig.RuntimePriority(runtimeconfig.TypePi))
	}
	if runtimeconfig.RuntimePriority("unknown") != 6 {
		t.Errorf("unknown priority = %d, want 6", runtimeconfig.RuntimePriority("unknown"))
	}
}
