package claim

// Faz 8: tipli kayıt ve alan çıkarma. İlke: model YALNIZCA mesajdaki ham ifadeyi yazar ("cuma"); tarihi
// hesaplamak, enum'u eşlemek ve zorunluluğu denetlemek KODUN işidir.

import (
	"context"
	"reflect"
	"regexp"
	"strings"
	"testing"
	"time"
)

// Salı. "cuma" → 2026-10-09; "bugün" → 2026-10-06.
var fieldsNow = time.Date(2026, 10, 6, 14, 30, 0, 0, time.UTC)

func TestCleanAtNormalizesFieldsInCode(t *testing.T) {
	ok, inc := CleanAt([]Candidate{{
		Text: "Ahmet cuma gününe kadar B-12 programını hazırlayacak.", Kind: "gorev",
		Fields: FieldMap{"Son Tarih": "cuma", "ATANAN": " Ahmet ", "proje": "B-12", "durum": "Devam", "uydurma_alan": "x"},
	}}, fieldsNow)
	if len(ok) != 1 || len(inc) != 0 {
		t.Fatalf("görev yazılabilir olmalı: ok=%v inc=%v", ok, inc)
	}
	want := FieldMap{"son_tarih": "2026-10-09", "atanan": "Ahmet", "proje": "B-12", "durum": "devam"}
	if !reflect.DeepEqual(ok[0].Fields, want) {
		t.Errorf("alanlar %v, beklenen %v (bilinmeyen alan atılmalı, tarih kodda çözülmeli)", ok[0].Fields, want)
	}

	// Geçersiz enum / tarih yok sayılır, varsayılan uygulanır; kayıt yine yazılır (zorunlu değil).
	ok, _ = CleanAt([]Candidate{{
		Text: "Rapor hazırla", Kind: "gorev", Fields: FieldMap{"durum": "çok acil", "son_tarih": "bilmem ne zaman"},
	}}, fieldsNow)
	if len(ok) != 1 || ok[0].Fields["durum"] != "acik" || ok[0].Fields["son_tarih"] != "" {
		t.Errorf("geçersiz değer atılıp varsayılan verilmeliydi: %+v", ok)
	}
	// Hiç alan yok: görev yine yazılır (alanlar isteğe bağlı), alansız kayıtlar geçerlidir.
	ok, _ = CleanAt([]Candidate{{Text: "Yarın toplantı var", Kind: "genel"}}, fieldsNow)
	if len(ok) != 1 || len(ok[0].Fields) != 0 {
		t.Errorf("şemasız tür alansız yazılmalı: %+v", ok)
	}
}

func TestCleanAtMissingRequiredFieldsMeanIncomplete(t *testing.T) {
	cases := []struct {
		name   string
		cand   Candidate
		wantOK bool
	}{
		{"pdks tarihsiz", Candidate{Text: "Ali geldi", Kind: "pdks", Fields: FieldMap{"personel": "Ali"}}, false},
		{"pdks personelsiz", Candidate{Text: "08:10 geldi", Kind: "pdks", Fields: FieldMap{"tarih": "bugün"}}, false},
		{"pdks tarihi anlaşılmaz", Candidate{Text: "Ali geldi", Kind: "pdks", Fields: FieldMap{"personel": "Ali", "tarih": "geçen sene"}}, false},
		{"pdks tam", Candidate{Text: "Ali bugün 8:10'da geldi", Kind: "pdks",
			Fields: FieldMap{"personel": "Ali", "tarih": "bugün", "giris": "8:10"}}, true},
		{"proje adsız", Candidate{Text: "Yeni bir proje var", Kind: "proje"}, false},
		{"proje ad key'den", Candidate{Text: "B-12 projesi başladı", Kind: "proje", Key: "B-12"}, true},
		{"personel adsız", Candidate{Text: "Yeni işe girdi", Kind: "personel", Fields: FieldMap{"bolum": "Kalıp"}}, false},
	}
	for _, c := range cases {
		ok, inc := CleanAt([]Candidate{c.cand}, fieldsNow)
		if (len(ok) == 1) != c.wantOK || (len(inc) == 1) == c.wantOK {
			t.Errorf("%s: ok=%d eksik=%d, yazılabilir=%v bekleniyordu", c.name, len(ok), len(inc), c.wantOK)
		}
	}

	ok, _ := CleanAt([]Candidate{{Text: "Ali bugün 8:10'da geldi", Kind: "pdks",
		Fields: FieldMap{"personel": "Ali", "tarih": "bugün", "giris": "8:10", "cikis": "17.45"}}}, fieldsNow)
	want := FieldMap{"personel": "Ali", "tarih": "2026-10-06", "giris": "08:10", "cikis": "17:45", "kaynak": "elle"}
	if len(ok) != 1 || !reflect.DeepEqual(ok[0].Fields, want) {
		t.Errorf("pdks alanları %+v, beklenen %v", ok, want)
	}
}

