"""Unified cross-source search — queries all available data sources.

Searches across:
- Local FTS5 index (Bibles, commentaries, lexicons)
- bolls.life v2/find API (137 translations)
- Sefaria (Jewish canon: Talmud, Midrash, commentaries)
- Scaife/Perseus (classical and patristic texts)
- DOAJ (open-access theology journal articles)

Results are merged, deduplicated, and ranked.
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Result key name (shared by all sources)
# ---------------------------------------------------------------------------

SOURCE_ACCORDANCE = "accordance"
SOURCE_BOLLS = "bolls"
SOURCE_SEFARIA = "sefaria"
SOURCE_SCAIFE = "scaife"
SOURCE_DOAJ = "doaj"


def _empty_result(source: str) -> List[Dict[str, Any]]:
    return []


# ---------------------------------------------------------------------------
# UnifiedSearcher
# ---------------------------------------------------------------------------


class UnifiedSearcher:
    """Cross-source search orchestrator.

    Queries all available data sources sequentially, handles each source's
    failure independently, and returns merged/deduplicated results.

    Usage::

        >>> from accordance_parser.unified_search import unified
        >>> results = unified.search_all("baptism")
    """

    def search_all(self, query: str, max_results: int = 30) -> List[Dict[str, Any]]:
        """Query ALL available sources sequentially.

        Each source is queried independently; failures in one source do not
        affect the others.  Results are merged, deduplicated on (source, title),
        and returned as a single ranked list.

        Args:
            query: The search query string.
            max_results: Per-source cap.  The merged list may be larger.

        Returns:
            List of dicts with keys: ``source``, ``title``, ``snippet``,
            ``reference``, ``url`` (optional).
        """
        all_results: List[Dict[str, Any]] = []

        # 1. Local FTS5 index
        try:
            from .search_index import SearchIndex  # lazy import

            idx = SearchIndex()
            rows = idx.search(query, max_results=max_results, snippet=True)
            for row in rows:
                source_type, source_name, source_id, snippet, citation, rank = row
                all_results.append(
                    {
                        "source": SOURCE_ACCORDANCE,
                        "title": source_name,
                        "snippet": _strip_marks(snippet),
                        "reference": citation or "",
                        "rank": rank,
                    }
                )
        except Exception as e:
            logger.warning("FTS5 local search failed: %s", e)

        # 2. bolls.life
        try:
            from .bible_api import search_bolls  # lazy import

            resp = search_bolls(query, page_size=max_results)
            results_raw = resp.get("results", []) if isinstance(resp, dict) else []
            for r in results_raw:
                ref = _bolls_ref(r)
                all_results.append(
                    {
                        "source": SOURCE_BOLLS,
                        "title": f"{r.get('translation', '')} — {r.get('book', '')} {r.get('chapter', '')}:{r.get('verse', '')}",
                        "snippet": r.get("text", ""),
                        "reference": ref,
                        "url": f"https://bolls.life/{r.get('translation', '').lower()}/{r.get('book', '').replace(' ', '%20')}/{r.get('chapter', 0)}/{r.get('verse', 0)}/",
                    }
                )
        except Exception as e:
            logger.warning("bolls.life search failed: %s", e)

        # 3. Sefaria
        try:
            from .sefaria_api import sefaria  # lazy import

            hits = sefaria.search(query, max_results=max_results)
            for h in hits:
                src = h.get("_source", {})
                title_ref = src.get("titleVariants", [{}])[0].get("text", "")
                if not title_ref:
                    title_ref = src.get("book", "") or src.get("title", "") or ""
                all_results.append(
                    {
                        "source": SOURCE_SEFARIA,
                        "title": title_ref,
                        "snippet": src.get("content", "") or src.get("text", ""),
                        "reference": src.get("ref", ""),
                        "url": f"https://www.sefaria.org/{src.get('ref', '')}",
                    }
                )
        except Exception as e:
            logger.warning("Sefaria search failed: %s", e)

        # 4. Scaife/Perseus
        try:
            from .scaife_api import scaife  # lazy import

            works = scaife.search_by_author(query)
            for w in works:
                group = w.get("group", "")
                work = w.get("work", "")
                wurn = w.get("work_urn", "")
                all_results.append(
                    {
                        "source": SOURCE_SCAIFE,
                        "title": f"{group} — {work}",
                        "snippet": w.get("description", ""),
                        "reference": wurn,
                        "url": f"https://scaife.perseus.org/reader/{wurn}/"
                        if wurn
                        else "",
                    }
                )
        except Exception as e:
            logger.warning("Scaife search failed: %s", e)

        # 5. DOAJ
        try:
            from .doaj_api import doaj  # lazy import

            articles = doaj.search_articles(query, max_results=max_results)
            for a in articles:
                bibjson = a.get("bibjson", {})
                title = bibjson.get("title", "")
                abstract = bibjson.get("abstract", "")
                # Build author string
                authors = bibjson.get("author", [])
                if isinstance(authors, list) and authors:
                    author_str = ", ".join(
                        a_.get("name", "") for a_ in authors if isinstance(a_, dict)
                    )
                else:
                    author_str = ""
                journal = bibjson.get("journal", {})
                if isinstance(journal, dict):
                    journal_title = journal.get("title", "")
                else:
                    journal_title = ""
                # Best URL
                links = bibjson.get("link", [])
                link_url = ""
                if isinstance(links, list) and links:
                    for lk in links:
                        if isinstance(lk, dict):
                            ltype = lk.get("type", "")
                            if ltype == "fulltext" or ltype == "full-text":
                                link_url = lk.get("url", "")
                                break
                            if not link_url:
                                link_url = lk.get("url", "")
                if not link_url:
                    link_url = f"https://doaj.org/article/{a.get('id', '')}"

                all_results.append(
                    {
                        "source": SOURCE_DOAJ,
                        "title": title or "(untitled)",
                        "snippet": abstract or "",
                        "reference": f"{author_str} — {journal_title}",
                        "url": link_url,
                    }
                )
        except Exception as e:
            logger.warning("DOAJ search failed: %s", e)

        # Deduplicate and sort
        seen: set = set()
        unique: List[Dict[str, Any]] = []
        for r in all_results:
            key = (
                r.get("source"),
                (r.get("title") or "").strip().lower()[:100],
                (r.get("snippet") or "").strip()[:80],
            )
            if key not in seen:
                seen.add(key)
                unique.append(r)

        # Sort: Accordance results first (they have rank), then others
        unique.sort(key=_sort_key)
        return unique

    # ------------------------------------------------------------------
    # search_by_category
    # ------------------------------------------------------------------

    _CATEGORY_SOURCE_MAP: Dict[str, List[str]] = {
        "bible": [SOURCE_ACCORDANCE, SOURCE_BOLLS],
        "commentary": [SOURCE_ACCORDANCE],
        "lexicon": [SOURCE_ACCORDANCE],
        "rabbinic": [SOURCE_SEFARIA],
        "classical": [SOURCE_SCAIFE],
        "academic": [SOURCE_DOAJ],
    }

    def search_by_category(
        self, query: str, category: str, max_results: int = 20
    ) -> List[Dict[str, Any]]:
        """Search, returning only results matching *category*.

        Args:
            query: The search query string.
            category: One of ``"bible"``, ``"commentary"``, ``"lexicon"``,
                      ``"rabbinic"``, ``"classical"``, ``"academic"``.
            max_results: Per-source cap.

        Returns:
            Filtered list of result dicts.
        """
        cat_lower = category.strip().lower()
        allowed_sources = self._CATEGORY_SOURCE_MAP.get(cat_lower)
        if allowed_sources is None:
            raise ValueError(
                f"Unknown category: {category!r}. "
                f"Valid: {sorted(self._CATEGORY_SOURCE_MAP.keys())}"
            )

        all_results = self.search_all(query, max_results=max_results)
        filtered = [r for r in all_results if r.get("source") in allowed_sources]
        return filtered[:max_results]


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _strip_marks(snippet: str) -> str:
    """Remove FTS5 highlight markers (``<mark>`` / ``</mark>``) from snippets."""
    return snippet.replace("<mark>", "").replace("</mark>", "")


def _bolls_ref(result: dict) -> str:
    """Build a human-readable reference from a bolls.life result dict."""
    book = result.get("book", "")
    chapter = result.get("chapter", 0)
    verse = result.get("verse", 0)
    return f"{book} {chapter}:{verse}" if book else ""


def _sort_key(result: Dict[str, Any]) -> tuple:
    """Sort key: Accordance first (by rank), then bolls, then others."""
    source_priority = {
        SOURCE_ACCORDANCE: 0,
        SOURCE_BOLLS: 1,
        SOURCE_SEFARIA: 2,
        SOURCE_SCAIFE: 3,
        SOURCE_DOAJ: 4,
    }
    source = result.get("source", "")
    rank = (
        result.get("rank", float("inf"))
        if source == SOURCE_ACCORDANCE
        else float("inf")
    )
    return (source_priority.get(source, 99), rank)


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

unified = UnifiedSearcher()


# ---------------------------------------------------------------------------
# MCP tool snippet
# ---------------------------------------------------------------------------

"""
@mcp.tool()
def search_all_sources(query: str, max_results: int = 30, category: str = "all") -> list:
    \\\"\\\"\\\"Search across ALL available sources: Bibles, commentaries, lexicons,
    Sefaria (rabbinic), Scaife (classical), and DOAJ (academic journals).\\\"\\\"\\\"
    from accordance_parser.unified_search import unified
    if category != "all":
        return unified.search_by_category(query, category, max_results)
    return unified.search_all(query, max_results)
"""
