package main

import (
	"context"
	"math"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/kami/maven/internal/router"
)

func TestPersonalBoundaryLinearHeadSeparatesSemanticDirections(t *testing.T) {
	personal := [][]float32{{1, 0}, {0.9, 0.1}, {0.8, -0.1}}
	world := [][]float32{{-1, 0}, {-0.9, 0.1}, {-0.8, -0.1}}
	head, ok := trainPersonalBoundaryLinearHead(personal, world)
	if !ok {
		t.Fatal("valid training vectors were rejected")
	}
	b := personalBoundary{personal: personal, world: world, head: head, loaded: true}
	for _, tc := range []struct {
		vector   []float32
		personal bool
	}{
		{vector: []float32{0.75, 0.2}, personal: true},
		{vector: []float32{-0.75, 0.2}, personal: false},
	} {
		personalScore, worldScore, ok := b.score(tc.vector)
		if !ok {
			t.Fatal("loaded boundary did not score")
		}
		if got := personalScore > worldScore; got != tc.personal {
			t.Fatalf("vector %v classified personal=%v (scores %.4f/%.4f), want %v",
				tc.vector, got, personalScore, worldScore, tc.personal)
		}
		if math.Abs(personalScore+worldScore-1) > 1e-12 {
			t.Fatalf("scores %.8f and %.8f are not complementary probabilities", personalScore, worldScore)
		}
	}
}

func TestPersonalBoundaryTrainingBalancesClasses(t *testing.T) {
	personal := [][]float32{{1, 0}, {0.8, 0.2}}
	world := [][]float32{{-1, 0}}
	oneWorld, ok := trainPersonalBoundaryLinearHead(personal, world)
	if !ok {
		t.Fatal("valid training vectors were rejected")
	}
	repeatedWorld := make([][]float32, 12)
	for i := range repeatedWorld {
		repeatedWorld[i] = world[0]
	}
	twelveWorld, ok := trainPersonalBoundaryLinearHead(personal, repeatedWorld)
	if !ok {
		t.Fatal("valid repeated training vectors were rejected")
	}
	if math.Abs(oneWorld.bias-twelveWorld.bias) > 1e-10 {
		t.Fatalf("duplicating one class moved bias from %.12f to %.12f", oneWorld.bias, twelveWorld.bias)
	}
	for i := range oneWorld.weights {
		if math.Abs(oneWorld.weights[i]-twelveWorld.weights[i]) > 1e-10 {
			t.Fatalf("duplicating one class moved weight %d from %.12f to %.12f",
				i, oneWorld.weights[i], twelveWorld.weights[i])
		}
	}
}

func TestPersonalBoundaryTrainingRejectsMixedDimensions(t *testing.T) {
	if _, ok := trainPersonalBoundaryLinearHead(
		[][]float32{{1, 0}},
		[][]float32{{-1, 0, 0}},
	); ok {
		t.Fatal("mixed embedding dimensions were accepted")
	}
}

// The corpus is grouped by sentence shape in personalboundary.go. This test
// leaves one entire shape out of training at a time, then requires the linear
// head to classify the omitted examples from the semantics learned from the
// other shapes. It is ordinary deterministic CI: the small axis vectors stand
// in for frozen embedding directions, so the test proves the training code
// generalises across groups rather than memorising one row at a time.
func TestPersonalBoundaryLinearHeadLeaveOneShapeOut(t *testing.T) {
	type example struct {
		vector []float32
		shape  int
		want   bool
	}
	const shapeCount = 6
	examples := make([]example, 0, shapeCount*4)
	for shape := 0; shape < shapeCount; shape++ {
		for variant := 0; variant < 2; variant++ {
			personal := make([]float32, shapeCount+1)
			world := make([]float32, shapeCount+1)
			personal[0], world[0] = 1, -1
			personal[shape+1] = float32(0.1 * float64(variant+1))
			world[shape+1] = float32(-0.1 * float64(variant+1))
			examples = append(examples,
				example{vector: personal, shape: shape, want: true},
				example{vector: world, shape: shape, want: false},
			)
		}
	}

	for omitted := 0; omitted < shapeCount; omitted++ {
		var personal, world [][]float32
		for _, example := range examples {
			if example.shape == omitted {
				continue
			}
			if example.want {
				personal = append(personal, example.vector)
			} else {
				world = append(world, example.vector)
			}
		}
		head, ok := trainPersonalBoundaryLinearHead(personal, world)
		if !ok {
			t.Fatalf("fold %d rejected valid vectors", omitted)
		}
		for _, example := range examples {
			if example.shape != omitted {
				continue
			}
			if got := head.logit(example.vector) > 0; got != example.want {
				t.Errorf("fold %d classified %v as personal=%v, want %v", omitted, example.vector, got, example.want)
			}
		}
	}
}

