package pipeline

import (
	"testing"

	"textmachine/backend/internal/store"
)

// memory_f1_test.go covers the F1 content-hash (D8): memoryVersion is a pure function of
// the frozen APPROVED rows + the algorithm versions. A change to the approved glossary
// re-pins loudly; auto/draft churn does not; the row id never leaks in.

func TestMemoryVersionStableAndSensitive(t *testing.T) {
	base := []store.GlossaryEntry{
		gl("阿Q", "А-кью", "name", "approved"),
		gl("鲁镇", "Лучжэнь", "", "approved"),
	}
	v0 := computeMemoryVersion(base, false)

	// Identical rows → identical version (idempotent; drift-proof content hash).
	if computeMemoryVersion(base, false) != v0 {
		t.Fatal("memoryVersion is not stable for identical rows")
	}

	// Changing an APPROVED dst → different version (a loud --resnapshot).
	changed := []store.GlossaryEntry{
		gl("阿Q", "А-Кью (изменено)", "name", "approved"),
		gl("鲁镇", "Лучжэнь", "", "approved"),
	}
	if computeMemoryVersion(changed, false) == v0 {
		t.Error("changing an approved dst did not change memoryVersion — F1 would silently regress")
	}

	// Adding an alias to an approved row → different version (aliases affect matching).
	withAlias := []store.GlossaryEntry{
		func() store.GlossaryEntry { e := gl("阿Q", "А-кью", "name", "approved"); e.Aliases = []store.GlossaryAlias{alias("阿桂")}; return e }(),
		gl("鲁镇", "Лучжэнь", "", "approved"),
	}
	if computeMemoryVersion(withAlias, false) == v0 {
		t.Error("adding an alias to an approved row did not change memoryVersion")
	}

	// Changing an AUTO row → SAME version (approved-only, D8; auto changes are caught by
	// the per-chunk content_hash instead).
	withAuto := append(append([]store.GlossaryEntry{}, base...), gl("小D", "Малыш Дэ", "", "auto"))
	if computeMemoryVersion(withAuto, false) != v0 {
		t.Error("an auto row changed memoryVersion — it must hash only approved rows (D8)")
	}
	withAuto2 := append(append([]store.GlossaryEntry{}, base...), gl("小D", "Другой Дэ", "", "auto"))
	if computeMemoryVersion(withAuto, false) != computeMemoryVersion(withAuto2, false) {
		t.Error("editing an auto row changed memoryVersion — auto must not enter the F1 hash")
	}
}

// TestMemoryVersionIgnoresID: the autoincrement glossary.id is fresh on every replace;
// it must NOT feed the hash (else a re-seed of identical content would false-resnapshot).
func TestMemoryVersionIgnoresID(t *testing.T) {
	a := []store.GlossaryEntry{{BookID: "b", ID: 1, Src: "甲", Dst: "А", Status: "approved"}}
	b := []store.GlossaryEntry{{BookID: "b", ID: 999, Src: "甲", Dst: "А", Status: "approved"}}
	if computeMemoryVersion(a, false) != computeMemoryVersion(b, false) {
		t.Error("memoryVersion depends on glossary.id — a re-seed would false-resnapshot")
	}
}

// TestMaterializeMemoryVersionMatches: the bank's Version() equals computeMemoryVersion
// of its input rows (the runner hashes the same thing it matches on).
func TestMaterializeMemoryVersionMatches(t *testing.T) {
	rows := specGlossary()
	b := materializeMemory(rows, false)
	if b.Version() != computeMemoryVersion(rows, false) {
		t.Error("bank Version() diverges from computeMemoryVersion of its rows")
	}
}

// TestMemoryVersionGateFoldsUnapprovedDecl covers self-review #4: when the post-check
// hard gate is ON, editing a DRAFT/auto row's decl (which changes the gate's resolved
// disposition but is in NEITHER content_hash NOR the approved-only hash) MUST change
// memoryVersion — else a $0 resume silently flips a chunk. Gate OFF: it must NOT change
// it (D8 approved-only holds; decl affects only the recomputed retrieval-state).
func TestMemoryVersionGateFoldsUnapprovedDecl(t *testing.T) {
	mk := func(declForm string) []store.GlossaryEntry {
		e := gl("鈴木", "Судзуки", "", "draft")
		e.Decl = declJSON(false, declForm)
		return []store.GlossaryEntry{gl("阿Q", "А-кью", "", "approved"), e}
	}
	a := mk("Судзуки")
	b := mk("Судзуку") // only the DRAFT decl form changed (dst unchanged, status draft)

	if computeMemoryVersion(a, false) != computeMemoryVersion(b, false) {
		t.Error("gate OFF: a draft decl change must NOT move memoryVersion (D8 approved-only)")
	}
	if computeMemoryVersion(a, true) == computeMemoryVersion(b, true) {
		t.Error("gate ON: a draft decl change MUST move memoryVersion (else a resume silently flips the chunk)")
	}
	// Toggling the gate itself also changes the version (a mode change is a loud re-pin).
	if computeMemoryVersion(a, false) == computeMemoryVersion(a, true) {
		t.Error("toggling the post-check gate must change memoryVersion")
	}
}
