package claim

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"
)

// Faz 6: patron rolü, çelişki netleştirme, `gecersiz` işaretleme. Ortak düzen manage_test.go'dadır.

var cfBase = time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)

// cfSeed çelişki testleri için doğrudan depoya kayıt koyar. with boş değilse ConflictsWith olur;
// n oluşturma sırasıdır (küme içi sıralama buna göre).
func (e *mgrEnv) cfSeed(t *testing.T, id, kind, text string, st Status, with string, n int, author Actor) {
	t.Helper()
	c := readClaim(id, kind, text, st, []float32{1, 0, 0})
	c.AuthorID, c.AuthorName = author.ID, author.Name
	c.ConflictsWith = with
	c.CreatedAt = cfBase.Add(time.Duration(n) * time.Minute)
	c.UpdatedAt = c.CreatedAt
	if err := e.repo.Create(context.Background(), &c); err != nil {
		t.Fatal(err)
	}
}

func (e *mgrEnv) statusOf(t *testing.T, id string) Status {
	t.Helper()
	c, err := e.repo.Get(context.Background(), id)
	if err != nil {
		t.Fatalf("Get %s: %v", id, err)
	}
	return c.Status
}

func (e *mgrEnv) wantStatus(t *testing.T, want map[string]Status) {
	t.Helper()
	for id, st := range want {
		if got := e.statusOf(t, id); got != st {
			t.Errorf("%s durumu = %s, beklenen %s", id, got, st)
		}
	}
}

func groupIDs(g ConflictGroup) []string {
	ids := make([]string, 0, len(g.Claims))
	for _, c := range g.Claims {
		ids = append(ids, c.ID)
	}
	return ids
}

func sameIDs(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

func findGroup(gs []ConflictGroup, ids ...string) *ConflictGroup {
	for i := range gs {
		if sameIDs(groupIDs(gs[i]), ids) {
			return &gs[i]
		}
	}
	return nil
}

// cluster3 a ← b, a ← c: üç kayıtlık çelişki kümesi (b ve c, a ile çelişti).
func cluster3(t *testing.T, e *mgrEnv) {
	t.Helper()
	e.cfSeed(t, "a", KindGeneral, "Vardiya 08:00", StatusConflict, "", 1, mgrOwner)
	e.cfSeed(t, "b", KindGeneral, "Vardiya 09:00", StatusConflict, "a", 2, mgrOther)
	e.cfSeed(t, "c", KindGeneral, "Vardiya 10:00", StatusConflict, "a", 3, mgrOther)
}

func TestConflictsGroupsByLinkAndLegacyStaysAlone(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "a", KindGeneral, "Vardiya 08:00", StatusConflict, "", 1, mgrOwner)
	e.cfSeed(t, "b", KindGeneral, "Vardiya 09:00", StatusConflict, "a", 2, mgrOther)
	e.cfSeed(t, "x", KindGeneral, "Kapı kodu 1234", StatusConflict, "", 3, mgrOwner) // Faz 6 öncesi: bağı yok
	e.cfSeed(t, "ok", KindGeneral, "Normal bilgi", StatusActive, "", 4, mgrOwner)

	gs, err := e.svc.Conflicts(context.Background(), mgrOther) // üye de görebilir
	if err != nil {
		t.Fatal(err)
	}
	if len(gs) != 2 {
		t.Fatalf("2 küme beklenirdi, %d geldi", len(gs))
	}
	if g := findGroup(gs, "a", "b"); g == nil || g.Kind != KindGeneral || g.Key != "" {
		t.Fatalf("a,b kümesi (en eskiden yeniye, ortak anahtar yok) bulunamadı: %+v", gs)
	}
	if findGroup(gs, "x") == nil {
		t.Fatalf("bağı olmayan eski çelişki tek başına küme olmalı: %+v", gs)
	}
	for _, g := range gs {
		for _, c := range g.Claims {
			if len(c.Embedding) != 0 {
				t.Fatal("çelişki listesi vektör taşımamalı")
			}
		}
	}
}

