package api

import (
	"context"
	"io"
	"log/slog"
	"net/http"
	"testing"
	"time"

	"fiko-ai/internal/agent"
	"fiko-ai/internal/api/ws"
	"fiko-ai/internal/conversation"
	"fiko-ai/internal/event"
	"fiko-ai/internal/prompt"
	"fiko-ai/internal/settings"
)

type settingsBody struct {
	Model       string  `json:"model"`
	Temperature float64 `json:"temperature"`
	TopP        float64 `json:"topP"`
	MaxTokens   int     `json:"maxTokens"`
	ContextSize int     `json:"contextSize"`
}

func settingsServer(t *testing.T, initial settings.Values) *Server {
	t.Helper()
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	svc := conversation.NewService(conversation.NewMemoryRepo())
	bus := event.NewMemoryBus(nil, 16)
	p := &fakeProvider{}
	ag := agent.New(svc, p, prompt.Default(), bus, log, agent.Config{})
	hub := ws.NewHub(ag, log, ws.Options{PingInterval: time.Hour, PongWait: 2 * time.Hour, WriteWait: time.Second})
	set := settings.New(initial, log, bus)
	s := New(context.Background(), Config{
		Env: "test", Version: "test", AllowedOrigins: []string{"http://localhost:4200"},
		DefaultUserID: "local", DefaultPageSize: 2, MaxPageSize: 5,
	}, Deps{Conversations: svc, LLM: p, Hub: hub, Bus: bus, Settings: set}, log)
	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		_ = ag.Close(ctx)
		_ = hub.Close(ctx)
		bus.Close()
	})
	return s
}

func TestSettingsAPI_PutCanResetToZero(t *testing.T) {
	s := settingsServer(t, settings.Values{Model: "m", Temperature: 0.85, TopP: 0.9, MaxTokens: 512, ContextSize: 8192})

	resp, raw := do(t, s, http.MethodPut, "/api/settings",
		map[string]any{"temperature": 0, "topP": 0, "maxTokens": 0, "contextSize": 0}, nil)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status=%d gövde=%s", resp.StatusCode, raw)
	}
	got := decode[settingsBody](t, raw)
	if got.Temperature != 0 || got.TopP != 0 || got.MaxTokens != 0 || got.ContextSize != 0 || got.Model != "m" {
		t.Fatalf("0'a dönüş uygulanmadı: %+v", got)
	}

	// kalıcılık: yeni GET aynı değerleri görmeli
	_, raw = do(t, s, http.MethodGet, "/api/settings", nil, nil)
	if again := decode[settingsBody](t, raw); again != got {
		t.Fatalf("GET farklı: %+v != %+v", again, got)
	}
}

func TestSettingsAPI_PartialKeepsOmittedFields(t *testing.T) {
	s := settingsServer(t, settings.Values{Model: "m", Temperature: 0.5, ContextSize: 4096})
	resp, raw := do(t, s, http.MethodPut, "/api/settings", map[string]any{"maxTokens": 256}, nil)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status=%d %s", resp.StatusCode, raw)
	}
	got := decode[settingsBody](t, raw)
	if got.Temperature != 0.5 || got.ContextSize != 4096 || got.MaxTokens != 256 {
		t.Fatalf("verilmeyen alanlar korunmalı: %+v", got)
	}
}

func TestSettingsAPI_InvalidBodiesAreBadRequest(t *testing.T) {
	s := settingsServer(t, settings.Values{Model: "m", Temperature: 0.5})
	for name, body := range map[string]any{
		"temperature_ust":     map[string]any{"temperature": 3},
		"topP_negatif":        map[string]any{"topP": -1},
		"contextSize_negatif": map[string]any{"contextSize": -1},
		"model_bos":           map[string]any{"model": ""},
		"yanlis_tip":          map[string]any{"temperature": "sıcak"},
	} {
		t.Run(name, func(t *testing.T) {
			resp, raw := do(t, s, http.MethodPut, "/api/settings", body, nil)
			if resp.StatusCode != http.StatusBadRequest {
				t.Fatalf("400 beklenirdi: %d %s", resp.StatusCode, raw)
			}
		})
	}
	_, raw := do(t, s, http.MethodGet, "/api/settings", nil, nil)
	if got := decode[settingsBody](t, raw); got.Temperature != 0.5 || got.Model != "m" {
		t.Fatalf("hatalı istekler durumu değiştirmemeli: %+v", got)
	}
}
