package claim

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

var (
	mgrOwner  = Actor{ID: "u-ali", Name: "Ali", Role: RoleUye}
	mgrOther  = Actor{ID: "u-veli", Name: "Veli", Role: RoleUye}
	mgrPatron = Actor{ID: "u-patron", Name: "Fikri", Role: RolePatron}
)

type mgrEnv struct {
	svc   *Service
	repo  *MemoryRepo
	audit *MemoryAuditRepo
	emb   *readEmb
	clock time.Time
}

func newMgr(t *testing.T) *mgrEnv {
	t.Helper()
	e := &mgrEnv{repo: NewMemoryRepo(), audit: NewMemoryAuditRepo(), emb: &readEmb{vecs: map[string][]float32{}},
		clock: time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC)}
	e.svc = NewService(e.repo, e.audit, nil, e.emb, Config{EmbedModel: readModel}, nil)
	e.svc.now = func() time.Time { e.clock = e.clock.Add(time.Minute); return e.clock }
	t.Cleanup(e.svc.Close)
	return e
}

// seed bir kaydı doğrudan depoya koyar (yazar owner).
func (e *mgrEnv) seed(t *testing.T, id, kind, text string, author Actor) {
	t.Helper()
	c := readClaim(id, kind, text, StatusActive, []float32{1, 0, 0})
	c.AuthorID, c.AuthorName = author.ID, author.Name
	if err := e.repo.Create(context.Background(), &c); err != nil {
		t.Fatal(err)
	}
}

func (e *mgrEnv) auditActions(t *testing.T, id string) []Action {
	t.Helper()
	es, err := e.audit.ListByClaim(context.Background(), id, 0, 0)
	if err != nil {
		t.Fatal(err)
	}
	out := make([]Action, 0, len(es))
	for _, x := range es {
		out = append(out, x.Action)
	}
	return out
}

// ---- Delete ----

func TestDeleteByAuthorKeepsValueInAudit(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME Kalıp A.Ş.", mgrOwner)

	if err := e.svc.Delete(context.Background(), mgrOwner, "c1"); err != nil {
		t.Fatal(err)
	}
	if _, err := e.repo.Get(context.Background(), "c1"); !errors.Is(err, ErrNotFound) {
		t.Fatalf("kayıt silinmeliydi: %v", err)
	}
	es, _ := e.audit.ListByClaim(context.Background(), "c1", 0, 0)
	if len(es) != 1 || es[0].Action != ActionDelete || es[0].By != "u-ali" || es[0].ByName != "Ali" {
		t.Fatalf("denetim yanlış: %+v", es)
	}
	if es[0].Before == nil || es[0].Before.Text != "ACME Kalıp A.Ş." || es[0].After != nil {
		t.Errorf("silinen değer denetimde yaşamalı: %+v", es[0])
	}
	if len(es[0].Before.Embedding) != 0 {
		t.Errorf("denetime vektör girmemeli")
	}
}

func TestDeleteByPatronAllowedOtherMemberForbidden(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME", mgrOwner)

	if err := e.svc.Delete(context.Background(), mgrOther, "c1"); !errors.Is(err, ErrForbidden) {
		t.Fatalf("başka üye silememeli: %v", err)
	}
	if _, err := e.repo.Get(context.Background(), "c1"); err != nil {
		t.Errorf("yetkisiz silme kaydı etkilememeli: %v", err)
	}
	if got := e.auditActions(t, "c1"); len(got) != 0 {
		t.Errorf("reddedilen işlem denetim yazmamalı: %v", got)
	}
	if err := e.svc.Delete(context.Background(), mgrPatron, "c1"); err != nil {
		t.Fatalf("patron silebilmeli: %v", err)
	}
	if es, _ := e.audit.ListByClaim(context.Background(), "c1", 0, 0); es[0].By != "u-patron" {
		t.Errorf("silen patron kaydedilmeli: %+v", es[0])
	}
}