func TestConflictsListKindSameKeyIsOneGroupWithKey(t *testing.T) {
	e := newMgr(t)
	// Bağsız eski kayıtlar: listelik türde aynı anahtar kümeyi birleştirir.
	for i, txt := range []string{"ACME telefon 111", "ACME telefon 222"} {
		id := []string{"f1", "f2"}[i]
		e.cfSeed(t, id, KindFirma, txt, StatusConflict, "", i+1, mgrOwner)
		c, _ := e.repo.Get(context.Background(), id)
		c.Key = "acme"
		if err := e.repo.Update(context.Background(), c); err != nil {
			t.Fatal(err)
		}
	}
	gs, err := e.svc.Conflicts(context.Background(), mgrOwner)
	if err != nil {
		t.Fatal(err)
	}
	g := findGroup(gs, "f1", "f2")
	if len(gs) != 1 || g == nil || g.Key != "acme" {
		t.Fatalf("aynı anahtarlı iki firma tek kümede ve ortak anahtarla gelmeli: %+v", gs)
	}
}

func TestConflictsRequiresActor(t *testing.T) {
	e := newMgr(t)
	if _, err := e.svc.Conflicts(context.Background(), Actor{}); !errors.Is(err, ErrNoActor) {
		t.Fatalf("kimliksiz: %v", err)
	}
}

func TestResolveKeepMakesChosenActiveAndOthersInvalid(t *testing.T) {
	e := newMgr(t)
	cluster3(t, e)
	res, err := e.svc.Resolve(context.Background(), mgrPatron, "b", DecisionKeep)
	if err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusInvalid, "b": StatusActive, "c": StatusInvalid})
	if len(res.Changes) != 3 {
		t.Fatalf("3 değişiklik beklenirdi: %+v", res.Changes)
	}
	// Denetim: kim, önce/sonra.
	es, _ := e.audit.ListByClaim(context.Background(), "a", 0, 0)
	if len(es) != 1 || es[0].Action != ActionStatus || es[0].By != mgrPatron.ID ||
		es[0].Before == nil || es[0].Before.Status != StatusConflict ||
		es[0].After == nil || es[0].After.Status != StatusInvalid {
		t.Fatalf("a için denetim yanlış: %+v", es)
	}
	// Karar sonrası çelişki listesi boş: kayıtlar silinmedi, yalnızca durumları değişti.
	if gs, _ := e.svc.Conflicts(context.Background(), mgrPatron); len(gs) != 0 {
		t.Fatalf("karar bekleyen küme kalmamalı: %+v", gs)
	}
}

func TestResolveKeepAllMakesEveryoneActive(t *testing.T) {
	e := newMgr(t)
	cluster3(t, e)
	if _, err := e.svc.Resolve(context.Background(), mgrPatron, "a", DecisionKeepAll); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusActive, "b": StatusActive, "c": StatusActive})
}

func TestResolveOnlyPatronAndRejectsBadInput(t *testing.T) {
	e := newMgr(t)
	cluster3(t, e)
	ctx := context.Background()
	if _, err := e.svc.Resolve(ctx, mgrOwner, "a", DecisionKeep); !errors.Is(err, ErrPatronOnly) {
		t.Fatalf("üye karar veremez (kaydın yazarı bile): %v", err)
	}
	if _, err := e.svc.Resolve(ctx, Actor{}, "a", DecisionKeep); !errors.Is(err, ErrNoActor) {
		t.Fatalf("kimliksiz: %v", err)
	}
	var ve *ValidationError
	if _, err := e.svc.Resolve(ctx, mgrPatron, "a", Decision("belki")); !errors.As(err, &ve) {
		t.Fatalf("geçersiz karar: %v", err)
	}
	if _, err := e.svc.Resolve(ctx, mgrPatron, "yok", DecisionKeep); !errors.Is(err, ErrNotFound) {
		t.Fatalf("olmayan kayıt: %v", err)
	}
	e.cfSeed(t, "n", KindGeneral, "Düz bilgi", StatusActive, "", 9, mgrOwner)
	if _, err := e.svc.Resolve(ctx, mgrPatron, "n", DecisionKeep); !errors.Is(err, ErrNotInConflict) {
		t.Fatalf("çelişkili olmayan: %v", err)
	}
	// Reddedilen hiçbir işlem durum değiştirmemeli.
	e.wantStatus(t, map[string]Status{"a": StatusConflict, "b": StatusConflict, "c": StatusConflict, "n": StatusActive})
}

