package index

import (
	"context"
	"database/sql"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"sync/atomic"
	"testing"

	"github.com/dusk-network/pituitary/internal/config"
	"github.com/dusk-network/pituitary/internal/model"
	"github.com/dusk-network/pituitary/internal/source"
	stindex "github.com/dusk-network/stroma/v3/index"
)

func TestSearchSpecsReturnsRankedSections(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	result, err := SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs() error = %v", err)
	}
	if len(result.Matches) == 0 {
		t.Fatal("SearchSpecs() returned no matches")
	}
	if got, want := result.ScoreKind, SearchSpecScoreKindHybridRelevance; got != want {
		t.Fatalf("result.ScoreKind = %q, want %q", got, want)
	}
	if !strings.Contains(result.ScoreDescription, "hybrid relevance") {
		t.Fatalf("result.ScoreDescription = %q, want hybrid relevance guidance", result.ScoreDescription)
	}
	if result.Matches[0].Ref == "" || result.Matches[0].SectionHeading == "" {
		t.Fatalf("top match = %+v, want stable ref and section heading", result.Matches[0])
	}

	var found042 bool
	for _, match := range result.Matches {
		if match.Kind != model.ArtifactKindSpec {
			t.Fatalf("match kind = %q, want %q", match.Kind, model.ArtifactKindSpec)
		}
		if match.Status == model.StatusSuperseded || match.Status == model.StatusDeprecated {
			t.Fatalf("default search unexpectedly returned inactive status %q in %+v", match.Status, match)
		}
		if match.Ref == "SPEC-042" {
			found042 = true
		}
	}
	if !found042 {
		t.Fatalf("SearchSpecs() matches = %+v, want SPEC-042 among results", result.Matches)
	}
}

func TestSearchSpecsBySemanticSimilarityReturnsSimilarityScores(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	result, err := SearchSpecsBySemanticSimilarityContext(context.Background(), cfg, SearchSpecQuery{
		Query:    "Per-Tenant Rate Limiting for Public API Endpoints",
		Statuses: []string{model.StatusDraft, model.StatusReview, model.StatusAccepted},
		Limit:    3,
	})
	if err != nil {
		t.Fatalf("SearchSpecsBySemanticSimilarityContext() error = %v", err)
	}
	if got, want := result.ScoreKind, SearchSpecScoreKindSemanticSimilarity; got != want {
		t.Fatalf("result.ScoreKind = %q, want %q", got, want)
	}
	if !strings.Contains(result.ScoreDescription, "cosine-style semantic similarity") {
		t.Fatalf("result.ScoreDescription = %q, want semantic similarity guidance", result.ScoreDescription)
	}
	if len(result.Matches) == 0 {
		t.Fatal("SearchSpecsBySemanticSimilarityContext() returned no matches")
	}
	if result.Matches[0].Score < 0.50 {
		t.Fatalf("top similarity score = %f, want a cosine-similarity-like value", result.Matches[0].Score)
	}
}

func TestSearchSpecsDefaultExcludesSupersededUnlessRequested(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	defaultResult, err := SearchSpecs(cfg, SearchSpecQuery{Query: "fixed window rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs() default error = %v", err)
	}
	for _, match := range defaultResult.Matches {
		if match.Ref == "SPEC-008" {
			t.Fatalf("default search unexpectedly returned superseded spec: %+v", match)
		}
	}

	historicalResult, err := SearchSpecs(cfg, SearchSpecQuery{
		Query:    "fixed window rate limiting",
		Statuses: []string{model.StatusSuperseded},
		Limit:    5,
	})
	if err != nil {
		t.Fatalf("SearchSpecs() superseded error = %v", err)
	}
	if len(historicalResult.Matches) == 0 {
		t.Fatal("SearchSpecs() with superseded filter returned no matches")
	}
	if historicalResult.Matches[0].Ref != "SPEC-008" {
		t.Fatalf("top historical match = %+v, want SPEC-008", historicalResult.Matches[0])
	}
}

