package providers

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

// newDashScopeTestServer sets up a mock SSE server for ChatStream calls and
// returns both the server and a pointer that will hold the last captured request body.
func newDashScopeTestServer(t *testing.T) (*httptest.Server, *map[string]any) {
	t.Helper()
	captured := &map[string]any{}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := json.NewDecoder(r.Body).Decode(captured); err != nil {
			t.Errorf("failed to decode request body: %v", err)
		}
		// stream=false path (Chat): return JSON
		if v, _ := (*captured)["stream"].(bool); !v {
			w.Header().Set("Content-Type", "application/json")
			fmt.Fprint(w, `{"choices":[{"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}]}`)
			return
		}
		// stream=true path (ChatStream): return SSE
		w.Header().Set("Content-Type", "text/event-stream")
		fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n\n")
		fmt.Fprint(w, "data: [DONE]\n\n")
	}))
	t.Cleanup(server.Close)
	return server, captured
}

// callDashScopeStream sends req through ChatStream and returns the captured body.
func callDashScopeStream(t *testing.T, req ChatRequest) map[string]any {
	t.Helper()
	server, captured := newDashScopeTestServer(t)
	p := NewDashScopeProvider("dashscope-test", "test-key", server.URL, "")
	p.retryConfig.Attempts = 1
	p.ChatStream(context.Background(), req, nil) //nolint:errcheck
	return *captured
}

// TestDashScopeModelSupportsThinking verifies the whitelist is correct.
func TestDashScopeModelSupportsThinking(t *testing.T) {
	p := NewDashScopeProvider("dashscope", "key", "", "")

	tests := []struct {
		model string
		want  bool
	}{
		{"qwen3.5-plus", true},
		{"qwen3.5-turbo", true},
		{"qwen3-max", true},
		{"qwen3-235b-a22b", true},
		{"qwen3-32b", true},
		{"qwen3-14b", true},
		{"qwen3-8b", true},
		// Models that do NOT support thinking:
		{"qwen3-plus", false},
		{"qwen3-turbo", false},
		{"qwen2-72b-instruct", false},
	}

	for _, tt := range tests {
		got := p.ModelSupportsThinking(tt.model)
		if got != tt.want {
			t.Errorf("ModelSupportsThinking(%q) = %v, want %v", tt.model, got, tt.want)
		}
	}
}

// TestDashScopeThinkingInjected_WhenModelSupports verifies enable_thinking IS sent
// for a model on the whitelist (qwen3.5-plus) when thinking_level is set.
// ChatStream is used because that's the path where thinking params are injected.
func TestDashScopeThinkingInjected_WhenModelSupports(t *testing.T) {
	body := callDashScopeStream(t, ChatRequest{
		Model:    "qwen3.5-plus",
		Messages: []Message{{Role: "user", Content: "hi"}},
		Options:  map[string]any{OptThinkingLevel: "medium"},
	})

	if body["enable_thinking"] != true {
		t.Errorf("enable_thinking = %v, want true for qwen3.5-plus with thinking_level=medium", body["enable_thinking"])
	}
	budget, _ := body["thinking_budget"].(float64)
	if budget <= 0 {
		t.Errorf("thinking_budget = %v, want > 0", body["thinking_budget"])
	}
	if _, hasThinkingLevel := body["thinking_level"]; hasThinkingLevel {
		t.Error("thinking_level should be removed from body (converted to enable_thinking)")
	}
}

// TestDashScopeThinkingNotInjected_WhenModelDoesNotSupport verifies enable_thinking
// is NOT sent for qwen3-plus (not on whitelist), even when thinking_level is set.
// This is the root cause fix: previously this caused "model 'qwen3-plus' is not supported".
func TestDashScopeThinkingNotInjected_WhenModelDoesNotSupport(t *testing.T) {
	body := callDashScopeStream(t, ChatRequest{
		Model:    "qwen3-plus",
		Messages: []Message{{Role: "user", Content: "hi"}},
		Options:  map[string]any{OptThinkingLevel: "medium"},
	})

	if _, has := body["enable_thinking"]; has {
		t.Errorf("enable_thinking should NOT be sent for qwen3-plus, got: %v", body["enable_thinking"])
	}
	if _, has := body["thinking_budget"]; has {
		t.Errorf("thinking_budget should NOT be sent for qwen3-plus, got: %v", body["thinking_budget"])
	}
}

// TestDashScopeNoThinkingLevel verifies that without thinking_level,
// enable_thinking is never injected regardless of model.
func TestDashScopeNoThinkingLevel(t *testing.T) {
	body := callDashScopeStream(t, ChatRequest{
		Model:    "qwen3-max",
		Messages: []Message{{Role: "user", Content: "hi"}},
		Options:  map[string]any{OptMaxTokens: 1},
	})

	if _, has := body["enable_thinking"]; has {
		t.Error("enable_thinking should NOT be sent without thinking_level")
	}
}

// TestDashScopeThinkingBudgetValues verifies thinking_budget level mapping.
func TestDashScopeThinkingBudgetValues(t *testing.T) {
	tests := []struct {
		level string
		want  int
	}{
		{"low", 4096},
		{"medium", 16384},
		{"high", 32768},
		{"unknown", 16384}, // default
	}
	for _, tt := range tests {
		got := dashscopeThinkingBudget(tt.level)
		if got != tt.want {
			t.Errorf("dashscopeThinkingBudget(%q) = %d, want %d", tt.level, got, tt.want)
		}
	}
}
