package client

import (
	"testing"

	"gokin/internal/config"
)

// newDeepSeekTestConfig mirrors newKimiTestConfig for the deepseek
// factory path — a minimal config that lets newDeepSeekClient build
// without network calls. Key is long enough to pass ValidateKeyFormat.
func newDeepSeekTestConfig() *config.Config {
	return &config.Config{
		API: config.APIConfig{
			DeepSeekKey: "sk-deepseek-test-key-for-unit-test-12345",
		},
		Model: config.ModelConfig{
			Name:            "deepseek-v4-pro",
			MaxOutputTokens: 4096,
		},
	}
}

// TestSupportsDeepSeekThinking pins which DeepSeek models get Extended
// Thinking auto-enabled. V4 (pro + flash) and legacy reasoner: yes.
// deepseek-chat: no — it's a pure chat model and the API rejects
// thinking blocks on that route.
func TestSupportsDeepSeekThinking(t *testing.T) {
	cases := []struct {
		model string
		want  bool
	}{
		// V4 generation — both variants support thinking.
		{"deepseek-v4-pro", true},
		{"deepseek-v4-flash", true},
		{"DeepSeek-V4-Pro", true}, // case-insensitive
		// Legacy reasoner.
		{"deepseek-reasoner", true},
		// Plain chat must NOT get thinking — endpoint rejects it.
		{"deepseek-chat", false},
		// Bare "deepseek" is a family-level fallback — treat as V4-ish.
		{"deepseek", true},
		// Non-DeepSeek models must not trip.
		{"glm-5.1", false},
		{"kimi-for-coding", false},
		{"", false},
		{"random", false},
	}
	for _, c := range cases {
		t.Run(c.model, func(t *testing.T) {
			if got := supportsDeepSeekThinking(c.model); got != c.want {
				t.Errorf("supportsDeepSeekThinking(%q) = %v, want %v", c.model, got, c.want)
			}
		})
	}
}

func TestNewClient_DetectsDeepSeekProviderFromModelName(t *testing.T) {
	cfg := newDeepSeekTestConfig()
	cfg.API.ActiveProvider = "glm"
	cfg.Model.Provider = ""

	pool := GetPool(cfg)
	pool.FlushProvider("deepseek")
	t.Cleanup(func() {
		pool.FlushProvider("deepseek")
	})

	c, err := NewClient(t.Context(), cfg, "deepseek-v4-pro")
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}
	t.Cleanup(func() {
		_ = c.Close()
	})

	if _, ok := pool.Get("deepseek", "deepseek-v4-pro"); !ok {
		t.Fatal("NewClient should cache inferred DeepSeek client under provider key deepseek")
	}
	if _, ok := pool.Get("glm", "deepseek-v4-pro"); ok {
		t.Fatal("NewClient should not cache DeepSeek model under stale active provider glm")
	}
}

// TestNewDeepSeekClient_AutoEnablesThinking confirms that a user who
// did not configure thinking still gets it on for V4 Pro. Parallels
// TestNewKimiClient_AutoEnablesThinking.
func TestNewDeepSeekClient_AutoEnablesThinking(t *testing.T) {
	cfg := newDeepSeekTestConfig()
	c, err := newDeepSeekClient(cfg, "deepseek-v4-pro")
	if err != nil {
		t.Fatalf("newDeepSeekClient: %v", err)
	}
	ac, ok := c.(*AnthropicClient)
	if !ok {
		t.Fatalf("expected *AnthropicClient, got %T", c)
	}
	if !ac.config.EnableThinking {
		t.Error("EnableThinking should auto-flip to true for deepseek-v4-pro")
	}
	if ac.config.ThinkingBudget != defaultDeepSeekThinkingBudget {
		t.Errorf("ThinkingBudget = %d, want %d (auto-default)",
			ac.config.ThinkingBudget, defaultDeepSeekThinkingBudget)
	}
	if ac.config.Provider != "deepseek" {
		t.Errorf("Provider = %q, want deepseek", ac.config.Provider)
	}
	if ac.config.BaseURL != DefaultDeepSeekBaseURL {
		t.Errorf("BaseURL = %q, want %q", ac.config.BaseURL, DefaultDeepSeekBaseURL)
	}
}

