package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"textmachine/backend/internal/llm"
)

// TestResolveCapabilityMerge checks the D3.1 layering: kind baseline < provider
// default < per-model override, field-by-field with the model winning.
func TestResolveCapabilityMerge(t *testing.T) {
	m := &Models{
		Providers: map[string]Provider{
			"openai": {Kind: "openai", Capabilities: &CapabilitiesConfig{
				BudgetField: "max_completion_tokens",
				Temperature: &TemperatureCap{Mode: "omit"},
			}},
			"local": {Kind: "local"},
		},
		Models: map[string]Model{
			"gpt-5-nano": {Provider: "openai"}, // inherits the provider default
			"gpt-5-mini": {Provider: "openai", Capabilities: &CapabilitiesConfig{
				Temperature: &TemperatureCap{Mode: "force", Value: 0.7}, // overrides temp only
			}},
			"local-x": {Provider: "local"},
		},
	}

	nano := m.ResolveCapability("gpt-5-nano")
	if nano.Budget != llm.BudgetMaxCompletionTokens || nano.Temp != llm.TempOmit {
		t.Fatalf("nano must inherit provider default: %+v", nano)
	}

	mini := m.ResolveCapability("gpt-5-mini")
	if mini.Budget != llm.BudgetMaxCompletionTokens {
		t.Fatalf("mini budget must still inherit the provider: %+v", mini)
	}
	if mini.Temp != llm.TempForce || mini.TempValue != 0.7 {
		t.Fatalf("mini temperature override must win: %+v", mini)
	}

	loc := m.ResolveCapability("local-x")
	if loc.Reasoning.Control != llm.ReasoningEffortField || loc.Reasoning.OffEffort != "none" {
		t.Fatalf("local baseline must map off->none: %+v", loc.Reasoning)
	}
	if loc.Budget != llm.BudgetMaxTokens || loc.Temp != llm.TempSend {
		t.Fatalf("local baseline budget/temp: %+v", loc)
	}
}

// TestLoadModelsRejectsBadCapability confirms a bogus capability enum fails
// fast at load (part of the models.yaml problem list), not as a runtime 4xx.
func TestLoadModelsRejectsBadCapability(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "models.yaml")
	body := fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
  p:
    kind: openai
    base_url: http://x
models:
  m:
    provider: p
    price: { input_per_m: 1, output_per_m: 2 }
    capabilities:
      budget_field: bogus
`, time.Now().UTC().Format("2006-01-02"))
	if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
		t.Fatal(err)
	}
	_, err := LoadModels(path)
	if err == nil || !strings.Contains(err.Error(), "budget_field") {
		t.Fatalf("bad budget_field must fail-fast mentioning the field, got %v", err)
	}
}

// TestLoadModelsCapabilityValidation covers the other validateCapabilities
// paths the single case above misses: the PROVIDER call-site, the
// temperature.mode / reasoning.control enums, and companion-field completeness
// (a control whose disable field is absent must fail at load, not no-op at
// runtime).
func TestLoadModelsCapabilityValidation(t *testing.T) {
	cases := []struct {
		name       string
		capsYAML   string // injected under provider p or model m as noted
		onProvider bool
		wantSubstr string
	}{
		{"provider bad reasoning.control", "capabilities: { reasoning: { control: bogus } }", true, "control"},
		{"model bad temperature.mode", "capabilities: { temperature: { mode: sned } }", false, "mode"},
		{"extra_body_disable without off_extra_body", "capabilities: { reasoning: { control: extra_body_disable } }", false, "off_extra_body"},
		{"effort without off_effort", "capabilities: { reasoning: { control: effort } }", false, "off_effort"},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			provCaps, modelCaps := "", ""
			if c.onProvider {
				provCaps = "\n    " + c.capsYAML
			} else {
				modelCaps = "\n    " + c.capsYAML
			}
			body := fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
  p:
    kind: openai
    base_url: http://x%s
models:
  m:
    provider: p
    price: { input_per_m: 1, output_per_m: 2 }%s
`, time.Now().UTC().Format("2006-01-02"), provCaps, modelCaps)
			dir := t.TempDir()
			path := filepath.Join(dir, "models.yaml")
			if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
				t.Fatal(err)
			}
			_, err := LoadModels(path)
			if err == nil || !strings.Contains(err.Error(), c.wantSubstr) {
				t.Fatalf("expected fail-fast mentioning %q, got %v", c.wantSubstr, err)
			}
		})
	}
}