func TestDeleteNotFoundAndNoActor(t *testing.T) {
	e := newMgr(t)
	if err := e.svc.Delete(context.Background(), mgrOwner, "yok"); !errors.Is(err, ErrNotFound) {
		t.Errorf("ErrNotFound bekleniyordu: %v", err)
	}
	if err := e.svc.Delete(context.Background(), Actor{}, "c1"); !errors.Is(err, ErrNoActor) {
		t.Errorf("ErrNoActor bekleniyordu: %v", err)
	}
	if err := e.svc.Delete(context.Background(), Actor{ID: "x", Role: RolePatron}, ""); err == nil {
		t.Errorf("boş kimlik hata vermeli")
	}
}

// ---- Edit ----

func TestEditByAuthorReembedsAndAudits(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME Kalıp A.Ş. tel 111", mgrOwner)
	e.emb.vecs["ACME Kalıp A.Ş. tel 222"] = []float32{0, 1, 0}

	got, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "  ACME Kalıp A.Ş.   tel 222 "})
	if err != nil {
		t.Fatal(err)
	}
	if got.Text != "ACME Kalıp A.Ş. tel 222" || len(got.Embedding) != 0 {
		t.Errorf("dönen kayıt yanlış (metin normalleşmeli, vektör taşınmamalı): %+v", got)
	}
	stored, _ := e.repo.Get(context.Background(), "c1")
	if stored.Text != "ACME Kalıp A.Ş. tel 222" || len(stored.Embedding) != 3 || stored.Embedding[1] != 1 || stored.EmbedModel != readModel {
		t.Errorf("metin ve YENİ vektör yazılmalı: %+v", stored)
	}
	if stored.AuthorID != "u-ali" || stored.Status != StatusActive || !stored.UpdatedAt.After(stored.CreatedAt) {
		t.Errorf("yazar/durum korunmalı, UpdatedAt ilerlemeli: %+v", stored)
	}
	es, _ := e.audit.ListByClaim(context.Background(), "c1", 0, 0)
	if len(es) != 1 || es[0].Action != ActionUpdate || es[0].Before.Text != "ACME Kalıp A.Ş. tel 111" || es[0].After.Text != "ACME Kalıp A.Ş. tel 222" {
		t.Errorf("önceki/sonraki değer denetimde olmalı: %+v", es)
	}
}

func TestEditByPatronKeepsOriginalAuthor(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindGeneral, "Vardiya 08:00", mgrOwner)
	if _, err := e.svc.Edit(context.Background(), mgrPatron, "c1", EditRequest{Text: "Vardiya 09:00"}); err != nil {
		t.Fatal(err)
	}
	stored, _ := e.repo.Get(context.Background(), "c1")
	if stored.AuthorID != "u-ali" {
		t.Errorf("yazar değişmemeli: %s", stored.AuthorID)
	}
	es, _ := e.audit.ListByClaim(context.Background(), "c1", 0, 0)
	if es[0].By != "u-patron" {
		t.Errorf("düzenleyen denetimde görünmeli: %+v", es[0])
	}
}

func TestEditForbiddenInvalidAndNoop(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME", mgrOwner)

	if _, err := e.svc.Edit(context.Background(), mgrOther, "c1", EditRequest{Text: "Hack"}); !errors.Is(err, ErrForbidden) {
		t.Errorf("başka üye düzenleyememeli: %v", err)
	}
	var ve *ValidationError
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "   "}); !errors.As(err, &ve) {
		t.Errorf("boş metin ValidationError olmalı: %v", err)
	}
	empty := ""
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "ACME", Key: &empty}); !errors.As(err, &ve) {
		t.Errorf("listelik türde boş anahtar ValidationError olmalı: %v", err)
	}
	// Değişiklik yok: denetim yazılmaz.
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: " ACME "}); err != nil {
		t.Errorf("no-op hata vermemeli: %v", err)
	}
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "yok", EditRequest{Text: "x"}); !errors.Is(err, ErrNotFound) {
		t.Errorf("ErrNotFound bekleniyordu: %v", err)
	}
	if got := e.auditActions(t, "c1"); len(got) != 0 {
		t.Errorf("reddedilen/no-op işlemler denetim yazmamalı: %v", got)
	}
}

