package providers

import (
	"strings"
	"testing"
)

// ── Profile Schema Tests ────────────────────

func TestGetProfileSchema_BuiltIn(t *testing.T) {
	for _, id := range []string{"openai", "anthropic", "venice", "openrouter"} {
		s := GetProfileSchema(id)
		if len(s.Fields) == 0 {
			t.Errorf("GetProfileSchema(%q) returned empty fields", id)
		}
		// Every schema should have system_prompt_prefix
		found := false
		for _, f := range s.Fields {
			if f.Key == "system_prompt_prefix" {
				found = true
			}
		}
		if !found {
			t.Errorf("GetProfileSchema(%q) missing system_prompt_prefix field", id)
		}
	}
}

func TestGetProfileSchema_UnknownFallsToOpenAI(t *testing.T) {
	s := GetProfileSchema("unknown-provider")
	oai := GetProfileSchema("openai")
	if len(s.Fields) != len(oai.Fields) {
		t.Errorf("Unknown provider should fall back to openai schema, got %d fields, want %d", len(s.Fields), len(oai.Fields))
	}
}

// ── Hook PreRequest Tests ───────────────────

func TestOpenAIHooks_SystemPromptPrefix(t *testing.T) {
	h := &OpenAIHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"system_prompt_prefix": "You are a helpful pirate.",
		},
	}
	req := &CompletionRequest{
		Messages: []Message{
			{Role: "system", Content: "Be concise."},
			{Role: "user", Content: "Hello"},
		},
	}

	h.PreRequest(cfg, req)

	if req.Messages[0].Content != "You are a helpful pirate.\n\nBe concise." {
		t.Errorf("system prompt prefix not prepended, got: %s", req.Messages[0].Content)
	}
}

func TestOpenAIHooks_SystemPromptPrefix_NoExisting(t *testing.T) {
	h := &OpenAIHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"system_prompt_prefix": "Be a pirate.",
		},
	}
	req := &CompletionRequest{
		Messages: []Message{
			{Role: "user", Content: "Hello"},
		},
	}

	h.PreRequest(cfg, req)

	if len(req.Messages) != 2 {
		t.Fatalf("expected 2 messages, got %d", len(req.Messages))
	}
	if req.Messages[0].Role != "system" || req.Messages[0].Content != "Be a pirate." {
		t.Errorf("expected injected system message, got role=%s content=%s", req.Messages[0].Role, req.Messages[0].Content)
	}
}

func TestOpenAIHooks_FrequencyPenalty(t *testing.T) {
	h := &OpenAIHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"frequency_penalty": 0.5,
		},
	}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hello"}}}

	h.PreRequest(cfg, req)

	if req.ExtraBody == nil {
		t.Fatal("ExtraBody should be set")
	}
	if v, ok := req.ExtraBody["frequency_penalty"].(float64); !ok || v != 0.5 {
		t.Errorf("frequency_penalty: got %v, want 0.5", req.ExtraBody["frequency_penalty"])
	}
}

func TestOpenAIHooks_NilSettings(t *testing.T) {
	h := &OpenAIHooks{}
	cfg := ProviderConfig{Settings: nil}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hello"}}}

	h.PreRequest(cfg, req) // should not panic
	if req.ExtraBody != nil {
		t.Errorf("ExtraBody should be nil with no settings")
	}
}

func TestAnthropicHooks_ExtendedThinking(t *testing.T) {
	h := &AnthropicHooks{}
	temp := 0.7
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"extended_thinking": true,
			"thinking_budget":   float64(20000), // JSON numbers are float64
		},
	}
	req := &CompletionRequest{
		Messages:    []Message{{Role: "user", Content: "Think hard"}},
		Temperature: &temp,
	}

	h.PreRequest(cfg, req)

	if req.Temperature != nil {
		t.Error("Temperature should be cleared for extended thinking")
	}
	if req.ExtraBody == nil {
		t.Fatal("ExtraBody should be set")
	}
	thinking, ok := req.ExtraBody["thinking"].(map[string]interface{})
	if !ok {
		t.Fatal("ExtraBody[thinking] should be map")
	}
	if thinking["type"] != "enabled" {
		t.Errorf("thinking.type = %v, want enabled", thinking["type"])
	}
	if thinking["budget_tokens"] != 20000 {
		t.Errorf("thinking.budget_tokens = %v, want 20000", thinking["budget_tokens"])
	}
}

func TestAnthropicHooks_ThinkingDefaultBudget(t *testing.T) {
	h := &AnthropicHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"extended_thinking": true,
			// no thinking_budget → should use default 10000
		},
	}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Think"}}}

	h.PreRequest(cfg, req)

	thinking := req.ExtraBody["thinking"].(map[string]interface{})
	if thinking["budget_tokens"] != 10000 {
		t.Errorf("default budget = %v, want 10000", thinking["budget_tokens"])
	}
}

