package services

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	einomodel "github.com/cloudwego/eino-ext/components/model/openai"
	"github.com/cloudwego/eino/components/tool"
	"github.com/cloudwego/eino/components/tool/utils"
	"github.com/cloudwego/eino/schema"
	"wa-assistant/backend/models"
)

type providerRoundTrip func(*http.Request) (*http.Response, error)

func (f providerRoundTrip) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }

func TestDirectDeepSeekKeepsProviderCredentialsAndVisionSeparate(t *testing.T) {
	db := agenticTestDB(t)
	if err := db.AutoMigrate(&models.AppSetting{}); err != nil {
		t.Fatal(err)
	}
	for k, v := range map[string]string{"api_key": "or-fixture", "api_model": "vendor/fixture", "deepseek_api_key": "ds-fixture", "deepseek_model": "deepseek-fixture", "ai_provider": "deepseek"} {
		db.Create(&models.AppSetting{Key: k, Value: v})
	}
	p := activePreset()
	if p.Key != "deepseek" || p.BaseURL != deepSeekBase || p.APIKey != "ds-fixture" || p.Model != "deepseek-fixture" {
		t.Fatal("direct provider configuration mismatch")
	}
	if vision := openRouterPreset(); vision.BaseURL != openRouterBase || vision.APIKey != "or-fixture" {
		t.Fatal("vision lost its OpenRouter scope")
	}
	db.Model(&models.AppSetting{}).Where("`key` = ?", "ai_provider").Update("value", "openrouter")
	p = activePreset()
	if p.Key != "openrouter" || p.APIKey != "or-fixture" || p.Model != "vendor/fixture" {
		t.Fatal("switching provider mixed credentials/model")
	}
	if apiKeyForPreset(aiPreset{BaseURL: deepSeekBase}) != "ds-fixture" {
		t.Fatal("direct key fallback uses another provider")
	}
}

func TestDirectDeepSeekCatalogUsesPreviewKeyWithoutSavingOrLeakingErrors(t *testing.T) {
	db := agenticTestDB(t)
	if err := db.AutoMigrate(&models.AppSetting{}); err != nil {
		t.Fatal(err)
	}
	db.Create(&models.AppSetting{Key: "deepseek_api_key", Value: "stored-fixture"})
	previous := providerModelsClient
	t.Cleanup(func() { providerModelsClient = previous })
	status := 200
	providerModelsClient = &http.Client{Transport: providerRoundTrip(func(r *http.Request) (*http.Response, error) {
		if r.URL.String() != deepSeekBase+"/models" || r.Header.Get("Authorization") != "Bearer preview-fixture" {
			t.Fatal("wrong provider endpoint/credential")
		}
		body := `{"data":[{"id":"deepseek-v4-pro"},{"id":"deepseek-flash"},{"id":"fixture-embedding"},{"id":"fixture-tts"},{"id":""}]}`
		if status != 200 {
			body = `{"error":"preview-fixture must never reach UI"}`
		}
		return &http.Response{StatusCode: status, Header: http.Header{}, Body: io.NopCloser(strings.NewReader(body)), Request: r}, nil
	})}
	items, err := ListProviderChatModels(context.Background(), "deepseek", "preview-fixture")
	if err != nil || len(items) != 2 || items[0].ID != "deepseek-flash" || items[1].ID != "deepseek-v4-pro" {
		t.Fatal("catalog parsing failed", err)
	}
	if ActiveAIProvider() != "openrouter" || deepSeekPreset().APIKey != "stored-fixture" {
		t.Fatal("catalog preview activated new settings")
	}
	status = 401
	_, err = ListProviderChatModels(context.Background(), "deepseek", "preview-fixture")
	if err == nil || !strings.Contains(err.Error(), "DeepSeek") || strings.Contains(err.Error(), "preview-fixture") {
		t.Fatal("provider error leaked body or lacks provider label")
	}
}

func TestDirectDeepSeekPreservesNativeToolReasoningWithinRun(t *testing.T) {
	type query struct {
		Query string `json:"query"`
	}
	echo, err := utils.InferTool("search_fixture", "Find a fixture fact", func(_ context.Context, in query) (map[string]string, error) {
		return map[string]string{"answer": "Retur 7 hari"}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		var body struct {
			Messages []struct {
				Role, Content, ReasoningContent string
				Raw                             json.RawMessage
			} `json:"messages"`
			Thinking  map[string]string `json:"thinking"`
			MaxTokens int               `json:"max_tokens"`
		}
		var raw map[string]any
		payload, _ := io.ReadAll(r.Body)
		if json.Unmarshal(payload, &body) != nil || json.Unmarshal(payload, &raw) != nil {
			t.Error("bad request JSON")
			w.WriteHeader(400)
			return
		}
		if r.Header.Get("Authorization") != "Bearer fixture-key" || body.Thinking["type"] != "enabled" || body.MaxTokens < 8192 {
			t.Error("thinking config/credential not forwarded")
		}
		w.Header().Set("Content-Type", "application/json")
		if requests == 1 {
			for _, m := range body.Messages {
				if m.Role == "assistant" {
					t.Error("old history fabricated assistant reasoning")
				}
			}
			_, _ = io.WriteString(w, `{"id":"fixture-1","choices":[{"index":0,"finish_reason":"tool_calls","message":{"role":"assistant","reasoning_content":"private-fixture-reasoning","content":"","tool_calls":[{"id":"call-1","type":"function","function":{"name":"search_fixture","arguments":"{\"query\":\"retur\"}"}}]}}]}`)
		} else {
			preserved := false
			for _, item := range raw["messages"].([]any) {
				m := item.(map[string]any)
				if m["role"] == "assistant" && m["reasoning_content"] == "private-fixture-reasoning" {
					preserved = true
				}
			}
			if !preserved {
				t.Error("reasoning_content dropped between tool rounds")
			}
			_, _ = io.WriteString(w, `{"id":"fixture-2","choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","reasoning_content":"private-final-reasoning","content":"Retur 7 hari."}}]}`)
		}
	}))
	defer server.Close()
	p := aiPreset{Key: "deepseek", BaseURL: deepSeekBase, APIKey: "fixture-key", Model: "deepseek-flash"}
	cfg := nativeAIModelConfig(p, 2200, .2)
	cfg.BaseURL = server.URL
	cm, err := einomodel.NewChatModel(context.Background(), cfg)
	if err != nil {
		t.Fatal(err)
	}
	messages := agentHistoryMessages(p, []models.ChatHistory{{Message: "Halo", Reply: "Halo kak"}})
	messages = append(messages, schema.UserMessage("Apa syarat retur?"))
	out, err := executeEino(context.Background(), cm, []tool.BaseTool{echo}, messages)
	if err != nil || requests != 2 || out.Content != "Retur 7 hari." {
		t.Fatalf("native tool loop failed: calls=%d err=%v", requests, err)
	}
}

func TestDirectDeepSeekSimpleCompletionDisablesThinking(t *testing.T) {
	inner := providerRoundTrip(func(r *http.Request) (*http.Response, error) {
		var body map[string]any
		if json.NewDecoder(r.Body).Decode(&body) != nil || body["thinking"].(map[string]any)["type"] != "disabled" {
			t.Fatal("small completion can exhaust tokens on reasoning")
		}
		return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("{}"))}, nil
	})
	req, _ := http.NewRequest(http.MethodPost, deepSeekBase+"/chat/completions", strings.NewReader(`{"model":"deepseek-flash","max_tokens":100}`))
	if _, err := (deepSeekSimpleTransport{inner: inner}).RoundTrip(req); err != nil {
		t.Fatal(err)
	}
}