func TestEditKeyCollisionAndRename(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME", mgrOwner)
	e.seed(t, "c2", KindFirma, "Barış Metal", mgrOwner)

	taken := "Barış Metal"
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "ACME", Key: &taken}); !errors.Is(err, ErrKeyTaken) {
		t.Fatalf("ErrKeyTaken bekleniyordu: %v", err)
	}
	// Başkasının geçersiz/eski kaydı anahtarı tutmaz.
	if err := e.repo.SetStatus(context.Background(), "c2", StatusInvalid, e.clock); err != nil {
		t.Fatal(err)
	}
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "Barış Metal Ltd.", Key: &taken}); err != nil {
		t.Fatalf("geçersiz kayıt anahtarı engellememeli: %v", err)
	}
	stored, _ := e.repo.Get(context.Background(), "c1")
	if stored.Key != "baris metal" || stored.Text != "Barış Metal Ltd." {
		t.Errorf("yeniden adlandırma yazılmadı: %+v", stored)
	}
}

func TestEditEmbedFailureDropsStaleVector(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindGeneral, "eski bilgi", mgrOwner)
	e.emb.err = errors.New("ollama kapalı")

	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "yeni bilgi"}); err != nil {
		t.Fatalf("embedding hatası düzenlemeyi düşürmemeli: %v", err)
	}
	stored, _ := e.repo.Get(context.Background(), "c1")
	if stored.Text != "yeni bilgi" || len(stored.Embedding) != 0 || stored.EmbedModel != "" {
		t.Errorf("eski metne ait vektör atılmalı: %+v", stored)
	}
}

func TestEditKeyOnlyKeepsVector(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindFirma, "ACME", mgrOwner)
	key := "ACME Kalıp"
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "ACME", Key: &key}); err != nil {
		t.Fatal(err)
	}
	stored, _ := e.repo.Get(context.Background(), "c1")
	if len(stored.Embedding) != 3 || e.emb.calls != 0 {
		t.Errorf("metin değişmedi: vektör korunmalı, embedding çağrılmamalı (çağrı=%d)", e.emb.calls)
	}
}

// ---- Get / List / History ----

func TestGetHasNoVectorAndListCounts(t *testing.T) {
	e := newMgr(t)
	for i, n := range []string{"A Firma", "B Firma", "C Firma"} {
		e.seed(t, "f"+string(rune('1'+i)), KindFirma, n, mgrOwner)
	}
	e.seed(t, "k1", KindKisi, "Ali Veli", mgrOther)

	c, err := e.svc.Get(context.Background(), mgrOther, "f1")
	if err != nil || len(c.Embedding) != 0 || c.Text != "A Firma" {
		t.Fatalf("Get yanlış: %+v %v", c, err)
	}
	if _, err := e.svc.Get(context.Background(), mgrOther, "yok"); !errors.Is(err, ErrNotFound) {
		t.Errorf("ErrNotFound bekleniyordu: %v", err)
	}
	items, total, err := e.svc.List(context.Background(), mgrOther, Filter{Kinds: []string{KindFirma}}, 2, 0)
	if err != nil || len(items) != 2 || total != 3 {
		t.Fatalf("sayfa 1: %d öğe, toplam %d, %v", len(items), total, err)
	}
	items, total, _ = e.svc.List(context.Background(), mgrOther, Filter{Kinds: []string{KindFirma}}, 2, 2)
	if len(items) != 1 || total != 3 || items[0].Text != "C Firma" {
		t.Errorf("sayfa 2 yanlış: %+v toplam %d", items, total)
	}
	if _, _, err := e.svc.List(context.Background(), Actor{}, Filter{}, 0, 0); !errors.Is(err, ErrNoActor) {
		t.Errorf("kimliksiz liste reddedilmeli: %v", err)
	}
}

