package anthropic

import (
	"encoding/json"
	"testing"
	"time"

	"nex/backend/internal/conversion"
	"nex/backend/internal/conversion/canonical"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDecodeTools(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}],
		"tools": [
			{"name": "search", "description": "Search", "input_schema": {"type":"object"}},
			{"name": "calc", "input_schema": {"type":"object"}}
		]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Len(t, req.Tools, 2)
	assert.Equal(t, "search", req.Tools[0].Name)
	assert.Equal(t, "Search", req.Tools[0].Description)
	assert.Equal(t, "calc", req.Tools[1].Name)
}

func TestDecodeToolChoice(t *testing.T) {
	tests := []struct {
		name     string
		jsonBody string
		wantType string
		wantName string
	}{
		{
			"auto string",
			`{"model":"claude-3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}`,
			"auto", "",
		},
		{
			"none string",
			`{"model":"claude-3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}],"tool_choice":"none"}`,
			"none", "",
		},
		{
			"any string",
			`{"model":"claude-3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}],"tool_choice":"any"}`,
			"any", "",
		},
		{
			"tool object",
			`{"model":"claude-3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"tool","name":"search"}}`,
			"tool", "search",
		},
		{
			"auto object",
			`{"model":"claude-3","max_tokens":1024,"messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"auto"}}`,
			"auto", "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, err := decodeRequest([]byte(tt.jsonBody))
			require.NoError(t, err)
			require.NotNil(t, req.ToolChoice)
			assert.Equal(t, tt.wantType, req.ToolChoice.Type)
			assert.Equal(t, tt.wantName, req.ToolChoice.Name)
		})
	}
}

func TestDecodeParameters_TopK(t *testing.T) {
	topK := 10
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}],
		"top_k": 10,
		"stop_sequences": ["STOP"]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	require.NotNil(t, req.Parameters.TopK)
	assert.Equal(t, topK, *req.Parameters.TopK)
	assert.Equal(t, []string{"STOP"}, req.Parameters.StopSequences)
}

func TestDecodeRequest_MetadataUserID(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}],
		"metadata": {"user_id": "user-123"}
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Equal(t, "user-123", req.UserID)
}

func TestDecodeSystem_Empty(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"system": "",
		"messages": [{"role": "user", "content": "hi"}]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Nil(t, req.System)
}

func TestDecodeSystem_Nil(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Nil(t, req.System)
}

func TestDecodeThinking_WithEffort(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}],
		"thinking": {"type": "enabled", "budget_tokens": 5000},
		"output_config": {"effort": "high"}
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	require.NotNil(t, req.Thinking)
	assert.Equal(t, "enabled", req.Thinking.Type)
	assert.Equal(t, "high", req.Thinking.Effort)
}

func TestDecodeOutputFormat_NilOutputConfig(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [{"role": "user", "content": "hi"}]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Nil(t, req.OutputFormat)
}

func TestDecodeMessage_UserWithOnlyToolResults(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 1024,
		"messages": [
			{"role": "user", "content": "hi"},
			{"role": "assistant", "content": [{"type": "tool_use", "id": "t1", "name": "fn", "input": {}}]},
			{
				"role": "user",
				"content": [{"type": "tool_result", "tool_use_id": "t1", "content": "result"}]
			}
		]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	lastMsg := req.Messages[len(req.Messages)-1]
	assert.Equal(t, canonical.RoleTool, lastMsg.Role)
	assert.Equal(t, "t1", lastMsg.Content[0].ToolUseID)
}

func TestDecodeContentBlocks_Nil(t *testing.T) {
	blocks, err := decodeContentBlocks(nil)
	require.NoError(t, err)
	assert.Len(t, blocks, 1)
	assert.Equal(t, "", blocks[0].Text)
}

func TestDecodeContentBlocks_String(t *testing.T) {
	blocks, err := decodeContentBlocks("hello")
	require.NoError(t, err)
	assert.Len(t, blocks, 1)
	assert.Equal(t, "hello", blocks[0].Text)
}