func TestPersonalBoundaryTrainingCorpusIsIndependent(t *testing.T) {
	// The strict stratified fixture already enforces this for its 72 rows. The
	// historical regression table lives here, so protect it here too: a future
	// seed addition must not copy a regression sentence into training.
	training := make(map[string]bool, len(personalSeeds)+len(worldSeeds))
	for _, seed := range append(append([]string(nil), personalSeeds...), worldSeeds...) {
		training[normalizePersonalBoundaryTraining(seed)] = true
	}
	for _, regression := range []string{
		"что я говорил про бэкапы?",
		"что я сказал вчера про отпуск",
		"я писал что-нибудь про сервер",
		"я упоминал про конференцию?",
		"что я отмечал по поводу переезда",
		"я рассказывал тебе про новую работу?",
		"во сколько у меня встреча",
		"когда мой следующий отпуск",
		"what did i say about backups",
		"did i tell you about the doctor",
		"как я говорил, почему небо синее",
		"как уже я говорил, какая столица франции",
		"почему трава зелёная",
		"столица франции",
		"как мне сварить борщ",
		"что мне посмотреть вечером",
		"я хочу узнать про рим",
		"кто такой гагарин",
		"how do i boil an egg",
		"во сколько закат сегодня",
		"когда сегодня заканчивается концерт",
		"во сколько завтра открывается аптека",
		"какой сегодня праздник",
		"что интересного произошло сегодня в мире",
		"кто выиграл вчера матч",
		"расскажи про эверест",
		"расскажи про войну 1812 года",
		"объясни что такое инфляция",
		"я рассказывал тебе про байкал?",
	} {
		if training[normalizePersonalBoundaryTraining(regression)] {
			t.Errorf("regression utterance leaked into training: %q", regression)
		}
	}
}

func TestPersonalBoundaryFrozenHeadDecodes(t *testing.T) {
	head, ok := frozenPersonalBoundaryHead()
	if !ok {
		t.Fatal("frozen head did not decode")
	}
	if len(head.weights) != 384 {
		t.Fatalf("frozen head has %d weights, want 384", len(head.weights))
	}
}

func TestPersonalBoundaryHashFloorFitsAndScores(t *testing.T) {
	b := &personalBoundary{}
	embedder := router.NewHashEmbedder(1024)
	query, err := router.EmbedQuery(context.Background(), embedder, "когда моя встреча")
	if err != nil {
		t.Fatal(err)
	}
	b.load(context.Background(), embedder)
	if _, _, ok := b.score(query); !ok {
		t.Fatal("hash-floor boundary declined to score")
	}
	if len(b.head.weights) != 1024 {
		t.Fatalf("hash-floor boundary has %d weights, want 1024", len(b.head.weights))
	}
}

// BenchmarkPersonalBoundaryHashFloorFitAndScore keeps startup cost measurable
// without making ambient CI load a correctness condition. In particular,
// -race and coverage instrumentation both multiply the cost of this numeric
// training loop; the functional test above is the deterministic gate.
func BenchmarkPersonalBoundaryHashFloorFitAndScore(b *testing.B) {
	embedder := router.NewHashEmbedder(1024)
	query, err := router.EmbedQuery(context.Background(), embedder, "когда моя встреча")
	if err != nil {
		b.Fatal(err)
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		boundary := &personalBoundary{}
		boundary.load(context.Background(), embedder)
		if _, _, ok := boundary.score(query); !ok {
			b.Fatal("hash-floor boundary declined to score")
		}
	}
}