func TestResolveLegacyConflictWithoutLinkDecidesOnlyItself(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "x", KindGeneral, "Kapı kodu 1234", StatusConflict, "", 1, mgrOwner)
	e.cfSeed(t, "y", KindGeneral, "Kapı kodu 9999", StatusConflict, "", 2, mgrOwner)
	if _, err := e.svc.Resolve(context.Background(), mgrPatron, "x", DecisionKeep); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"x": StatusActive, "y": StatusConflict})
}

func TestInvalidateOnlyPatronIdempotentAndAudited(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "n", KindGeneral, "Eski stok bilgisi", StatusActive, "", 1, mgrOwner)
	ctx := context.Background()

	if _, err := e.svc.Invalidate(ctx, mgrOwner, "n"); !errors.Is(err, ErrPatronOnly) {
		t.Fatalf("yazarı bile olsa üye geçersiz işaretleyemez: %v", err)
	}
	if _, err := e.svc.Invalidate(ctx, Actor{}, "n"); !errors.Is(err, ErrNoActor) {
		t.Fatalf("kimliksiz: %v", err)
	}
	if _, err := e.svc.Invalidate(ctx, mgrPatron, "yok"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("olmayan kayıt: %v", err)
	}
	e.wantStatus(t, map[string]Status{"n": StatusActive})

	got, err := e.svc.Invalidate(ctx, mgrPatron, "n")
	if err != nil || got.Status != StatusInvalid {
		t.Fatalf("Invalidate: %+v %v", got, err)
	}
	if _, err := e.svc.Invalidate(ctx, mgrPatron, "n"); err != nil { // ikinci kez: sessizce aynı
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"n": StatusInvalid})
	es, _ := e.audit.ListByClaim(ctx, "n", 0, 0)
	if len(es) != 1 || es[0].Action != ActionStatus || es[0].By != mgrPatron.ID ||
		es[0].Before.Status != StatusActive || es[0].After.Status != StatusInvalid {
		t.Fatalf("tek denetim girdisi beklenirdi (ikinci çağrı yazmamalı): %+v", es)
	}
}

func TestInvalidateOneOfTwoReleasesTheOther(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "a", KindGeneral, "Vardiya 08:00", StatusConflict, "", 1, mgrOwner)
	e.cfSeed(t, "b", KindGeneral, "Vardiya 09:00", StatusConflict, "a", 2, mgrOther)
	if _, err := e.svc.Invalidate(context.Background(), mgrPatron, "a"); err != nil {
		t.Fatal(err)
	}
	// b artık kimseyle çelişmiyor: "çelişkili" kalmamalı.
	e.wantStatus(t, map[string]Status{"a": StatusInvalid, "b": StatusActive})
	es, _ := e.audit.ListByClaim(context.Background(), "b", 0, 0)
	if len(es) != 1 || es[0].By != mgrPatron.ID || es[0].After.Status != StatusActive {
		t.Fatalf("b'nin aktif olması denetlenmeli: %+v", es)
	}
}

func TestInvalidateInThreeWayCluster(t *testing.T) {
	ctx := context.Background()
	// Çevre kayıt çıkınca kalanlar hâlâ bağlı: ikisi de çelişkili kalır.
	e := newMgr(t)
	cluster3(t, e)
	if _, err := e.svc.Invalidate(ctx, mgrPatron, "b"); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusConflict, "b": StatusInvalid, "c": StatusConflict})

	// Merkez kayıt çıkınca kalanlar birbirine bağlı değil: ikisi de aktif olur.
	e = newMgr(t)
	cluster3(t, e)
	if _, err := e.svc.Invalidate(ctx, mgrPatron, "a"); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusInvalid, "b": StatusActive, "c": StatusActive})
}

