package tool

import (
	"slices"
	"testing"
)

// MemoryRepo sözleşmeyi sağlamalıdır (mongostore.ToolRepo ile aynı testten geçer).
func TestMemoryRepoContract(t *testing.T) {
	RepositoryContract(t, func() Repository { return NewMemoryRepo() })
}

func fields(errs []FieldError) []string {
	out := make([]string, 0, len(errs))
	for _, e := range errs {
		out = append(out, e.Field)
	}
	return out
}

func TestNormalize(t *testing.T) {
	in := Tool{
		Name:        "  CATIA.Create_Plane  ",
		Description: "  açıklama  ",
		Permissions: []string{"WRITE", "read", "write", " "},
		Tags:        []string{"CATIA", "geometry"},
	}
	Normalize(&in)
	if in.Name != "CATIA.Create_Plane" {
		t.Errorf("ad kırpılmadı (geçersizliğe Normalize dokunmaz): %q", in.Name)
	}
	if in.Version != DefaultVersion || in.Status != StatusEnabled || in.Kind != KindCustom || in.Risk != RiskLow {
		t.Errorf("varsayılanlar uygulanmadı: %+v", in)
	}
	if in.TimeoutMs != DefaultTimeoutMs {
		t.Errorf("zaman aşımı varsayılanı uygulanmadı: %d", in.TimeoutMs)
	}
	// İzinler kanonik sırada ve tekilleştirilmiş olmalı.
	if !slices.Equal(in.Permissions, []string{"read", "write"}) {
		t.Errorf("izinler: %v", in.Permissions)
	}
	if !slices.Equal(in.Tags, []string{"catia", "geometry"}) {
		t.Errorf("etiketler: %v", in.Tags)
	}
	// Boş listeler nil değil [] olmalı (API'de null görünmemeli).
	fresh := Tool{Name: "x.y"}
	Normalize(&fresh)
	if fresh.Permissions == nil || fresh.Tags == nil {
		t.Errorf("listeler boş yazılmalı: %+v", fresh)
	}
}

func TestValidate(t *testing.T) {
	valid := func() Tool {
		return Tool{
			Name:        "catia.create_plane",
			Description: "Seçilen eksen üzerinden plane oluşturur.",
			Permissions: []string{"execute", "write"},
			Risk:        RiskMedium,
			TimeoutMs:   10_000,
			InputSchema: map[string]any{"type": "object"},
		}
	}
	t.Run("geçerli", func(t *testing.T) {
		x := valid()
		Normalize(&x)
		if errs := Validate(&x); len(errs) != 0 {
			t.Errorf("geçerli sayılmalıydı: %+v", errs)
		}
	})

	cases := []struct {
		name      string
		mutate    func(*Tool)
		wantField string
	}{
		{"ad biçimi", func(x *Tool) { x.Name = "Catia Plane" }, "name"},
		{"ad çok bölüm", func(x *Tool) { x.Name = "a.b.c.d.e" }, "name"},
		{"açıklama boş", func(x *Tool) { x.Description = "" }, "description"},
		{"sürüm", func(x *Tool) { x.Version = "1.0" }, "version"},
		{"durum", func(x *Tool) { x.Status = "acik" }, "status"},
		{"tür", func(x *Tool) { x.Kind = "harici" }, "kind"},
		{"risk", func(x *Tool) { x.Risk = "orta" }, "risk"},
		{"şema tipi", func(x *Tool) { x.InputSchema = map[string]any{"type": "array"} }, "inputSchema"},
		{"bilinmeyen izin", func(x *Tool) { x.Permissions = []string{"sudo"} }, "permissions[0]"},
		{"zaman aşımı sınırı", func(x *Tool) { x.TimeoutMs = MaxTimeoutMs + 1 }, "timeoutMs"},
		{"negatif zaman aşımı", func(x *Tool) { x.TimeoutMs = -1 }, "timeoutMs"},
		{"tekrar sınırı", func(x *Tool) { x.Retry = Retry{MaxAttempts: MaxRetryAttempts + 1} }, "retry.maxAttempts"},
		{"negatif bekleme", func(x *Tool) { x.Retry = Retry{BackoffMs: -5} }, "retry.backoffMs"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			x := valid()
			Normalize(&x)
			tc.mutate(&x)
			errs := Validate(&x)
			if got := fields(errs); !slices.Contains(got, tc.wantField) {
				t.Errorf("alan %q hatası bekleniyordu, gelen: %v", tc.wantField, got)
			}
		})
	}

	t.Run("tüm hatalar birlikte döner", func(t *testing.T) {
		x := Tool{Name: "X", Description: "", Risk: "yok", Permissions: []string{"sudo"}}
		Normalize(&x)
		got := fields(Validate(&x))
		for _, want := range []string{"name", "description", "risk", "permissions[0]"} {
			if !slices.Contains(got, want) {
				t.Errorf("%q hatası eksik: %v", want, got)
			}
		}
	})
}