func normalizePersonalBoundaryTraining(value string) string {
	return strings.Join(strings.Fields(strings.ToLower(value)), " ")
}

// A handler with no embedder never loads the seeds, so the boundary falls back
// to the possession markers. That is the offline floor and it must keep working
// — an embedder that fails to load must not open the boundary.
func TestBoundaryFallsBackToMarkersWithNoEmbedder(t *testing.T) {
	h := personalHandler()
	if !h.isPersonalTurn(context.Background(), &queryTurn{
		dec: router.Decision{Utterance: "во сколько у меня встреча"},
	}) {
		t.Error("no embedder: a possession question must still be personal")
	}
	if h.isPersonalTurn(context.Background(), &queryTurn{
		dec: router.Decision{Utterance: "почему небо синее"},
	}) {
		t.Error("no embedder: a world question must still pass")
	}
}

// TestONNXPersonalBoundary — the number that matters, scored against the
// embedder homesrv actually runs. Opt-in via MAVEN_ONNX_LIB, exactly like
// TestONNXRecall in internal/memory/recalleval.
//
// Every case here is held out: none of these strings is a seed. The #495
// regression is the first row — "что я говорил про бэкапы?" reached SearXNG and
// was answered from a Habr article, and no possession word appears in it.
func TestONNXPersonalBoundary(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()

	cases := []struct {
		utterance string
		personal  bool
	}{
		{"что я говорил про бэкапы?", true},
		{"что я сказал вчера про отпуск", true},
		{"я писал что-нибудь про сервер", true},
		{"я упоминал про конференцию?", true},
		{"что я отмечал по поводу переезда", true},
		{"я рассказывал тебе про новую работу?", true},
		{"во сколько у меня встреча", true},
		{"когда мой следующий отпуск", true},
		{"what did i say about backups", true},
		{"did i tell you about the doctor", true},
		{"как я говорил, почему небо синее", false},
		{"как уже я говорил, какая столица франции", false},
		{"почему трава зелёная", false},
		{"столица франции", false},
		{"как мне сварить борщ", false},
		{"что мне посмотреть вечером", false},
		{"я хочу узнать про рим", false},
		{"кто такой гагарин", false},
		{"how do i boil an egg", false},
		// Asking when a public thing happens (Vikunja #553). "во сколько закат
		// сегодня" was answered "не знаю — не нашла у тебя такой записи",
		// because the frame lived only on the personal side. The pair above it
		// is the control: "во сколько у меня встреча" is the same frame about
		// something that IS his, and it has to stay personal.
		{"во сколько закат сегодня", false},
		{"когда сегодня заканчивается концерт", false},
		{"во сколько завтра открывается аптека", false},
		// The "какой сегодня X" frame. These clear the weather topic after the
		// V-553 seeds and were then refused here, which is the same defect one
		// source further down the chain.
		{"какой сегодня праздник", false},
		{"что интересного произошло сегодня в мире", false},
		{"кто выиграл вчера матч", false},
		// The narrative shape, held out from the seeds above (Vikunja #554).
		// The control is the row after them: the same verb about his own words
		// is still his.
		{"расскажи про эверест", false},
		{"расскажи про войну 1812 года", false},
		{"объясни что такое инфляция", false},
		{"я рассказывал тебе про байкал?", true},
	}

	h := &reactiveHandler{recall: recallWiring{embedder: emb}}
	ctx := context.Background()
	wrong := 0
	for _, c := range cases {
		vec, err := router.EmbedQuery(ctx, emb, c.utterance)
		if err != nil {
			t.Fatalf("embed %q: %v", c.utterance, err)
		}
		turn := &queryTurn{dec: router.Decision{Utterance: c.utterance}, vec: vec}
		got := h.isPersonalTurn(ctx, turn)
		p, w, ok := h.recall.boundary.score(vec)
		if !ok {
			t.Fatal("seeds did not load with a working embedder")
		}
		if got != c.personal {
			wrong++
			t.Errorf("%q: personal=%v want %v (personal %.4f world %.4f)", c.utterance, got, c.personal, p, w)
		}
		t.Logf("personal=%-5v personal %.4f world %.4f delta %+.4f  %s", got, p, w, p-w, c.utterance)
	}
	t.Logf("personal boundary: %d/%d held-out utterances correct", len(cases)-wrong, len(cases))
}

