package llm

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestAnthropicWireShapeAndUsage(t *testing.T) {
	var got map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/v1/messages" {
			t.Errorf("path = %s", r.URL.Path)
		}
		if r.Header.Get("x-api-key") != "k" || r.Header.Get("anthropic-version") == "" {
			t.Errorf("headers: key=%q version=%q", r.Header.Get("x-api-key"), r.Header.Get("anthropic-version"))
		}
		if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
			t.Fatal(err)
		}
		w.Write([]byte(`{"id":"msg1","model":"claude-sonnet-5","content":[{"type":"text","text":"пере"},{"type":"text","text":"вод"}],
			"stop_reason":"end_turn",
			"usage":{"input_tokens":60,"output_tokens":20,"cache_creation_input_tokens":30,"cache_read_input_tokens":10}}`))
	}))
	defer srv.Close()

	c := NewAnthropicClient(AnthropicConfig{BaseURL: srv.URL, APIKey: "k", Profile: fastProfile(), CacheTTL: "5m"}, nil)
	resp, err := c.Complete(context.Background(), LLMRequest{
		Model: "claude-sonnet-5",
		Messages: []Message{
			{Role: "system", Content: "роль"},
			{Role: "system", Content: "бриф", CacheBoundary: true},
			{Role: "user", Content: "чанк"},
		},
		MaxTokens:   512,
		Temperature: 0.4,
	})
	if err != nil {
		t.Fatal(err)
	}

	// Wire: ведущие system-сообщения стали system-блоками, cache_control — на
	// помеченном блоке, с TTL.
	sys, ok := got["system"].([]any)
	if !ok || len(sys) != 2 {
		t.Fatalf("system blocks = %v", got["system"])
	}
	first := sys[0].(map[string]any)
	if _, hasCC := first["cache_control"]; hasCC {
		t.Fatal("unmarked block must not carry cache_control")
	}
	second := sys[1].(map[string]any)
	cc, ok := second["cache_control"].(map[string]any)
	if !ok || cc["type"] != "ephemeral" || cc["ttl"] != "5m" {
		t.Fatalf("cache_control = %v", second["cache_control"])
	}
	msgs, ok := got["messages"].([]any)
	if !ok || len(msgs) != 1 {
		t.Fatalf("messages = %v", got["messages"])
	}

	// Usage-инвариант: PromptTokens = input + cache_read + cache_creation.
	if resp.Usage.PromptTokens != 100 || resp.Usage.CachedTokens != 10 || resp.Usage.CacheCreationTokens != 30 {
		t.Fatalf("usage = %+v", resp.Usage)
	}
	if resp.Text != "перевод" || resp.FinishReason != FinishStop || resp.Model != "claude-sonnet-5" {
		t.Fatalf("resp = %+v", resp)
	}
	// Sampling-параметры на этот wire не отправляются: актуальные Claude-модели
	// отклоняют temperature с 400.
	if _, has := got["temperature"]; has {
		t.Fatal("temperature must not be sent to the Anthropic wire")
	}
}

func TestAnthropicRejectsUnmappedReasoning(t *testing.T) {
	c := NewAnthropicClient(AnthropicConfig{BaseURL: "http://unused", APIKey: "k", Profile: fastProfile()}, nil)
	_, err := c.Complete(context.Background(), LLMRequest{
		Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16, ReasoningEffort: "high",
	})
	if err == nil {
		t.Fatal("unmapped reasoning depth must fail loud, not silently run without thinking")
	}
}

func TestAnthropicStopReasonMapping(t *testing.T) {
	cases := map[string]string{
		"end_turn":   FinishStop,
		"max_tokens": FinishLength,
		"refusal":    FinishRefusal,
		"weird":      "",
	}
	for wire, want := range cases {
		if got := mapAnthropicStopReason(wire); got != want {
			t.Errorf("map(%q) = %q, want %q", wire, got, want)
		}
	}
}

func TestAnthropicTooManyBoundaries(t *testing.T) {
	c := NewAnthropicClient(AnthropicConfig{BaseURL: "http://unused", APIKey: "k", Profile: fastProfile()}, nil)
	msgs := make([]Message, 5)
	for i := range msgs {
		msgs[i] = Message{Role: "system", Content: "x", CacheBoundary: true}
	}
	_, err := c.Complete(context.Background(), LLMRequest{Model: "m", Messages: msgs, MaxTokens: 16})
	if err == nil {
		t.Fatal("more than 4 cache boundaries must fail loud (API limit)")
	}
}

func TestAnthropicJSONOnlyUnsupported(t *testing.T) {
	c := NewAnthropicClient(AnthropicConfig{BaseURL: "http://unused", APIKey: "k", Profile: fastProfile()}, nil)
	_, err := c.Complete(context.Background(), LLMRequest{
		Model: "m", Messages: []Message{{Role: "user", Content: "u"}}, MaxTokens: 16, JSONOnly: true,
	})
	if err == nil {
		t.Fatal("JSONOnly must fail loud, not silently drop the constraint")
	}
}
