package pipeline

import (
	"strings"
	"testing"
)

func TestMessagesInjectionLayout(t *testing.T) {
	tpl := writeTemplate(t, "Стабильный системный префикс.\n---USER---\nПереведи: {{text}}")
	v := RenderVars{Book: testBook(), Text: "исходный чанк"}
	injection := "ГЛОССАРИЙ:\n阿Q → А-кью"

	msgs, err := MessagesWithInjection(tpl, v, injection)
	if err != nil {
		t.Fatal(err)
	}
	if len(msgs) != 3 {
		t.Fatalf("want 3 messages (system, injection, user), got %d", len(msgs))
	}
	// [0] stable system prefix carries the cache boundary.
	if msgs[0].Role != "system" || !msgs[0].CacheBoundary {
		t.Errorf("msg[0] must be the system prefix with CacheBoundary=true: %+v", msgs[0])
	}
	// [1] injection sits AFTER the boundary (volatile → CacheBoundary=false).
	if msgs[1].Role != "system" || msgs[1].CacheBoundary || msgs[1].Content != injection {
		t.Errorf("msg[1] must be the injection with CacheBoundary=false: %+v", msgs[1])
	}
	// [2] the volatile user chunk. It must contain ONLY the rendered chunk, never the
	// glossary (ch.Text stays clean — the 3a contract; the coverage gate reads ch.Text).
	if msgs[2].Role != "user" {
		t.Errorf("msg[2] must be user: %+v", msgs[2])
	}
	if strings.Contains(msgs[2].Content, "ГЛОССАРИЙ") || strings.Contains(msgs[2].Content, "阿Q") {
		t.Errorf("glossary leaked into the user (ch.Text) message: %q", msgs[2].Content)
	}
	if !strings.Contains(msgs[2].Content, "исходный чанк") {
		t.Errorf("user message lost the chunk: %q", msgs[2].Content)
	}
}

func TestMessagesEmptyInjectionIdentical(t *testing.T) {
	tpl := writeTemplate(t, "Системный префикс.\n---USER---\nТекст: {{text}}")
	v := RenderVars{Book: testBook(), Text: "чанк"}
	plain, _ := Messages(tpl, v)
	withEmpty, _ := MessagesWithInjection(tpl, v, "   ") // whitespace-only → no injection
	if len(plain) != 2 || len(withEmpty) != 2 {
		t.Fatalf("empty injection must yield 2 messages: %d vs %d", len(plain), len(withEmpty))
	}
	for i := range plain {
		if plain[i] != withEmpty[i] {
			t.Errorf("empty-injection message %d differs from plain: %+v vs %+v", i, withEmpty[i], plain[i])
		}
	}
}

// TestInjectionChangesRequestHash: the injection is folded into request_hash via the
// messages, so a different glossary is a different request (a changed approved term
// re-derives the chunk, never serves a stale checkpoint).
func TestInjectionChangesRequestHash(t *testing.T) {
	tpl := writeTemplate(t, "Префикс.\n---USER---\n{{text}}")
	v := RenderVars{Book: testBook(), Text: "чанк"}
	none, _ := Messages(tpl, v)
	inj, _ := MessagesWithInjection(tpl, v, "ГЛОССАРИЙ:\n阿Q → А-кью")
	hNone := RequestHash("b", 1, 0, 0, "draft", "translator", "m", 0.3, "off", false, 1024, "snap", none)
	hInj := RequestHash("b", 1, 0, 0, "draft", "translator", "m", 0.3, "off", false, 1024, "snap", inj)
	if hNone == hInj {
		t.Error("injection did not change request_hash — a glossary change would silently hit a stale checkpoint")
	}
}

func TestRenderGlossaryBlock(t *testing.T) {
	confirmed := pickedEntry{entry: &memoryEntry{src: "阿Q", dst: "А-кью", status: "approved"}, disp: memConfirmed}
	ambiguous := pickedEntry{entry: &memoryEntry{src: "小D", dst: "Малыш Дэ", status: "auto"}, disp: memAmbiguous}
	emptyDst := pickedEntry{entry: &memoryEntry{src: "鈴木", dst: "", status: "auto"}, disp: memAmbiguous}

	block := renderGlossaryBlock([]pickedEntry{confirmed, ambiguous, emptyDst})
	if !strings.Contains(block, "阿Q → А-кью") {
		t.Errorf("confirmed line missing: %q", block)
	}
	if !strings.Contains(block, "小D → Малыш Дэ  ⟨проверить⟩") {
		t.Errorf("ambiguous line missing its unverified tag: %q", block)
	}
	if strings.Contains(block, "鈴木") {
		t.Errorf("empty-dst candidate should be skipped (nothing to inject): %q", block)
	}
	// Nothing to inject → empty block (лучше ничего).
	if renderGlossaryBlock(nil) != "" {
		t.Error("empty selection must render an empty block")
	}
	if renderGlossaryBlock([]pickedEntry{emptyDst}) != "" {
		t.Error("only-empty-dst selection must render an empty block")
	}
}