func TestONNXPersonalBoundaryFourFold(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()
	ctx := context.Background()
	embedAll := func(values []string) [][]float32 {
		vectors := make([][]float32, len(values))
		for i, value := range values {
			vector, err := router.EmbedQuery(ctx, emb, value)
			if err != nil {
				t.Fatalf("embed %q: %v", value, err)
			}
			vectors[i] = vector
		}
		return vectors
	}
	personalVectors := embedAll(personalSeeds)
	worldVectors := embedAll(worldSeeds)

	type group struct {
		name                         string
		personalStart, personalCount int
		worldStart, worldCount       int
	}
	groups := []group{
		{name: "remembered_speech", personalStart: 8, personalCount: 8, worldStart: 20, worldCount: 8},
		{name: "possession", personalStart: 16, personalCount: 10, worldStart: 28, worldCount: 12},
		{name: "narrative", personalStart: 26, personalCount: 8, worldStart: 40, worldCount: 8},
		{name: "first_person_preamble", personalStart: 34, personalCount: 8, worldStart: 48, worldCount: 8},
		{name: "advice_current_info", personalStart: 42, personalCount: 8, worldStart: 56, worldCount: 8},
		{name: "public_proper_nouns", personalStart: 50, personalCount: 10, worldStart: 64, worldCount: 8},
	}

	const foldCount = 4
	aggregateCorrect, aggregateTotal := 0, 0
	for omittedFold := 0; omittedFold < foldCount; omittedFold++ {
		trainingPersonal := append([][]float32(nil), personalVectors[:8]...)
		trainingWorld := append([][]float32(nil), worldVectors[:20]...)
		var heldPersonal, heldWorld [][]float32
		partition := func(vectors [][]float32, start, count int, training, held *[][]float32) {
			for relative, vector := range vectors[start : start+count] {
				if relative%foldCount == omittedFold {
					*held = append(*held, vector)
				} else {
					*training = append(*training, vector)
				}
			}
		}
		for _, group := range groups {
			partition(personalVectors, group.personalStart, group.personalCount, &trainingPersonal, &heldPersonal)
			partition(worldVectors, group.worldStart, group.worldCount, &trainingWorld, &heldWorld)
		}
		head, ok := trainPersonalBoundaryLinearHead(
			trainingPersonal,
			trainingWorld,
		)
		if !ok {
			t.Fatalf("fold %d: valid training fold rejected", omittedFold)
		}
		correct, total := 0, 0
		for _, vector := range heldPersonal {
			total++
			if head.logit(vector) > 0 {
				correct++
			}
		}
		for _, vector := range heldWorld {
			total++
			if head.logit(vector) <= 0 {
				correct++
			}
		}
		t.Logf("fold %d: %d/%d held-out training examples", omittedFold+1, correct, total)
		aggregateCorrect += correct
		aggregateTotal += total
	}
	t.Logf("four-fold aggregate: %d/%d", aggregateCorrect, aggregateTotal)
	if aggregateCorrect < 99 {
		t.Errorf("four-fold aggregate %d/%d, want at least 99/104", aggregateCorrect, aggregateTotal)
	}
}

