"""
Catena Aurea module — Thomas Aquinas's patristic commentary on the four Gospels.

Loads pre-built JSON data (``data/catena_aurea.json``) and exposes the same
public API as ``CommentaryModule`` so that existing MCP tools and the search
index can consume it transparently.

Data structure (per gospel)::

    {
      "Matthew": {
        "1:1":    [{"latin_author": "Jerome", "latin_text": "...",
                     "english_author": "Jerome", "english_text": "..."}, ...],
        "1:3-6":  [...],
        ...
      },
      "Mark":    {...},
      "Luke":    {...},
      "John":    {...}
    }
"""

from __future__ import annotations

import json
import re
from pathlib import Path

from .canon import resolve_book, osis_to_spaceless


# ---------------------------------------------------------------------------
# Reference parsing (inline, avoids importing CommentaryModule)
# ---------------------------------------------------------------------------


def _parse_ref(reference: str) -> tuple[str, int, int] | None:
    """Parse a reference like 'Matthew 1:1' → (spaceless_book, chapter, verse)."""
    ref = reference.strip()
    # Try "Book Ch:V" pattern
    m = re.match(r"^([A-Za-z0-9 ]+?)\s+(\d+):(\d+)$", ref)
    if m:
        book_raw = m.group(1).strip()
        chapter = int(m.group(2))
        verse = int(m.group(3))
        book_obj = resolve_book(book_raw)
        if book_obj is None:
            return None
        return (osis_to_spaceless(book_obj.osis), chapter, verse)
    # Try "Book Ch" (chapter only)
    m = re.match(r"^([A-Za-z0-9 ]+?)\s+(\d+)$", ref)
    if m:
        book_raw = m.group(1).strip()
        chapter = int(m.group(2))
        book_obj = resolve_book(book_raw)
        if book_obj is None:
            return None
        return (osis_to_spaceless(book_obj.osis), chapter, 1)
    return None


# ---------------------------------------------------------------------------
# CatenaAureaModule
# ---------------------------------------------------------------------------