func TestCleanAtDerivesKeyFromNameField(t *testing.T) {
	ok, _ := CleanAt([]Candidate{{Text: "ACME Kalıp müşteri firmadır.", Kind: "firma",
		Fields: FieldMap{"ad": "ACME Kalıp A.Ş.", "tur": "müşteriler"}}}, fieldsNow)
	if len(ok) != 1 || ok[0].Key != "acme kalip a s" || ok[0].Fields["tur"] != "musteri" || ok[0].Fields["ad"] != "ACME Kalıp A.Ş." {
		t.Errorf("firma anahtarı ad alanından türetilmeli, enum eşlenmeli: %+v", ok)
	}
}

func TestParseCandidatesToleratesOddFieldValues(t *testing.T) {
	cs := ParseCandidates(`{"remember":[
		{"text":"B-12 görevi","kind":"gorev","fields":{"proje":12,"atanan":true,"son_tarih":"cuma","ic_ice":{"a":1}}},
		{"text":"Bozuk alan","kind":"gorev","fields":"metin"},
		{"text":"Alansız","kind":"genel"}]}`)
	if len(cs) != 3 {
		t.Fatalf("3 aday bekleniyordu: %+v", cs)
	}
	if got := cs[0].Fields; got["proje"] != "12" || got["atanan"] != "true" || got["son_tarih"] != "cuma" || len(got) != 3 {
		t.Errorf("sayı/bool metne çevrilmeli, iç içe nesne atılmalı: %v", got)
	}
	if len(cs[1].Fields) != 0 || cs[1].Text != "Bozuk alan" {
		t.Errorf("bozuk fields adayı bozmamalı: %+v", cs[1])
	}
}

var isoDate = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)

// writeScript üç adaylık çıkarım betiğidir: tarihi "cuma" olan görev, tarihsiz (eksik) pdks ve tam pdks.
func writeScript() *fakeLLM {
	return scripted(
		Candidate{Text: "Ahmet cuma gününe kadar B-12 programını hazırlayacak.", Kind: "gorev",
			Fields: FieldMap{"proje": "B-12", "atanan": "Ahmet", "son_tarih": "cuma"}},
		Candidate{Text: "Ali geldi", Kind: "pdks", Fields: FieldMap{"personel": "Ali"}}, // tarih yok → eksik bilgi
		Candidate{Text: "Ali bugün 8:10'da geldi", Kind: "pdks", Fields: FieldMap{"personel": "Ali", "tarih": "bugün", "giris": "8:10"}},
	)
}

const writeMsg = "Ahmet cuma B-12... Ali geldi. Ali bugün 8:10'da geldi."