func TestONNXPersonalBoundarySemanticGroupHoldout(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()
	ctx := context.Background()
	embedAll := func(values []string) [][]float32 {
		vectors := make([][]float32, len(values))
		for i, value := range values {
			vector, err := router.EmbedQuery(ctx, emb, value)
			if err != nil {
				t.Fatalf("embed %q: %v", value, err)
			}
			vectors[i] = vector
		}
		return vectors
	}
	personalVectors := embedAll(personalSeeds)
	worldVectors := embedAll(worldSeeds)

	type group struct {
		name                         string
		personalStart, personalCount int
		worldStart, worldCount       int
	}
	groups := []group{
		{name: "remembered_speech", personalStart: 8, personalCount: 8, worldStart: 20, worldCount: 8},
		{name: "possession", personalStart: 16, personalCount: 10, worldStart: 28, worldCount: 12},
		{name: "narrative", personalStart: 26, personalCount: 8, worldStart: 40, worldCount: 8},
		{name: "first_person_preamble", personalStart: 34, personalCount: 8, worldStart: 48, worldCount: 8},
		{name: "advice_current_info", personalStart: 42, personalCount: 8, worldStart: 56, worldCount: 8},
		{name: "public_proper_nouns", personalStart: 50, personalCount: 10, worldStart: 64, worldCount: 8},
	}

	aggregateCorrect, aggregateTotal := 0, 0
	for _, omitted := range groups {
		excluding := func(vectors [][]float32, start, count int) [][]float32 {
			result := make([][]float32, 0, len(vectors)-count)
			result = append(result, vectors[:start]...)
			return append(result, vectors[start+count:]...)
		}
		head, ok := trainPersonalBoundaryLinearHead(
			excluding(personalVectors, omitted.personalStart, omitted.personalCount),
			excluding(worldVectors, omitted.worldStart, omitted.worldCount),
		)
		if !ok {
			t.Fatalf("%s: valid training fold rejected", omitted.name)
		}
		correct, total := 0, 0
		for _, vector := range personalVectors[omitted.personalStart : omitted.personalStart+omitted.personalCount] {
			total++
			if head.logit(vector) > 0 {
				correct++
			}
		}
		for _, vector := range worldVectors[omitted.worldStart : omitted.worldStart+omitted.worldCount] {
			total++
			if head.logit(vector) <= 0 {
				correct++
			}
		}
		t.Logf("leave %-21s out: %d/%d", omitted.name, correct, total)
		aggregateCorrect += correct
		aggregateTotal += total
		// Whole-shape holdout is an honest diagnostic, not a 100% release gate:
		// some shapes (notably private-vs-general possession) define a distinct
		// semantic ambiguity. The separately authored challenge set remains the
		// strict generalisation gate.
	}
	if aggregateCorrect < 92 {
		t.Errorf("whole-shape aggregate %d/%d, want at least 92/104", aggregateCorrect, aggregateTotal)
	}
}

