package eval

import (
	"context"
	"os"
	"path/filepath"
	"testing"

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

// TestONNXRoutingHeads — the cascade with the routing heads wired, which is
// what V-664 deploys. Opt-in via MAVEN_ONNX_LIB, same as TestONNXBaseline, and
// one TestONNX* per process.
//
// The comparison worth reading is against TestONNXBaseline, which is the same
// cascade with the same grammars and the same classifier floor and no heads.
// Only the middle arm varies.
//
// It also checks the Go unigram tokenizer against the Python one, because the
// heads were trained through transformers and are read through a hand-written
// tokenizer. A mismatch shows up here as a score below what Python measured on
// the same weights, and nowhere else.
func TestONNXRoutingHeads(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")
	}
	// Absolute, because onnxruntime resolves a graph's external weights file
	// against the model path it was given, and a relative one lands in the
	// test's working directory.
	root, err := filepath.Abs("../../..")
	if err != nil {
		t.Fatal(err)
	}
	model := filepath.Join(root, "models/embedder/multilingual-e5-small/model_quantized.onnx")
	tok := filepath.Join(root, "models/embedder/multilingual-e5-small/tokenizer.json")
	heads := filepath.Join(root, "models/embedder/router-heads/router_heads.onnx")
	for _, p := range []string{lib, model, tok, heads} {
		if _, err := os.Stat(p); err != nil {
			t.Skipf("missing %s: %v", p, err)
		}
	}
	emb, err2 := router.NewONNXEmbedder(model, tok, lib)
	if err2 != nil {
		t.Skipf("onnx embedder unavailable: %v", err2)
	}
	err = nil
	defer emb.Close()

	h, err := router.NewRouterHeads(heads, tok)
	if err != nil {
		t.Skipf("routing heads unavailable: %v", err)
	}
	defer h.Close()
	recordONNXGateExecuted(t)

	f, err := Load()
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	rep, err := Score(context.Background(), "heads+classifier", withHeads(t, emb, h), f)
	if err != nil {
		t.Fatalf("Score: %v", err)
	}
	t.Log("\n" + rep.String() + rep.Failures())
}

// withHeads mirrors newBaselineRouter and adds the one arm under test. It is a
// separate function rather than a parameter so the baseline's signature stays
// the shape every other test calls it with.
func withHeads(t *testing.T, emb router.Embedder, h *router.RouterHeads) *router.Router {
	t.Helper()
	acts := router.DefaultActMatcher{Fns: actFns}
	return router.New(router.Config{
		Grammars:   baselineGrammars(acts),
		Classifier: newBaselineClassifier(t, emb),
		Extractor: router.Extractor{
			Time:  router.StubDateTimeParser{},
			Acts:  acts,
			Facts: router.DefaultFactParser{},
		},
		Threshold: config.DefaultRouterThreshold,
		Heads:     h,
	})
}