// Uçtan uca (patron): model "cuma" yazar, depoya gerçek tarih gider; eksik pdks hiç yazılmaz.
func TestWriteStoresCanonicalFieldsAndSkipsIncomplete(t *testing.T) {
	s, repo, _ := newSvc(t, writeScript(), &fakeEmb{}, nil)
	res := mustWrite(t, s, faz7Patron, writeMsg)
	if res.Added != 2 || res.Incomplete != 1 || res.Denied != 0 {
		t.Fatalf("2 kayıt + 1 eksik bilgi bekleniyordu: %+v", res)
	}
	if !strings.Contains(res.Summary(), "1 kayıtta eksik bilgi var") {
		t.Errorf("özet eksik bilgiyi söylemeli: %q", res.Summary())
	}
	all, _ := repo.List(context.Background(), Filter{}, 0, 0)
	if len(all) != 2 {
		t.Fatalf("depoda 2 kayıt olmalı: %d", len(all))
	}
	for _, c := range all {
		switch c.Kind {
		case "gorev":
			d := c.Fields["son_tarih"]
			tm, err := time.Parse("2006-01-02", d)
			if !isoDate.MatchString(d) || err != nil || tm.Weekday() != time.Friday {
				t.Errorf("son_tarih gerçek bir Cuma tarihi olmalı, geldi %q", d)
			}
			if c.Fields["durum"] != "acik" || c.Fields["atanan"] != "Ahmet" || c.SubjectID != "" {
				t.Errorf("görev alanları yanlış: %+v", c)
			}
		case "pdks":
			if !isoDate.MatchString(c.Fields["tarih"]) || c.Fields["giris"] != "08:10" || c.Fields["kaynak"] != "elle" {
				t.Errorf("pdks alanları kanonik olmalı: %v", c.Fields)
			}
			// "Ali" adı yazma sırasında bir hesaba BAĞLANMAZ (uydurma eşleme yok); ilgili kişiyi patron atar.
			// O zamana kadar kaydı yalnızca patron görür (güvenli varsayılan).
			if c.SubjectID != "" || ali.CanSee(&c) || veli.CanSee(&c) || !faz7Patron.CanSee(&c) {
				t.Errorf("yeni pdks kaydı ilgili kişi atanana kadar yalnızca patrona görünmeli: %+v", c)
			}
		}
		if err := c.Validate(); err != nil {
			t.Errorf("%s kaydı Validate'ten geçmeli: %v", c.ID, err)
		}
	}
}

// Üye kişisel veri türünü YAZAMAZ; ekip türü (görev) yazılmaya devam eder ve özet bunu söyler.
func TestWriteMemberCannotCreatePrivateKinds(t *testing.T) {
	s, repo, _ := newSvc(t, writeScript(), &fakeEmb{}, nil)
	res := mustWrite(t, s, veli, writeMsg)
	if res.Added != 1 || res.Denied != 1 {
		t.Fatalf("1 görev yazılmalı, 1 pdks reddedilmeli: %+v", res)
	}
	if !strings.Contains(res.Summary(), "yalnızca patron girebilir") {
		t.Errorf("özet reddi söylemeli: %q", res.Summary())
	}
	all, _ := repo.List(context.Background(), Filter{}, 0, 0)
	if len(all) != 1 || all[0].Kind != "gorev" {
		t.Errorf("depoda yalnızca görev olmalı: %+v", all)
	}
}

func TestValidateRejectsBadFieldsAndSubject(t *testing.T) {
	base := func() Claim {
		return Claim{ID: "c1", Text: "x", Kind: "gorev", AuthorID: "u1", Status: StatusActive}
	}
	bad := map[string]func(*Claim){
		"bilinmeyen alan":       func(c *Claim) { c.Fields = map[string]string{"yok_boyle": "x"} },
		"kanonik olmayan tarih": func(c *Claim) { c.Fields = map[string]string{"son_tarih": "cuma"} },
		"geçersiz enum":         func(c *Claim) { c.Fields = map[string]string{"durum": "belki"} },
		"şemasız türde alan":    func(c *Claim) { c.Kind = "serbest"; c.Fields = map[string]string{"a": "b"} },
		"ekip türünde subject":  func(c *Claim) { c.SubjectID = "u-ali" },
		"çok uzun alan":         func(c *Claim) { c.Fields = map[string]string{"atanan": strings.Repeat("a", 201)} },
	}
	for name, mut := range bad {
		c := base()
		mut(&c)
		if c.Validate() == nil {
			t.Errorf("%s: Validate hata vermeliydi", name)
		}
	}
	good := base()
	good.Fields = map[string]string{"son_tarih": "2026-10-09", "durum": "devam"}
	if err := good.Validate(); err != nil {
		t.Errorf("kanonik alanlar geçmeli: %v", err)
	}
	pd := Claim{ID: "p1", Text: "Ali geldi", Kind: "pdks", AuthorID: "u1", Status: StatusActive, SubjectID: "u-ali",
		Fields: map[string]string{"personel": "Ali", "tarih": "2026-10-06"}}
	if err := pd.Validate(); err != nil {
		t.Errorf("özel türde subjectId geçerli: %v", err)
	}
	// Şema öncesi (alansız) kayıtlar geçerli kalır.
	legacy := Claim{ID: "l1", Text: "eski kayıt", Kind: "gorev", AuthorID: "u1", Status: StatusActive}
	if err := legacy.Validate(); err != nil {
		t.Errorf("alansız eski kayıt geçerli kalmalı: %v", err)
	}
}