// This challenge set was originally authored after the six-shape training
// corpus and the 72-case matrix were frozen. Its sole miss then informed the
// regularisation comparison, so it is now a strict regression gate rather than
// independent evidence. It remains outside the production corpus.
func TestONNXPersonalBoundaryChallenge(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()

	cases := []struct {
		utterance string
		personal  bool
	}{
		{"какой пароль приложения я задал для почтового клиента?", true},
		{"на каком порту я решил поднять тестовый сервис?", true},
		{"какую причину я указал, когда отменил бронь?", true},
		{"где в гараже я сложил зимние шины?", true},
		{"какой сериал я бросил после второго сезона?", true},
		{"о чём мы договорились с Олегом на прошлой неделе?", true},
		{"почему мой монитор мерцает при частоте 144 герца?", false},
		{"подойдёт ли кабель Thunderbolt 3 к разъёму USB4?", false},
		{"как вывести запах дыма из моей куртки?", false},
		{"что означают кольца на флаге Олимпиады?", false},
		{"почему после дождя на асфальте видна радуга?", false},
		{"какой формат файлов поддерживает Kindle Paperwhite?", false},
		{"which SSH key did I install on the build server?", true},
		{"what spending limit did I set for the travel card?", true},
		{"where did I store the spare apartment fob?", true},
		{"which objection did I raise during the design review?", true},
		{"what route did I plan for the Sunday hike?", true},
		{"when did I promise Maya I would send the draft?", true},
		{"why does my mechanical keyboard sometimes chatter?", false},
		{"can my USB-C charger safely power a Steam Deck?", false},
		{"how do I stop condensation inside my camera lens?", false},
		{"what caused the Tacoma Narrows Bridge to collapse?", false},
		{"why are some auroras red instead of green?", false},
		{"which codecs does the current Firefox release support?", false},
	}

	b := &personalBoundary{}
	b.load(context.Background(), emb)
	correct := 0
	minimumMargin := math.Inf(1)
	for _, testCase := range cases {
		vector, err := router.EmbedQuery(context.Background(), emb, testCase.utterance)
		if err != nil {
			t.Fatalf("embed %q: %v", testCase.utterance, err)
		}
		personal, world, ok := b.score(vector)
		if !ok {
			t.Fatal("loaded boundary declined to score")
		}
		got := personal > world
		signedMargin := personal - world
		if !testCase.personal {
			signedMargin = -signedMargin
		}
		if signedMargin < minimumMargin {
			minimumMargin = signedMargin
		}
		if got == testCase.personal {
			correct++
		} else {
			t.Logf("miss %q: personal=%v want %v (%.4f/%.4f)", testCase.utterance, got, testCase.personal, personal, world)
		}
	}
	t.Logf("regularisation challenge: %d/%d, minimum signed margin %+.4f", correct, len(cases), minimumMargin)
	if correct != len(cases) {
		t.Errorf("regularisation challenge %d/%d, want every case correct", correct, len(cases))
	}
}

// TestONNXPersonalBoundaryPostRetuneChallenge was authored only after the L2
// coefficient and frozen head had been selected using corpus cross-validation.
// It deliberately returns to private configuration, commitments and stored
// choices with new objects, and contrasts them with public technical facts,
// compatibility and maintenance. No result from this table may be used to
// tune the current head; a miss is evidence for the next independently
// evaluated model revision.
func TestONNXPersonalBoundaryPostRetuneChallenge(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()

	cases := []struct {
		utterance string
		personal  bool
	}{
		{"какое имя я выбрал для гостевой сети Wi-Fi?", true},
		{"на какой день я перенёс техосмотр машины?", true},
		{"какую сумму мы с Мариной согласовали за ремонт кухни?", true},
		{"где я сохранил резервные коды от GitHub?", true},
		{"какой из макетов визитки я одобрил?", true},
		{"что я решил делать со страховкой перед поездкой?", true},
		{"какой диапазон частот использует Wi-Fi 6E?", false},
		{"почему OLED-экраны со временем выгорают?", false},
		{"можно ли подключить монитор DisplayPort к Thunderbolt 4?", false},
		{"чем безопасно чистить замшевые ботинки?", false},
		{"когда появился протокол WebSocket?", false},
		{"почему соль ускоряет таяние льда?", false},
		{"which hostname did I assign to the home NAS?", true},
		{"what date did I move the annual checkup to?", true},
		{"where did I save the recovery phrase for the hardware wallet?", true},
		{"which catering quote did we accept for the party?", true},
		{"what did I decide about renewing the domain?", true},
		{"which paint sample did I approve for the hallway?", true},
		{"does Wi-Fi 7 work with older wireless clients?", false},
		{"why can an SSD slow down when it is nearly full?", false},
		{"how should suede shoes be cleaned?", false},
		{"when was the WebSocket protocol standardized?", false},
		{"what does a hardware-wallet recovery phrase do?", false},
		{"why does road salt damage concrete?", false},
	}

	b := &personalBoundary{}
	b.load(context.Background(), emb)
	correct := 0
	minimumMargin := math.Inf(1)
	for _, testCase := range cases {
		vector, err := router.EmbedQuery(context.Background(), emb, testCase.utterance)
		if err != nil {
			t.Fatalf("embed %q: %v", testCase.utterance, err)
		}
		personal, world, ok := b.score(vector)
		if !ok {
			t.Fatal("loaded boundary declined to score")
		}
		got := personal > world
		signedMargin := personal - world
		if !testCase.personal {
			signedMargin = -signedMargin
		}
		if signedMargin < minimumMargin {
			minimumMargin = signedMargin
		}
		if got == testCase.personal {
			correct++
		} else {
			t.Logf("miss %q: personal=%v want %v (%.4f/%.4f)", testCase.utterance, got, testCase.personal, personal, world)
		}
	}
	t.Logf("post-retune challenge: %d/%d, minimum signed margin %+.4f", correct, len(cases), minimumMargin)
	if correct != len(cases) {
		t.Errorf("post-retune challenge %d/%d, want every case correct", correct, len(cases))
	}
}