// ---- UI (arayüz görünümü) sözleşmesi ----

// ResolveUI, tanımdaki açık görünümü korur; auto/boş durumda ön ekten türetir.
// Arayüz "auto"yu asla görmemelidir; bu yüzden türetme sunucunun işidir.
func TestResolveUI(t *testing.T) {
	cases := []struct {
		label string
		tool  Tool
		want  UI
	}{
		{"açık görünüm korunur", Tool{Name: "biz.find", UI: UIMetrics}, UIMetrics},
		{"auto → biz tablo", Tool{Name: "biz.find", UI: UIAuto}, UITable},
		{"auto → fs dosya", Tool{Name: "fs.read_text", UI: UIAuto}, UIFile},
		{"auto → web liste", Tool{Name: "web.search", UI: UIAuto}, UIList},
		{"auto → sys kod", Tool{Name: "sys.exec", UI: UIAuto}, UICode},
		{"auto → bilinmeyen ön ek json", Tool{Name: "catia.plane", UI: UIAuto}, UIJson},
		{"boş UI de auto sayılır", Tool{Name: "memory.recall"}, UITable},
		{"noktasız ad json", Tool{Name: "echo"}, UIJson},
	}
	for _, c := range cases {
		t.Run(c.label, func(t *testing.T) {
			if got := ResolveUI(&c.tool); got != c.want {
				t.Errorf("ResolveUI = %q, beklenen %q", got, c.want)
			}
		})
	}
	// nil tool çökmeye yol açmamalı (türetme opsiyonel bir kolaylık).
	if got := ResolveUI(nil); got != UIJson {
		t.Errorf("ResolveUI(nil) = %q, beklenen json", got)
	}
}

// UI doğrulaması: geçersiz değer reddedilir, boş değer auto'ya düşer.
func TestValidateUIField(t *testing.T) {
	base := func() Tool {
		return Tool{Name: "x.y", Title: "T", Description: "d", Version: "1.0.0",
			Status: StatusEnabled, Kind: KindCustom, Risk: RiskLow, Permissions: []string{"execute"}}
	}
	t.Run("geçersiz ui reddedilir", func(t *testing.T) {
		x := base()
		x.UI = UI("olmayan")
		if got := fields(Validate(&x)); !slices.Contains(got, "ui") {
			t.Errorf("geçersiz ui reddedilmedi, alanlar: %v", got)
		}
	})
	t.Run("boş ui auto olur", func(t *testing.T) {
		x := base()
		Normalize(&x)
		if x.UI != UIAuto {
			t.Errorf("Normalize sonrası ui = %q, beklenen auto", x.UI)
		}
	})
	t.Run("tüm geçerli ui kabul edilir", func(t *testing.T) {
		for _, u := range AllUI {
			x := base()
			x.UI = u
			if got := fields(Validate(&x)); slices.Contains(got, "ui") {
				t.Errorf("geçerli ui %q reddedildi: %v", u, got)
			}
		}
	})
}