func TestParseTimestamp(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  int64
	}{
		{"valid RFC3339", "2024-01-15T00:00:00Z", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Unix()},
		{"empty", "", 0},
		{"invalid", "not-a-date", 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, parseTimestamp(tt.input))
		})
	}
}

func TestEncodeToolChoice(t *testing.T) {
	tests := []struct {
		name   string
		choice *canonical.ToolChoice
		want   map[string]any
	}{
		{"auto", canonical.NewToolChoiceAuto(), map[string]any{"type": "auto"}},
		{"none", canonical.NewToolChoiceNone(), map[string]any{"type": "none"}},
		{"any", canonical.NewToolChoiceAny(), map[string]any{"type": "any"}},
		{"tool", canonical.NewToolChoiceNamed("search"), map[string]any{"type": "tool", "name": "search"}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := encodeToolChoice(tt.choice)
			r, ok := result.(map[string]any)
			require.True(t, ok)
			assert.Equal(t, tt.want["type"], r["type"])
			assert.Equal(t, tt.want["name"], r["name"])
		})
	}
}

func TestEncodeThinkingConfig(t *testing.T) {
	budget := 5000
	tests := []struct {
		name string
		cfg  *canonical.ThinkingConfig
		want map[string]any
	}{
		{"enabled", &canonical.ThinkingConfig{Type: "enabled", BudgetTokens: &budget}, map[string]any{"type": "enabled", "budget_tokens": float64(5000)}},
		{"disabled", &canonical.ThinkingConfig{Type: "disabled"}, map[string]any{"type": "disabled"}},
		{"adaptive", &canonical.ThinkingConfig{Type: "adaptive"}, map[string]any{"type": "adaptive"}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := encodeThinkingConfig(tt.cfg)
			assert.Equal(t, tt.want["type"], result["type"])
		})
	}
}

func TestEncodeRequest_PublicFields(t *testing.T) {
	maxTokens := 1024
	parallel := false
	req := &canonical.CanonicalRequest{
		Model:           "claude-3",
		Parameters:      canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:        []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		UserID:          "user-123",
		ParallelToolUse: &parallel,
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, map[string]any{"user_id": "user-123"}, result["metadata"])
	assert.Equal(t, true, result["disable_parallel_tool_use"])
}

func TestEncodeRequest_DefaultMaxTokens(t *testing.T) {
	req := &canonical.CanonicalRequest{
		Model:    "claude-3",
		Messages: []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, float64(4096), result["max_tokens"])
}

func TestEncodeRequest_TopK(t *testing.T) {
	maxTokens := 1024
	topK := 10
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens, TopK: &topK},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, float64(10), result["top_k"])
}

func TestEncodeRequest_WithTools(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		Tools: []canonical.CanonicalTool{
			{Name: "search", Description: "Search things", InputSchema: json.RawMessage(`{"type":"object"}`)},
		},
		ToolChoice: canonical.NewToolChoiceAuto(),
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	tools, okt := result["tools"].([]any)
	require.True(t, okt)
	assert.Len(t, tools, 1)
	tool, okt2 := tools[0].(map[string]any)
	require.True(t, okt2)
	assert.Equal(t, "search", tool["name"])
	assert.Equal(t, "Search things", tool["description"])
	tc, oktc := result["tool_choice"].(map[string]any)
	require.True(t, oktc)
	assert.Equal(t, "auto", tc["type"])
}

func TestEncodeRequest_ThinkingWithEffort(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		Thinking:   &canonical.ThinkingConfig{Type: "enabled", Effort: "high"},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	oc, ok := result["output_config"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "high", oc["effort"])
}

