"""vault keyword / FTS 搜索 · 状态查询。"""

from __future__ import annotations

from typing import Any

from niannian.vault.enhancements import (
    FEATURE_FTS,
    enhancement_active,
    vault_entry_count,
)
from niannian.vault.fts_index import VaultFtsIndex
from niannian.vault.store import VaultStore


def _match_needle(text: str, needle: str) -> bool:
    return needle in (text or "").lower()


class VaultSearchService:
    def __init__(self, store: VaultStore, *, config: dict[str, Any] | None = None) -> None:
        self._store = store
        self._config = config or {}
        self._fts = VaultFtsIndex(store._home)

    def _entry_count(self) -> int:
        return vault_entry_count(self._store.load_index())

    def _fts_active(self) -> bool:
        return enhancement_active(
            self._config,
            FEATURE_FTS,
            entry_count=self._entry_count(),
        )

    def _sync_fts_index(self) -> None:
        if self._fts_active():
            self._fts.rebuild_from_store(self._store)

    def index_document(self, *, vault_id: str, rel_path: str, namespace: str, title: str, body: str) -> None:
        if self._fts_active():
            self._fts.upsert(
                vault_id=vault_id,
                rel_path=rel_path,
                namespace=namespace,
                title=title,
                body=body,
            )

    def search(
        self,
        q: str,
        *,
        namespace: str = "user",
        limit: int = 20,
        cultural_batch_id: str | None = None,
    ) -> list[dict[str, Any]]:
        if cultural_batch_id:
            return self._store.find_by_frontmatter(
                namespace=namespace,
                key="cultural_batch_id",
                value=cultural_batch_id,
                limit=limit,
            )
        needle = (q or "").strip().lower()
        if not needle:
            return []

        if self._fts_active():
            hits = self._fts.search(needle, namespace=namespace, limit=limit)
            if hits:
                return self._enrich_hits(hits, namespace=namespace)
            self._sync_fts_index()
            hits = self._fts.search(needle, namespace=namespace, limit=limit)
            if hits:
                return self._enrich_hits(hits, namespace=namespace)

        return self._keyword_search(needle, namespace=namespace, limit=limit)

    def _enrich_hits(self, hits: list[dict[str, Any]], *, namespace: str) -> list[dict[str, Any]]:
        out: list[dict[str, Any]] = []
        for h in hits:
            entry = self._store.find_entry(str(h["vault_id"]))
            fm: dict[str, Any] = {}
            if entry:
                try:
                    doc = self._store.read_document(str(entry["rel_path"]))
                    fm = doc.get("frontmatter") or {}
                except (FileNotFoundError, OSError):
                    pass
            out.append(
                {
                    "vault_id": h.get("vault_id"),
                    "rel_path": h.get("rel_path") or (entry or {}).get("rel_path"),
                    "title": h.get("title") or (entry or {}).get("title"),
                    "category": (entry or {}).get("category") or fm.get("category"),
                    "subproject": (entry or {}).get("subproject") or fm.get("subproject"),
                    "snippet": (h.get("snippet") or "")[:120],
                    "score": h.get("score"),
                    "score_verdict": fm.get("score_verdict"),
                }
            )
            if len(out) >= len(hits):
                pass
        return out

    def _keyword_search(self, needle: str, *, namespace: str, limit: int) -> list[dict[str, Any]]:
        ns = namespace if namespace in ("user", "niannian") else "user"
        hits: list[dict[str, Any]] = []
        for entry in self._store.load_index().get("entries") or []:
            if not isinstance(entry, dict):
                continue
            if str(entry.get("namespace") or "") != ns:
                continue
            rel = str(entry.get("rel_path") or "")
            title = str(entry.get("title") or "")
            cat = str(entry.get("category") or "")
            sub = str(entry.get("subproject") or "")
            snippet = ""
            try:
                doc = self._store.read_document(rel)
                body = doc.get("body") or ""
                snippet = body[:160].replace("\n", " ").strip()
                fm = doc.get("frontmatter") or {}
            except (FileNotFoundError, OSError):
                fm = {}
            haystack = " ".join([title, cat, sub, snippet, str(fm.get("source_url") or "")]).lower()
            if not _match_needle(haystack, needle):
                continue
            hits.append(
                {
                    "vault_id": entry.get("vault_id"),
                    "rel_path": rel,
                    "title": title,
                    "category": cat,
                    "subproject": sub,
                    "snippet": snippet[:120],
                    "score": fm.get("score"),
                    "score_verdict": fm.get("score_verdict"),
                }
            )
            if len(hits) >= limit:
                break
        return hits

    def status(
        self,
        *,
        vault_id: str | None = None,
        atom_id: str | None = None,
    ) -> dict[str, Any]:
        entry: dict[str, Any] | None = None
        if vault_id:
            entry = self._store.find_entry(vault_id)
        elif atom_id:
            for row in self._store.load_index().get("entries") or []:
                if isinstance(row, dict) and str(row.get("atom_id") or "") == atom_id:
                    entry = row
                    break
        if entry is None:
            raise FileNotFoundError("vault_entry_not_found")
        rel = str(entry["rel_path"])
        doc = self._store.read_document(rel)
        fm = dict(doc.get("frontmatter") or {})
        return {
            "vault_id": entry.get("vault_id"),
            "atom_id": entry.get("atom_id") or fm.get("atom_id"),
            "rel_path": rel,
            "namespace": fm.get("namespace") or entry.get("namespace"),
            "title": fm.get("title") or entry.get("title"),
            "score": fm.get("score"),
            "score_verdict": fm.get("score_verdict"),
            "taxonomy_status": fm.get("taxonomy_status"),
            "cultural_batch_id": fm.get("cultural_batch_id"),
        }
