package settings

import (
	"reflect"
	"strings"
	"testing"
)

func ptr[T any](v T) *T { return &v }

func TestUpdate_ExplicitZeroResetsToProviderDefault(t *testing.T) {
	s := New(Values{Model: "m", Temperature: 0.85, TopP: 0.9, MaxTokens: 512, ContextSize: 8192}, testLogger(), nil)

	got, err := s.Update(Patch{Temperature: ptr(0.0), TopP: ptr(0.0), MaxTokens: ptr(0), ContextSize: ptr(0)})
	if err != nil {
		t.Fatalf("0 değerleri geçerli olmalı: %v", err)
	}
	if got.Temperature != 0 || got.TopP != 0 || got.MaxTokens != 0 || got.ContextSize != 0 {
		t.Fatalf("sıfırlanmadı: %+v", got)
	}
	if got.Model != "m" {
		t.Fatalf("verilmeyen alan (model) korunmalı: %q", got.Model)
	}
	if opts := s.LLMOptions(); opts.Temperature != nil || opts.MaxTokens != 0 || opts.ContextSize != 0 {
		t.Fatalf("sağlayıcıya parametre gitmemeli: %+v", opts)
	}
}

func TestUpdate_NilFieldsAreUntouched(t *testing.T) {
	s := New(Values{Model: "m", Temperature: 0.5, ContextSize: 4096}, testLogger(), nil)
	got, err := s.Update(Patch{MaxTokens: ptr(256)})
	if err != nil {
		t.Fatal(err)
	}
	if got.Model != "m" || got.Temperature != 0.5 || got.ContextSize != 4096 || got.MaxTokens != 256 {
		t.Fatalf("beklenmeyen: %+v", got)
	}
}

func TestUpdate_RejectsInvalidAndChangesNothing(t *testing.T) {
	// ConfirmMode güvenli varsayılanla doldurulur: New() normalize eder, dolayısıyla
	// "hatalı güncelleme hiçbir şeyi değiştirmedi" karşılaştırması anlamlı kalsın.
	start := New(Values{Model: "m", Temperature: 0.5, TopP: 0.5, MaxTokens: 100, ContextSize: 2048},
		testLogger(), nil).Get()
	cases := []struct {
		name  string
		patch Patch
		want  string
	}{
		{"temperature_ust", Patch{Temperature: ptr(2.5)}, "temperature"},
		{"temperature_negatif", Patch{Temperature: ptr(-0.1)}, "temperature"},
		{"topP_ust", Patch{TopP: ptr(1.5)}, "topP"},
		{"topP_negatif", Patch{TopP: ptr(-0.2)}, "topP"},
		{"maxTokens_negatif", Patch{MaxTokens: ptr(-1)}, "maxTokens"},
		{"maxTokens_ust", Patch{MaxTokens: ptr(MaxModelTokens + 1)}, "maxTokens"},
		{"contextSize_negatif", Patch{ContextSize: ptr(-5)}, "contextSize"},
		{"contextSize_ust", Patch{ContextSize: ptr(MaxModelTokens + 1)}, "contextSize"},
		{"model_bos", Patch{Model: ptr("")}, "model"},
		{"model_bosluk", Patch{Model: ptr("   ")}, "model"},
		{"gecerli_ile_gecersiz_birlikte", Patch{Model: ptr("yeni"), Temperature: ptr(9.0)}, "temperature"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			s := New(start, testLogger(), nil)
			_, err := s.Update(tc.patch)
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("%q içeren hata beklenirdi: %v", tc.want, err)
			}
			if got := s.Get(); !sameSettings(got, start) {
				t.Fatalf("hatalı güncelleme durumu değiştirmemeli: %+v", got)
			}
		})
	}
}

// sameSettings, iki Values eşit mi karşılaştırır.
//
// Neden yardımcı fonksiyon? Values içinde HARİTA alanları var (promptOverrides,
// promptDisabled) ve Go haritaları == ile karşılaştırılamaz. Testler "hatalı
// güncelleme hiçbir şeyi değiştirmemeli" diye doğruladığı için alan alan eşitlik
// şart. reflect.DeepEqual harita sırasına BAKMAZ; map[string]string için doğrudur.
func sameSettings(a, b Values) bool {
	return reflect.DeepEqual(a, b)
}

func TestUpdate_TrimsModel(t *testing.T) {
	s := New(Values{Model: "a"}, testLogger(), nil)
	got, err := s.Update(Patch{Model: ptr("  qwen3:30b \n")})
	if err != nil {
		t.Fatal(err)
	}
	if got.Model != "qwen3:30b" {
		t.Fatalf("model kırpılmalı: %q", got.Model)
	}
}