func TestEncodeResponse_UsageWithCacheAndCreation(t *testing.T) {
	cacheRead := 30
	cacheCreation := 10
	sr := canonical.StopReasonEndTurn
	resp := &canonical.CanonicalResponse{
		ID:         "msg-1",
		Model:      "claude-3",
		Content:    []canonical.ContentBlock{canonical.NewTextBlock("ok")},
		StopReason: &sr,
		Usage: canonical.CanonicalUsage{
			InputTokens:         100,
			OutputTokens:        50,
			CacheReadTokens:     &cacheRead,
			CacheCreationTokens: &cacheCreation,
		},
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	usage, oku := result["usage"].(map[string]any)
	require.True(t, oku)
	assert.Equal(t, float64(100), usage["input_tokens"])
	assert.Equal(t, float64(30), usage["cache_read_input_tokens"])
	assert.Equal(t, float64(10), usage["cache_creation_input_tokens"])
}

func TestEncodeResponse_StopReasons(t *testing.T) {
	tests := []struct {
		name       string
		stopReason canonical.StopReason
		want       string
	}{
		{"end_turn", canonical.StopReasonEndTurn, "end_turn"},
		{"max_tokens", canonical.StopReasonMaxTokens, "max_tokens"},
		{"tool_use", canonical.StopReasonToolUse, "tool_use"},
		{"stop_sequence", canonical.StopReasonStopSequence, "stop_sequence"},
		{"refusal", canonical.StopReasonRefusal, "refusal"},
		{"content_filter→end_turn", canonical.StopReasonContentFilter, "end_turn"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sr := tt.stopReason
			resp := &canonical.CanonicalResponse{
				ID:         "r1",
				Model:      "claude-3",
				Content:    []canonical.ContentBlock{canonical.NewTextBlock("ok")},
				StopReason: &sr,
			}
			body, err := encodeResponse(resp)
			require.NoError(t, err)

			var result map[string]any
			require.NoError(t, json.Unmarshal(body, &result))
			assert.Equal(t, tt.want, result["stop_reason"])
		})
	}
}

func TestEncodeSystem_SystemBlocks(t *testing.T) {
	result := encodeSystem([]canonical.SystemBlock{{Text: "part1"}, {Text: "part2"}})
	blocks, ok := result.([]map[string]any)
	require.True(t, ok)
	assert.Len(t, blocks, 2)
	assert.Equal(t, "part1", blocks[0]["text"])
}

func TestEncodeModelInfoResponse(t *testing.T) {
	info := &canonical.CanonicalModelInfo{
		ID:      "claude-3-opus",
		Name:    "Claude 3 Opus",
		Created: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Unix(),
	}

	body, err := encodeModelInfoResponse(info)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, "claude-3-opus", result["id"])
	assert.Equal(t, "Claude 3 Opus", result["display_name"])
}

func TestDecodeModelInfoResponse(t *testing.T) {
	body := []byte(`{"id":"claude-3-opus","type":"model","display_name":"Claude 3 Opus","created_at":"2024-01-15T00:00:00Z"}`)
	info, err := decodeModelInfoResponse(body)
	require.NoError(t, err)
	assert.Equal(t, "claude-3-opus", info.ID)
	assert.Equal(t, "Claude 3 Opus", info.Name)
	assert.NotEqual(t, int64(0), info.Created)
}

func TestDecodeResponse_PauseTurn(t *testing.T) {
	body := []byte(`{
		"id": "msg-1", "type": "message", "role": "assistant", "model": "claude-3",
		"content": [{"type": "text", "text": "ok"}],
		"stop_reason": "pause_turn",
		"usage": {"input_tokens": 1, "output_tokens": 1}
	}`)
	resp, err := decodeResponse(body)
	require.NoError(t, err)
	assert.Equal(t, canonical.StopReason("pause_turn"), *resp.StopReason)
}

func TestEncodeResponse_NoStopReason(t *testing.T) {
	resp := &canonical.CanonicalResponse{
		ID:      "msg-1",
		Model:   "claude-3",
		Content: []canonical.ContentBlock{canonical.NewTextBlock("ok")},
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, "end_turn", result["stop_reason"])
}

func TestDecodeRequest_MaxTokensZero(t *testing.T) {
	body := []byte(`{
		"model": "claude-3",
		"max_tokens": 0,
		"messages": [{"role": "user", "content": "hi"}]
	}`)
	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Nil(t, req.Parameters.MaxTokens)
}