// TestNewDeepSeekClient_NoThinkingForChat confirms deepseek-chat is
// correctly left without a thinking budget — otherwise API requests
// to that route would 400.
func TestNewDeepSeekClient_NoThinkingForChat(t *testing.T) {
	cfg := newDeepSeekTestConfig()
	cfg.Model.Name = "deepseek-chat"
	c, err := newDeepSeekClient(cfg, "deepseek-chat")
	if err != nil {
		t.Fatalf("newDeepSeekClient: %v", err)
	}
	ac := c.(*AnthropicClient)
	if ac.config.EnableThinking {
		t.Error("thinking must NOT auto-enable for deepseek-chat")
	}
	if ac.config.ThinkingBudget != 0 {
		t.Errorf("ThinkingBudget should stay 0 for deepseek-chat, got %d",
			ac.config.ThinkingBudget)
	}
}

// TestNewDeepSeekClient_CustomBaseURLOverrides confirms that a user
// can point at an alternate endpoint (e.g. a corporate proxy or a
// future region-specific mirror) via model.custom_base_url.
func TestNewDeepSeekClient_CustomBaseURLOverrides(t *testing.T) {
	cfg := newDeepSeekTestConfig()
	cfg.Model.CustomBaseURL = "https://proxy.example.internal/anthropic"
	c, err := newDeepSeekClient(cfg, "deepseek-v4-pro")
	if err != nil {
		t.Fatalf("newDeepSeekClient: %v", err)
	}
	ac := c.(*AnthropicClient)
	if ac.config.BaseURL != "https://proxy.example.internal/anthropic" {
		t.Errorf("custom base URL not honoured: got %q", ac.config.BaseURL)
	}
}

// TestNewDeepSeekClient_MissingKey confirms the factory returns a
// user-friendly error when no API key is configured.
func TestNewDeepSeekClient_MissingKey(t *testing.T) {
	cfg := &config.Config{
		Model: config.ModelConfig{Name: "deepseek-v4-pro"},
	}
	_, err := newDeepSeekClient(cfg, "deepseek-v4-pro")
	if err == nil {
		t.Fatal("expected error for missing DeepSeek key")
	}
}

// TestAnthropicClient_SupportsPromptCachingForDeepSeek pins that the
// caching gate recognises the DeepSeek base URL. Live testing (Apr
// 2026) confirmed DeepSeek V4 honours cache_control markers and
// reports cache_read_input_tokens on repeat prefixes — a 1.2K-token
// system prompt saw 95% input-token reduction on the second call.
// If this test starts failing, someone dropped the DeepSeek clause
// from supportsPromptCaching and users are now paying full price
// per turn.
func TestAnthropicClient_SupportsPromptCachingForDeepSeek(t *testing.T) {
	cases := []struct {
		baseURL string
		want    bool
	}{
		// DeepSeek endpoints — must all pass the gate.
		{"https://api.deepseek.com/anthropic", true},
		{"https://api.deepseek.com/anthropic/v1/messages", true},
		// Other Anthropic-compat endpoints we already support.
		{"https://api.kimi.com/coding", true},
		{"https://api.moonshot.ai/anthropic", true},
		{"https://api.minimax.io/anthropic", true},
		// Native Anthropic.
		{DefaultAnthropicBaseURL, true},
		{"", true}, // empty BaseURL defaults to Anthropic
		// GLM is known not to honour cache_control — still gated off.
		{"https://api.z.ai/api/anthropic", false},
		// Unknown provider — conservative off.
		{"https://random.example.com/api", false},
	}
	for _, c := range cases {
		t.Run(c.baseURL, func(t *testing.T) {
			ac := &AnthropicClient{config: AnthropicConfig{BaseURL: c.baseURL}}
			if got := ac.supportsPromptCaching(); got != c.want {
				t.Errorf("supportsPromptCaching(BaseURL=%q) = %v, want %v",
					c.baseURL, got, c.want)
			}
		})
	}
}