func TestRestoreBringsBackInvalidOnly(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "n", KindGeneral, "Bilgi", StatusActive, "", 1, mgrOwner)
	ctx := context.Background()
	if _, err := e.svc.Restore(ctx, mgrPatron, "n"); !errors.Is(err, ErrNotInvalid) {
		t.Fatalf("geçersiz olmayanı geri almak: %v", err)
	}
	if _, err := e.svc.Invalidate(ctx, mgrPatron, "n"); err != nil {
		t.Fatal(err)
	}
	if _, err := e.svc.Restore(ctx, mgrOwner, "n"); !errors.Is(err, ErrPatronOnly) {
		t.Fatalf("üye geri alamaz: %v", err)
	}
	got, err := e.svc.Restore(ctx, mgrPatron, "n")
	if err != nil || got.Status != StatusActive {
		t.Fatalf("Restore: %+v %v", got, err)
	}
	e.wantStatus(t, map[string]Status{"n": StatusActive})
	if acts := e.auditActions(t, "n"); len(acts) != 2 {
		t.Fatalf("iki durum değişikliği denetlenmeli: %v", acts)
	}
}

func TestDeleteOfConflictingClaimReleasesTheOther(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "a", KindGeneral, "Vardiya 08:00", StatusConflict, "", 1, mgrOwner)
	e.cfSeed(t, "b", KindGeneral, "Vardiya 09:00", StatusConflict, "a", 2, mgrOther)
	// b'nin yazarı kendi kaydını siler: karşısındaki a tek başına kalır → aktif.
	if err := e.svc.Delete(context.Background(), mgrOther, "b"); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusActive})
	es, _ := e.audit.ListByClaim(context.Background(), "a", 0, 0)
	if len(es) != 1 || es[0].By != mgrOther.ID || es[0].After.Status != StatusActive {
		t.Fatalf("a'nın aktif olması silen kişi adına denetlenmeli: %+v", es)
	}
}

func TestFullFlowWriteConflictThenPatronDecides(t *testing.T) {
	e := newMgr(t)
	e.cfSeed(t, "a", KindGeneral, "Kalıp B-12 teslimi cuma", StatusActive, "", 1, mgrOwner)
	// İkinci kullanıcı çelişkili bilgi girdi (Service.apply'ın yazdığı şekil).
	e.cfSeed(t, "b", KindGeneral, "Kalıp B-12 teslimi pazartesi", StatusConflict, "a", 2, mgrOther)
	if err := e.repo.SetStatus(context.Background(), "a", StatusConflict, cfBase.Add(2*time.Minute)); err != nil {
		t.Fatal(err)
	}
	gs, err := e.svc.Conflicts(context.Background(), mgrOwner)
	if err != nil || len(gs) != 1 || len(gs[0].Claims) != 2 {
		t.Fatalf("karar bekleyen tek küme: %+v %v", gs, err)
	}
	if _, err := e.svc.Resolve(context.Background(), mgrPatron, "a", DecisionKeep); err != nil {
		t.Fatal(err)
	}
	e.wantStatus(t, map[string]Status{"a": StatusActive, "b": StatusInvalid})
}

func TestWriteResultMessageAddsNeutralConflictNote(t *testing.T) {
	none := &WriteResult{Added: 3}
	if none.ConflictNote() != "" || none.Message() != none.Summary() {
		t.Fatalf("çelişki yokken özet değişmemeli: %q", none.Message())
	}
	one := &WriteResult{Added: 3, Incomplete: 2, Conflicts: []Conflict{{NewID: "n", ExistingID: "e"}}}
	// Spec biçimi aynen korunur; not ayrı cümle olarak eklenir.
	if !strings.HasPrefix(one.Message(), "3 kayıt eklendi, 1 çelişki var, 2 kayıtta eksik bilgi var. ") {
		t.Fatalf("özet biçimi bozulmamalı: %q", one.Message())
	}
	many := &WriteResult{Added: 5, Conflicts: make([]Conflict, 3)}
	for _, msg := range []string{one.Message(), many.Message()} {
		if !strings.Contains(msg, "iki farklı bilgi var") || !strings.Contains(msg, "patron karar verecek") {
			t.Errorf("not nötr olmalı ve kararı patrona bırakmalı: %q", msg)
		}
		for _, bad := range []string{"yanlış", "hata yap", "suç", "güvenilir"} {
			if strings.Contains(msg, bad) {
				t.Errorf("suçlayıcı/puanlayıcı dil yok: %q içinde %q", msg, bad)
			}
		}
	}
}