func TestHistoryAccessAndSurvivesDelete(t *testing.T) {
	e := newMgr(t)
	e.seed(t, "c1", KindGeneral, "v1", mgrOwner)
	// Create denetimi Service.Write ile yazılır; burada elle ekliyoruz ki yazar anlık görüntüde bulunsun.
	created := readClaim("c1", KindGeneral, "v1", StatusActive, nil)
	created.AuthorID = "u-ali"
	if err := e.audit.Append(context.Background(), &AuditEntry{ID: "a0", ClaimID: "c1", Action: ActionCreate, By: "u-ali",
		At: e.clock, After: &created}); err != nil {
		t.Fatal(err)
	}
	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "v2"}); err != nil {
		t.Fatal(err)
	}
	if err := e.svc.Delete(context.Background(), mgrPatron, "c1"); err != nil {
		t.Fatal(err)
	}

	for _, who := range []Actor{mgrOwner, mgrPatron} {
		h, err := e.svc.History(context.Background(), who, "c1", 0, 0)
		if err != nil || len(h) != 3 {
			t.Fatalf("%s: geçmiş %d girdi, hata %v", who.ID, len(h), err)
		}
		if h[0].Action != ActionDelete || h[1].Action != ActionUpdate || h[2].Action != ActionCreate {
			t.Errorf("sıra yeni önce olmalı: %v %v %v", h[0].Action, h[1].Action, h[2].Action)
		}
	}
	if _, err := e.svc.History(context.Background(), mgrOther, "c1", 0, 0); !errors.Is(err, ErrForbidden) {
		t.Errorf("başka üye geçmişi görmemeli: %v", err)
	}
	if _, err := e.svc.History(context.Background(), mgrPatron, "hiç-yok", 0, 0); !errors.Is(err, ErrNotFound) {
		t.Errorf("ErrNotFound bekleniyordu: %v", err)
	}
	page, _ := e.svc.History(context.Background(), mgrPatron, "c1", 1, 1)
	if len(page) != 1 || page[0].Action != ActionUpdate {
		t.Errorf("sayfalama yanlış: %+v", page)
	}
	if past, _ := e.svc.History(context.Background(), mgrPatron, "c1", 10, 99); len(past) != 0 {
		t.Errorf("ofset sonu boş dönmeli: %+v", past)
	}
}

// Saat donmuş olsa bile (kaba çözünürlük) art arda işlemler KESİN artan damga alır: geçmiş sırası
// kimliğe (rastgele) kalmaz.
func TestHistoryOrderIsStableWithFrozenClock(t *testing.T) {
	e := newMgr(t)
	frozen := time.Date(2026, 10, 6, 9, 0, 0, 123456789, time.UTC)
	e.svc.now = func() time.Time { return frozen }
	e.seed(t, "c1", KindGeneral, "v1", mgrOwner)

	if _, err := e.svc.Edit(context.Background(), mgrOwner, "c1", EditRequest{Text: "v2"}); err != nil {
		t.Fatal(err)
	}
	if _, err := e.svc.Edit(context.Background(), mgrPatron, "c1", EditRequest{Text: "v3"}); err != nil {
		t.Fatal(err)
	}
	if err := e.svc.Delete(context.Background(), mgrOwner, "c1"); err != nil {
		t.Fatal(err)
	}
	for i := 0; i < 20; i++ { // kimlikler rastgele: tek koşuda şans eseri doğru çıkmasın
		h, err := e.svc.History(context.Background(), mgrPatron, "c1", 0, 0)
		if err != nil || len(h) != 3 {
			t.Fatalf("geçmiş: %d girdi, %v", len(h), err)
		}
		if h[0].Action != ActionDelete || h[1].By != "u-patron" || h[2].By != "u-ali" {
			t.Fatalf("sıra yeni önce olmalı: %s/%s %s/%s %s/%s", h[0].Action, h[0].By, h[1].Action, h[1].By, h[2].Action, h[2].By)
		}
		if !h[0].At.After(h[1].At) || !h[1].At.After(h[2].At) {
			t.Fatalf("damgalar kesin artan olmalı: %v %v %v", h[0].At, h[1].At, h[2].At)
		}
		if h[2].At.Nanosecond()%int(time.Millisecond) != 0 {
			t.Fatalf("damga milisaniyeye kırpılmalı: %v", h[2].At)
		}
	}
}

func TestCanModify(t *testing.T) {
	c := &Claim{AuthorID: "u-ali"}
	cases := []struct {
		a    Actor
		want bool
	}{
		{mgrOwner, true}, {mgrPatron, true}, {mgrOther, false},
		{Actor{}, false}, {Actor{ID: "u-ali", Role: ""}, true}, {Actor{ID: "  "}, false},
	}
	for _, tc := range cases {
		if got := CanModify(tc.a, c); got != tc.want {
			t.Errorf("CanModify(%+v) = %v, beklenen %v", tc.a, got, tc.want)
		}
	}
	if CanModify(mgrPatron, nil) {
		t.Errorf("nil kayıt değiştirilemez")
	}
}