func TestSearchSpecsAppliesDomainFilter(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	result, err := SearchSpecs(cfg, SearchSpecQuery{
		Query:  "rate limiting",
		Domain: "payments",
	})
	if err != nil {
		t.Fatalf("SearchSpecs() error = %v", err)
	}
	if len(result.Matches) != 0 {
		t.Fatalf("SearchSpecs() matches = %+v, want no matches for unrelated domain", result.Matches)
	}
}

func TestSearchSpecsIncludesRepoIdentity(t *testing.T) {
	t.Parallel()

	cfg := loadMultiRepoFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	result, err := SearchSpecs(cfg, SearchSpecQuery{Query: "shared repo rollout", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs() error = %v", err)
	}
	if len(result.Matches) == 0 {
		t.Fatal("SearchSpecs() returned no matches")
	}
	if got, want := result.Matches[0].Repo, "shared"; got != want {
		t.Fatalf("top match repo = %q, want %q", got, want)
	}
	if got, want := result.Matches[0].SourceRef, "file://specs/shared-rollout/spec.toml"; got != want {
		t.Fatalf("top match source_ref = %q, want %q", got, want)
	}
}

func TestSearchSpecsRejectsInvalidStatuses(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	_, err = SearchSpecs(cfg, SearchSpecQuery{
		Query:    "rate limiting",
		Statuses: []string{"invalid"},
	})
	if err == nil || err.Error() != `unsupported status "invalid"` {
		t.Fatalf("SearchSpecs() error = %v, want unsupported status", err)
	}
}

func TestSearchSpecsRejectsLimitAboveMaximum(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	_, err = SearchSpecs(cfg, SearchSpecQuery{
		Query: "rate limiting",
		Limit: maxSearchLimit + 1,
	})
	if err == nil || err.Error() != `limit must be less than or equal to 50` {
		t.Fatalf("SearchSpecs() error = %v, want maximum-limit validation", err)
	}
}

func TestSearchSpecsContextHonorsCanceledContext(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	_, err = SearchSpecsContext(ctx, cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("SearchSpecsContext() error = %v, want context.Canceled", err)
	}
}

func TestSearchSpecsIndexesMarkdownContracts(t *testing.T) {
	t.Parallel()

	repo := t.TempDir()
	configPath := filepath.Join(repo, "pituitary.toml")
	mustWriteFile(t, configPath, `
[workspace]
root = "."
index_path = ".pituitary/pituitary.db"

[runtime.embedder]
provider = "fixture"
model = "fixture-8d"
timeout_ms = 1000
max_retries = 0

[[sources]]
name = "contracts"
adapter = "filesystem"
kind = "markdown_contract"
path = "contracts"
`)
	mustWriteFile(t, filepath.Join(repo, "contracts", "auth", "session-policy.md"), `
Ref: RFC-AUTH-001
Status: accepted
Domain: identity

# Session Policy

Interactive authentication sessions must use tenant-scoped policy checks and sliding-window enforcement.
`)

	cfg, err := config.Load(configPath)
	if err != nil {
		t.Fatalf("config.Load() error = %v", err)
	}
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	result, err := SearchSpecs(cfg, SearchSpecQuery{
		Query:    "tenant-scoped authentication sessions",
		Statuses: []string{model.StatusAccepted},
		Limit:    5,
	})
	if err != nil {
		t.Fatalf("SearchSpecs() error = %v", err)
	}
	if len(result.Matches) == 0 {
		t.Fatal("SearchSpecs() returned no matches")
	}
	if got, want := result.Matches[0].Ref, "RFC-AUTH-001"; got != want {
		t.Fatalf("top match ref = %q, want %q", got, want)
	}
	if result.Matches[0].Inference == nil {
		t.Fatalf("top match inference = nil, want structured inference confidence")
	}
	if got, want := result.Matches[0].Inference.Kind, config.SourceKindMarkdownContract; got != want {
		t.Fatalf("top match inference kind = %q, want %q", got, want)
	}
	if got, want := result.Matches[0].Inference.Level, "medium"; got != want {
		t.Fatalf("top match inference level = %q, want %q", got, want)
	}
	if len(result.Matches[0].Inference.Reasons) == 0 || result.Matches[0].Inference.Reasons[0] != "applies_to missing" {
		t.Fatalf("top match inference reasons = %+v, want applies_to warning", result.Matches[0].Inference.Reasons)
	}
}

func TestSearchSpecsDownranksHistoricalSectionsByDefault(t *testing.T) {
	t.Parallel()

	repo := t.TempDir()
	configPath := filepath.Join(repo, "pituitary.toml")
	mustWriteSearchFile(t, configPath, `
[workspace]
root = "."
index_path = ".pituitary/pituitary.db"

[runtime.embedder]
provider = "fixture"
model = "fixture-8d"
timeout_ms = 1000
max_retries = 0

[[sources]]
name = "specs"
adapter = "filesystem"
kind = "spec_bundle"
path = "specs"
`)
	mustWriteSearchFile(t, filepath.Join(repo, "specs", "locality-kernel", "spec.toml"), `
id = "SPEC-LOCALITY"
title = "Locality Kernel Contract"
status = "accepted"
domain = "runtime"
body = "body.md"
`)
	mustWriteSearchFile(t, filepath.Join(repo, "specs", "locality-kernel", "body.md"), `
## Requirements

Locality continuity semantics define the active runtime contract.

## Historical provenance

Locality continuity kernel semantics governed earlier drafts.
`)

	cfg, err := config.Load(configPath)
	if err != nil {
		t.Fatalf("config.Load() error = %v", err)
	}
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	activeResult, err := SearchSpecs(cfg, SearchSpecQuery{Query: "locality continuity kernel semantics", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs() default error = %v", err)
	}
	if len(activeResult.Matches) == 0 {
		t.Fatal("SearchSpecs() default returned no matches")
	}
	if strings.Contains(strings.ToLower(activeResult.Matches[0].SectionHeading), "historical provenance") {
		t.Fatalf("default top match = %+v, want active section ahead of historical provenance", activeResult.Matches[0])
	}

	historicalResult, err := SearchSpecs(cfg, SearchSpecQuery{Query: "historical provenance locality continuity kernel semantics", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs() historical error = %v", err)
	}
	if len(historicalResult.Matches) == 0 {
		t.Fatal("SearchSpecs() historical returned no matches")
	}
	if !strings.Contains(strings.ToLower(historicalResult.Matches[0].SectionHeading), "historical provenance") {
		t.Fatalf("historical top match = %+v, want historical provenance section when query asks for history", historicalResult.Matches[0])
	}
}

func TestHistoricalSectionRerankerDownranksHistoricalCandidates(t *testing.T) {
	t.Parallel()

	candidates := []stindex.SearchHit{
		{ChunkID: 2, Ref: "SPEC-LOCALITY", Heading: "Historical provenance", Score: 0.9},
		{ChunkID: 1, Ref: "SPEC-LOCALITY", Heading: "Requirements", Score: 0.8},
	}

	reranked, err := historicalSectionReranker{}.Rerank(context.Background(), "locality continuity semantics", candidates)
	if err != nil {
		t.Fatalf("Rerank() error = %v", err)
	}
	if got := reranked[0].Heading; got != "Requirements" {
		t.Fatalf("top reranked heading = %q, want active section ahead of historical provenance", got)
	}
	if !reflect.DeepEqual(reranked, []stindex.SearchHit{
		{ChunkID: 1, Ref: "SPEC-LOCALITY", Heading: "Requirements", Score: 0.8},
		{ChunkID: 2, Ref: "SPEC-LOCALITY", Heading: "Historical provenance", Score: 0.9},
	}) {
		t.Fatalf("reranked candidates = %+v", reranked)
	}
}

func TestSearchSpecsRejectsEmbedderFingerprintMismatch(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	db, err := sql.Open("sqlite3", cfg.Workspace.ResolvedIndexPath)
	if err != nil {
		t.Fatalf("sql.Open() error = %v", err)
	}
	if _, err := db.Exec(`UPDATE metadata SET value = ? WHERE key = 'embedder_fingerprint'`, "fixture|fixture-16d|plain_v1"); err != nil {
		t.Fatalf("update embedder_fingerprint: %v", err)
	}
	if err := db.Close(); err != nil {
		t.Fatalf("close writable db: %v", err)
	}

	_, err = SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err == nil {
		t.Fatal("SearchSpecs() error = nil, want fingerprint mismatch")
	}
	if got := err.Error(); !strings.Contains(got, "embedder fingerprint") || !strings.Contains(got, "pituitary index --rebuild") {
		t.Fatalf("SearchSpecs() error = %q, want fingerprint rebuild guidance", got)
	}
}

// #341: cfg.Runtime.Search.PrefilterDimension threads through the hybrid
// retrieval call site (loadRankedCandidatesContextWithFusion) and reaches
// stroma as SearchParams.SearchDimension. The fixture embedder is non-MRL,
// but stroma's API only requires SearchDimension <= stored embedder
// dimension on a float32 snapshot, so a value <= the fixture's 8 dims is
// accepted and the truncated-prefix path returns the same shape of result.
func TestSearchSpecsHonorsMatryoshkaPrefilterDimension(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	// Baseline: default zero leaves stroma at full stored dimension.
	baseline, err := SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs(prefilter=0) error = %v", err)
	}
	if len(baseline.Matches) == 0 {
		t.Fatal("baseline returned no matches; cannot validate threading")
	}

	// Truncated prefilter: must be threaded through and accepted.
	cfg.Runtime.Search.PrefilterDimension = 4
	truncated, err := SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("SearchSpecs(prefilter=4) error = %v", err)
	}
	if len(truncated.Matches) == 0 {
		t.Fatal("SearchSpecs(prefilter=4) returned no matches; expected non-empty truncated result")
	}
	if got, want := truncated.ScoreKind, SearchSpecScoreKindHybridRelevance; got != want {
		t.Fatalf("truncated.ScoreKind = %q, want %q (threading must not change score kind)", got, want)
	}
}

// #341: an oversized PrefilterDimension is rejected locally — before
// the query is embedded — so a config typo cannot burn an OpenAI-
// compatible round trip per failed search. The error must point at the
// config key so operators can fix it without reading stroma's source.
func TestSearchSpecsRejectsPrefilterDimensionAboveStored(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	// fixture-8d is 8 dimensions; 9 must be rejected before any query
	// embedding is computed.
	cfg.Runtime.Search.PrefilterDimension = 9
	_, err = SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err == nil {
		t.Fatal("SearchSpecs(prefilter > stored) error = nil, want preflight rejection")
	}
	if !strings.Contains(err.Error(), "runtime.search.matryoshka_prefilter_dimension") {
		t.Fatalf("SearchSpecs() error = %q, want preflight to name the config key", err.Error())
	}
	if !strings.Contains(err.Error(), "exceeds stored embedder_dimension") {
		t.Fatalf("SearchSpecs() error = %q, want preflight to name the stored embedder dimension", err.Error())
	}
}

// #341 preflight regression: the oversized-prefilter rejection must
// fire BEFORE any embedder method is invoked. The OpenAI-compatible
// embedder probes its dimension by issuing an EmbedQueries round trip
// when its cache is empty, so a config typo would otherwise burn one
// HTTP request per search call. We pin the order by pointing
// runtime.embedder at an httptest server that fails the test if any
// request lands — a real spy on the provider, not just a string match
// on the error message.
func TestSearchSpecsPrefilterPreflightSkipsEmbedder(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	// Spy on the embedder by serving an HTTP endpoint that fails the
	// test on any request. If the preflight ever regresses to running
	// after embedder.Dimension or any other provider call, the test
	// will fail here even before we look at the returned error.
	var calls atomic.Int64
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		calls.Add(1)
		t.Errorf("embedder must not be called when preflight rejects; got %s %s", r.Method, r.URL.Path)
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	cfg.Runtime.Embedder = config.RuntimeProvider{
		Provider:  config.RuntimeProviderOpenAI,
		Model:     "spy-embedder",
		Endpoint:  server.URL + "/v1",
		TimeoutMS: 1000,
	}
	cfg.Runtime.Search.PrefilterDimension = 9 // fixture-8d stores 8 dims

	_, err = SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err == nil {
		t.Fatal("SearchSpecs() error = nil, want preflight rejection")
	}
	// The preflight reads metadata.embedder_dimension from SQLite, not
	// from the embedder. The error must come from our local preflight,
	// not from stroma's post-embedding rejection downstream — this
	// guards the OpenAI-quota safety property from regressing into a
	// post-embed rejection that still burns a round trip.
	if !strings.Contains(err.Error(), "runtime.search.matryoshka_prefilter_dimension") {
		t.Fatalf("SearchSpecs() error = %q, want local preflight (not stroma) to reject the typo", err.Error())
	}
	if got := calls.Load(); got != 0 {
		t.Fatalf("embedder spy received %d request(s); want 0 — preflight regressed past the embedder", got)
	}
}

// #341 preflight regression: the preflight runs before newEmbedder is
// constructed, so even if the embedder's constructor itself made a
// network call (it does for unreachable endpoints), the typo check
// still lands first. We approximate this by pointing runtime.embedder
// at a non-existent endpoint and verifying the prefilter error wins.
func TestSearchSpecsPrefilterPreflightWinsOverEmbedderConstruction(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	cfg.Runtime.Search.PrefilterDimension = 9
	// Swap embedder to one that would make a remote call if reached.
	// The preflight must reject before the embedder is constructed or
	// invoked — even though this endpoint would otherwise burn a TCP
	// dial attempt or an HTTP probe for Dimension().
	cfg.Runtime.Embedder = config.RuntimeProvider{
		Provider:  config.RuntimeProviderOpenAI,
		Model:     "would-not-resolve",
		Endpoint:  "http://127.0.0.1:1/v1",
		TimeoutMS: 100,
	}

	_, err = SearchSpecs(cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err == nil {
		t.Fatal("SearchSpecs() error = nil, want preflight rejection")
	}
	if !strings.Contains(err.Error(), "runtime.search.matryoshka_prefilter_dimension") {
		t.Fatalf("SearchSpecs() error = %q, want preflight to reject before embedder construction/call", err.Error())
	}
}

// #341: the vector-only semantic similarity path is intentionally not
// threaded with PrefilterDimension because stroma v3's VectorSearchQuery
// does not expose SearchDimension. Setting the config must therefore
// neither error nor change the result of that path. This protects
// callers from surprise regressions and pins the documented behavior.
func TestSearchSpecsBySemanticSimilarityIgnoresPrefilterDimension(t *testing.T) {
	t.Parallel()

	cfg := loadFixtureConfig(t)
	records, err := source.LoadFromConfig(cfg)
	if err != nil {
		t.Fatalf("source.LoadFromConfig() error = %v", err)
	}
	if _, err := Rebuild(cfg, records); err != nil {
		t.Fatalf("Rebuild() error = %v", err)
	}

	baseline, err := SearchSpecsBySemanticSimilarityContext(context.Background(), cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("baseline semantic-similarity error = %v", err)
	}

	// Even an oversized prefilter (which stroma would reject on the
	// hybrid path) must be a no-op here because the vector-only path
	// never forwards it to stroma.
	cfg.Runtime.Search.PrefilterDimension = 9
	got, err := SearchSpecsBySemanticSimilarityContext(context.Background(), cfg, SearchSpecQuery{Query: "rate limiting", Limit: 5})
	if err != nil {
		t.Fatalf("semantic-similarity must ignore prefilter; got error = %v", err)
	}
	if len(got.Matches) != len(baseline.Matches) {
		t.Fatalf("semantic-similarity match count = %d, want %d (prefilter must not affect this path)", len(got.Matches), len(baseline.Matches))
	}
}

func mustWriteSearchFile(tb testing.TB, path, content string) {
	tb.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		tb.Fatalf("mkdir %s: %v", filepath.Dir(path), err)
	}
	if err := os.WriteFile(path, []byte(strings.TrimSpace(content)+"\n"), 0o644); err != nil {
		tb.Fatalf("write %s: %v", path, err)
	}
}