class CatenaAureaModule:
    """A single-gospel Catena Aurea commentary module.

    CatenaAureaModule instances are created per gospel (e.g. "Matthew").
    The data is loaded lazily from ``data/catena_aurea.json`` on first access.

    Public API mirrors CommentaryModule:
      - ``get_section(ref)`` / ``get_section_latin(ref)`` — formatted text
      - ``list_books()`` — one book with total entry count
      - ``list_chapter_sections(book, ch)`` — sections in a chapter
      - ``format`` → ``"catena_aurea"``
      - ``title`` → ``"Catena Aurea"``
      - ``abbrev`` → ``"CA"``
      - ``_entries`` → synthetic list for search index compatibility
    """

    _DATA: dict | None = None  # shared lazy-loaded JSON, keyed by gospel name

    def __init__(self, gospel: str, abbrev: str | None = None):
        """*gospel* is the gospel name as used in the JSON keys: ``"Matthew"``.

        *abbrev* is the module abbreviation used in the MCP server
        (e.g. ``"CA-Matthew"``).
        """
        self._gospel = gospel
        self._abbrev = abbrev or f"CA-{gospel}"
        self._title = "Catena Aurea"
        self._format = "catena_aurea"
        self._entries: list[tuple] = []
        self._entries_built = False
        # Stubs for search index compatibility
        self.filepath = Path(__file__).parent.parent / "data" / "catena_aurea.json"
        self.size = 0
        self.mm = None  # search index should use catena_aurea-specific path
        self._rr_count = 0
        self._rt_count = 0
        # Build entries eagerly on init for search index compatibility
        self._build_entries()

    # ---- lazy loading ----

    @classmethod
    def _load_data(cls) -> dict:
        """Load the JSON once per process and cache at class level."""
        if cls._DATA is not None:
            return cls._DATA
        data_path = Path(__file__).parent.parent / "data" / "catena_aurea.json"
        if not data_path.exists():
            raise FileNotFoundError(
                f"Catena Aurea data file not found: {data_path}\n"
                f"Run: python3 scripts/build_catena_aurea.py"
            )
        with open(data_path, "r", encoding="utf-8") as f:
            cls._DATA = json.load(f)
        return cls._DATA

    @property
    def _gospel_data(self) -> dict:
        """Return the per-gospel dict::

        {"1:1": [entries], "1:3-6": [entries], ...}
        """
        data = self._load_data()
        if self._gospel not in data:
            raise KeyError(f"Gospel '{self._gospel}' not found in Catena Aurea data")
        return data[self._gospel]

    # ---- entry list builder (for _entries and search index) ----

    def _build_entries(self) -> None:
        """Build synthetic _entries list: [(book, chapter, vs_start, vs_end, idx)].**

        Each entry is a 5-tuple compatible with CommentaryModule._entries.
        The ``idx`` is a synthetic ID (0-based) into the entry list for
        the given verse citation key.
        """
        if self._entries_built:
            return
        self._entries_built = True
        self._entries = []

        gospel_data = self._gospel_data

        # Build the synthetic list
        idx = 0
        for key, entries in sorted(gospel_data.items()):
            # Parse key: "1:1" or "1:3-6"
            m = re.match(r"^(\d+):(\d+)(?:-(\d+))?$", key)
            if not m:
                continue
            chapter = int(m.group(1))
            vs_start = int(m.group(2))
            vs_end = int(m.group(3)) if m.group(3) else vs_start

            for entry_idx in range(len(entries)):
                book_spaceless = (
                    osis_to_spaceless(resolve_book(self._gospel).osis)
                    if resolve_book(self._gospel)
                    else self._gospel
                )
                self._entries.append((book_spaceless, chapter, vs_start, vs_end, idx))
                idx += 1

    @property
    def _entries_prop(self) -> list[tuple]:
        """Lazy accessor for _entries (for search index compatibility)."""
        self._build_entries()
        return self._entries

    # ---- properties ----

    @property
    def format(self) -> str:
        """Return the module format string (``"catena_aurea"``)."""
        return self._format

    @property
    def title(self) -> str:
        """Return the module title."""
        return self._title

    @property
    def abbrev(self) -> str:
        """Return the module abbreviation."""
        return self._abbrev

    # ---- public API ----

    def get_section(self, reference: str) -> str:
        """Return formatted English commentary for a Bible reference.

        *reference* should be something like ``"Matthew 1:1"``.

        Returns a string with a citation header followed by commentary entries
        separated by ``---``, each with author attribution.  When the reference
        falls within a pericope-level key (e.g. ``"John 3:5"`` matched by the
        ``"3:1"`` key covering 3:1-21), the header clarifies what verse range
        is actually shown.
        """
        result = self._lookup_with_meta(reference)
        if result is None:
            return f"[No Catena Aurea commentary found for {reference}]"

        entries, matched_key, match_type = result
        header = self._build_header(reference, matched_key, match_type)

        parts = []
        for entry in entries:
            author = entry.get("english_author", "").strip()
            text = entry.get("english_text", "").strip()
            if not text:
                continue
            if author:
                parts.append(f"{author}:\n{text}")
            else:
                parts.append(text)

        if not parts:
            return f"[No Catena Aurea commentary found for {reference}]"

        return header + "\n\n---\n\n".join(parts)

    def get_section_latin(self, reference: str) -> str:
        """Return formatted Latin commentary for a Bible reference."""
        result = self._lookup_with_meta(reference)
        if result is None:
            return f"[No Catena Aurea Latin commentary found for {reference}]"

        entries, matched_key, match_type = result
        header = self._build_header(reference, matched_key, match_type)

        parts = []
        for entry in entries:
            author = entry.get("latin_author", "").strip()
            text = entry.get("latin_text", "").strip()
            if not text:
                continue
            if author:
                parts.append(f"{author}:\n{text}")
            else:
                parts.append(text)

        if not parts:
            return f"[No Catena Aurea Latin commentary found for {reference}]"

        return header + "\n\n---\n\n".join(parts)

    def _build_header(self, requested: str, matched_key: str, match_type: str) -> str:
        """Build a citation header for a commentary section.

        Examples:
            Exact:   ``Catena Aurea — Matthew 1:1``
            Range:   ``Catena Aurea — Matthew 3:1-6 (covers Matthew 3:3)``
            Chapter: ``Catena Aurea — Matthew 5:1 (nearest to Matthew 5:3)``
        """
        # Convert matched_key like "3:1-6" to "Matthew 3:1-6"
        if "-" in matched_key:
            verse_ref = (
                f"{self._gospel} {matched_key.replace(':', ':').replace('-', '-')}"
            )
        else:
            verse_ref = f"{self._gospel} {matched_key}"

        # Strip gospel name from requested if present (e.g. "John 3:5" → "3:5")
        short_req = requested
        if " " in short_req:
            short_req = short_req.split(" ", 1)[1]

        if match_type == "exact":
            return f"# Catena Aurea — {verse_ref}\n\n"
        elif match_type == "range":
            return (
                f"# Catena Aurea — {verse_ref}\n"
                f"> *(covers {self._gospel} {short_req})*\n\n"
            )
        else:  # chapter_fallback
            return (
                f"# Catena Aurea — {verse_ref}\n"
                f"> *(nearest entry to {self._gospel} {short_req})*\n\n"
            )

    def list_books(self) -> list:
        """Return list of ``(book, entry_count)`` tuples."""
        gospel_data = self._gospel_data
        total = sum(len(v) for v in gospel_data.values())
        return [(self._gospel, total)]

    def list_chapter_sections(self, book: str, chapter: int) -> list[dict]:
        """Return all section entries for a specific book and chapter.

        Returns a list of dicts with keys:
          - ref: verse reference string
          - verse_start: int
          - verse_end: int
          - heading: first ~120 chars of English commentary text
        """
        # Normalize book
        book_obj = resolve_book(book)
        if book_obj is None:
            return []
        if book_obj.name != self._gospel:
            return []

        gospel_data = self._gospel_data
        result = []

        for key, entries in sorted(gospel_data.items()):
            m = re.match(r"^(\d+):(\d+)(?:-(\d+))?$", key)
            if not m:
                continue
            ch = int(m.group(1))
            if ch != chapter:
                continue
            vs_start = int(m.group(2))
            vs_end = int(m.group(3)) if m.group(3) else vs_start

            # Build heading from first entry's English text
            heading = None
            if entries:
                text = entries[0].get("english_text", "").strip()
                if text:
                    heading = text[:120]

            if vs_start == vs_end:
                ref = f"{self._gospel} {ch}:{vs_start}"
            else:
                ref = f"{self._gospel} {ch}:{vs_start}-{vs_end}"

            result.append(
                {
                    "ref": ref,
                    "verse_start": vs_start,
                    "verse_end": vs_end,
                    "heading": heading,
                }
            )

        return result

    @property
    def skipped(self) -> bool:
        """Return False — Catena Aurea is never skipped."""
        return False

    def close(self) -> None:
        """No-op — Catena Aurea has no file handles to close."""
        pass

    # ---- internal lookup ----

    def _lookup_with_meta(self, reference: str) -> tuple[list[dict], str, str] | None:
        """Resolve a reference and return (entries, matched_key, match_type).

        *match_type* is one of ``"exact"``, ``"range"``, or ``"chapter_fallback"``.
        *matched_key* is the JSON data key (e.g. ``"3:1-6"``, ``"3:1"``).
        Returns ``None`` if no match at all.
        """
        parsed = _parse_ref(reference)
        if parsed is None:
            return None
        book_spaceless, chapter, verse = parsed

        # Verify book matches this gospel
        book_obj = resolve_book(book_spaceless.replace(" ", ""))
        if book_obj is None or book_obj.name != self._gospel:
            return None

        gospel_data = self._gospel_data

        # Try exact verse match
        key = f"{chapter}:{verse}"
        if key in gospel_data:
            return (gospel_data[key], key, "exact")

        # Try range matches (e.g. verse 4 falls within key "3-6")
        for key, entries in gospel_data.items():
            m = re.match(r"^(\d+):(\d+)-(\d+)$", key)
            if not m:
                continue
            ch = int(m.group(1))
            if ch != chapter:
                continue
            vs_start = int(m.group(2))
            vs_end = int(m.group(3))
            if vs_start <= verse <= vs_end:
                return (entries, key, "range")

        # Try any verse in chapter (first match)
        for key in sorted(gospel_data.keys()):
            m = re.match(r"^(\d+):", key)
            if m and int(m.group(1)) == chapter:
                return (gospel_data[key], key, "chapter_fallback")

        return None

    # ---- search index support ----

    def _get_entry_text(
        self, entry: tuple
    ) -> tuple[str, int, int, int, str, str, str, str]:
        """Return (book, chapter, vs_start, vs_end, english_text, latin_text,
        english_author, latin_author) for a synthetic entry tuple.

        The *entry* is one of the synthetic (book, ch, vs_s, vs_e, idx) tuples
        from ``_entries``.
        """
        book, ch, vs_s, vs_e, idx = entry
        self._build_entries()

        # Find the entry in the data by walking through
        gospel_data = self._gospel_data
        current_idx = 0
        for key, entries in sorted(gospel_data.items()):
            m = re.match(r"^(\d+):(\d+)(?:-(\d+))?$", key)
            if not m:
                continue
            entry_ch = int(m.group(1))
            entry_vs = int(m.group(2))
            entry_ve = int(m.group(3)) if m.group(3) else entry_vs

            if entry_ch != ch or entry_vs != vs_s or entry_ve != vs_e:
                current_idx += len(entries)
                continue

            # Found the right key
            local_idx = idx - current_idx
            if 0 <= local_idx < len(entries):
                ent = entries[local_idx]
                return (
                    book,
                    ch,
                    vs_s,
                    vs_e,
                    ent.get("english_text", ""),
                    ent.get("latin_text", ""),
                    ent.get("english_author", ""),
                    ent.get("latin_author", ""),
                )

            current_idx += len(entries)

        return (book, ch, vs_s, vs_e, "", "", "", "")

    def _get_index_text(self, entry: tuple) -> str:
        """Return combined English and Latin commentary text for FTS5 indexing.

        Used by the search index to get pre-parsed text instead of raw bytes.
        Both languages are included so FTS5 matches Latin words (e.g.,
        "generationis") as well as English words.
        """
        _, _, _, _, english_text, latin_text, english_author, latin_author = (
            self._get_entry_text(entry)
        )

        parts = []
        if english_text:
            prefix = f"{english_author}: " if english_author else ""
            parts.append(f"{prefix}{english_text}")
        if latin_text:
            prefix = f"{latin_author}: " if latin_author else ""
            parts.append(f"{prefix}{latin_text}")

        return " | ".join(parts)