func TestONNXPersonalBoundaryLatency(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()
	ctx := context.Background()
	query, err := router.EmbedQuery(ctx, emb, "что я решил насчёт переезда?")
	if err != nil {
		t.Fatal(err)
	}

	b := &personalBoundary{}
	coldStart := time.Now()
	b.load(ctx, emb)
	if _, _, ok := b.score(query); !ok {
		t.Fatal("loaded boundary declined to score")
	}
	cold := time.Since(coldStart)

	const iterations = 100000
	steadyStart := time.Now()
	for i := 0; i < iterations; i++ {
		if _, _, ok := b.score(query); !ok {
			t.Fatal("loaded boundary declined to score")
		}
	}
	steady := time.Since(steadyStart) / iterations
	t.Logf("boundary cold load+train+score: %s; steady score: %s/op", cold, steady)
	// This is a user-visible first-turn path. Keep a generous ceiling to avoid
	// noisy CI while making an accidental per-turn training/load regression
	// unmistakable.
	if cold > 5*time.Second {
		t.Errorf("cold boundary load %s exceeds 5s local usability ceiling", cold)
	}
	if steady > 100*time.Microsecond {
		t.Errorf("steady boundary score %s exceeds 100µs ceiling", steady)
	}
}

func TestONNXPersonalBoundaryFrozenHeadMatchesCorpusFit(t *testing.T) {
	lib := os.Getenv("MAVEN_ONNX_LIB")
	if lib == "" {
		t.Skip("MAVEN_ONNX_LIB unset — see AGENTS.md § Embedder model for intent routing")
	}
	dir := filepath.Join("../..", "models/embedder/multilingual-e5-small")
	emb, err := router.NewONNXEmbedder(filepath.Join(dir, "model_quantized.onnx"), filepath.Join(dir, "tokenizer.json"), lib)
	if err != nil {
		t.Skipf("onnx embedder unavailable: %v", err)
	}
	defer emb.Close()
	ctx := context.Background()
	embedAll := func(values []string) [][]float32 {
		vectors := make([][]float32, len(values))
		for i, value := range values {
			vector, err := router.EmbedQuery(ctx, emb, value)
			if err != nil {
				t.Fatalf("embed %q: %v", value, err)
			}
			vectors[i] = vector
		}
		return vectors
	}
	fitted, ok := trainPersonalBoundaryLinearHead(embedAll(personalSeeds), embedAll(worldSeeds))
	if !ok {
		t.Fatal("corpus fit failed")
	}
	frozen, ok := frozenPersonalBoundaryHead()
	if !ok {
		t.Fatal("frozen head did not decode")
	}
	if math.Abs(fitted.bias-frozen.bias) > 1e-9 {
		t.Fatalf("frozen bias %.12f != fitted %.12f", frozen.bias, fitted.bias)
	}
	for i := range fitted.weights {
		if math.Abs(fitted.weights[i]-frozen.weights[i]) > 5e-7 {
			t.Fatalf("frozen weight %d %.12f != fitted %.12f", i, frozen.weights[i], fitted.weights[i])
		}
	}
}