func TestAnthropicHooks_NoThinking(t *testing.T) {
	h := &AnthropicHooks{}
	temp := 0.7
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"extended_thinking": false,
		},
	}
	req := &CompletionRequest{
		Messages:    []Message{{Role: "user", Content: "Hi"}},
		Temperature: &temp,
	}

	h.PreRequest(cfg, req)

	if req.ExtraBody != nil {
		t.Error("ExtraBody should be nil when thinking is disabled")
	}
	if req.Temperature == nil {
		t.Error("Temperature should be preserved when thinking is off")
	}
}

func TestVeniceHooks_WebSearch(t *testing.T) {
	h := &VeniceHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"enable_web_search": true,
		},
	}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Search for news"}}}

	h.PreRequest(cfg, req)

	vp, ok := req.ExtraBody["venice_parameters"].(map[string]interface{})
	if !ok {
		t.Fatal("venice_parameters missing from ExtraBody")
	}
	if vp["enable_web_search"] != "always" {
		t.Errorf("enable_web_search = %v, want 'always'", vp["enable_web_search"])
	}
}

func TestVeniceHooks_ThinkingDisablesSystemPrompt(t *testing.T) {
	h := &VeniceHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"enable_thinking": true,
		},
	}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Think"}}}

	h.PreRequest(cfg, req)

	vp := req.ExtraBody["venice_parameters"].(map[string]interface{})
	if vp["include_venice_system_prompt"] != false {
		t.Error("Thinking mode should set include_venice_system_prompt=false")
	}
}

func TestVeniceHooks_NoSettings(t *testing.T) {
	h := &VeniceHooks{}
	cfg := ProviderConfig{Settings: map[string]interface{}{}}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hi"}}}

	h.PreRequest(cfg, req)

	if req.ExtraBody != nil {
		t.Error("ExtraBody should be nil with empty settings")
	}
}

func TestOpenRouterHooks_Route(t *testing.T) {
	h := &OpenRouterHooks{}
	cfg := ProviderConfig{
		Settings: map[string]interface{}{
			"route":              "fallback",
			"require_parameters": true,
		},
		CustomHeaders: map[string]string{},
	}
	req := &CompletionRequest{Messages: []Message{{Role: "user", Content: "Hi"}}}

	h.PreRequest(cfg, req)

	// Note: OpenRouter hooks set headers on the cfg copy, which is local.
	// The real effect is tested via the serialized header value.
	// Since cfg is passed by value in hooks, this test verifies the logic runs without panic.
}

// ── GetHooks Tests ──────────────────────────

func TestGetHooks_BuiltIn(t *testing.T) {
	for _, id := range []string{"openai", "anthropic", "venice", "openrouter"} {
		h := GetHooks(id)
		if h == nil {
			t.Errorf("GetHooks(%q) returned nil", id)
		}
	}
}

func TestGetHooks_Unknown(t *testing.T) {
	h := GetHooks("nonexistent")
	if h != nil {
		t.Error("GetHooks for unknown type should return nil")
	}
}

// ── MergePersonaSettings Tests ──────────────

func TestMergePersonaSettings_Basic(t *testing.T) {
	provider := map[string]interface{}{
		"system_prompt_prefix": "Provider prompt",
		"frequency_penalty":    0.3,
	}
	persona := map[string]interface{}{
		"system_prompt_prefix": "Persona prompt",
	}

	merged := MergePersonaSettings("openai", provider, persona)

	if merged["system_prompt_prefix"] != "Persona prompt" {
		t.Errorf("persona should override provider, got %v", merged["system_prompt_prefix"])
	}
	if merged["frequency_penalty"] != 0.3 {
		t.Errorf("provider value should be preserved, got %v", merged["frequency_penalty"])
	}
}

func TestMergePersonaSettings_ProviderOnlyBlocked(t *testing.T) {
	provider := map[string]interface{}{
		"route": "auto",
	}
	persona := map[string]interface{}{
		"route": "fallback", // route is ProviderOnly for openrouter
	}

	merged := MergePersonaSettings("openrouter", provider, persona)

	if merged["route"] != "auto" {
		t.Errorf("ProviderOnly field should not be overridden by persona, got %v", merged["route"])
	}
}

func TestMergePersonaSettings_EmptyOverrides(t *testing.T) {
	provider := map[string]interface{}{"key": "value"}
	merged := MergePersonaSettings("openai", provider, nil)
	if merged["key"] != "value" {
		t.Error("empty overrides should return provider settings unchanged")
	}
}

// ── mergeExtraBody Tests ────────────────────

func TestMergeExtraBody(t *testing.T) {
	body := []byte(`{"model":"gpt-4","stream":true}`)
	extra := map[string]interface{}{
		"venice_parameters": map[string]interface{}{
			"enable_web_search": "always",
		},
	}

	merged, err := mergeExtraBody(body, extra)
	if err != nil {
		t.Fatalf("mergeExtraBody error: %v", err)
	}

	// Should contain both original and extra fields
	s := string(merged)
	if !strings.Contains(s, `"model"`) || !strings.Contains(s, `"stream"`) {
		t.Error("original fields missing from merged body")
	}
	if !strings.Contains(s, `"venice_parameters"`) || !strings.Contains(s, `"enable_web_search"`) {
		t.Error("extra fields missing from merged body")
	}
}
