"""Session 76: Type-6 Generic Tools + .acc2 Module Integration.

Nine scenarios exercising type-6 tools (grammars, maps), English dictionaries,
text-critical apparatus, Spanish ministry, .acc2 harmonies, comprehensive
cross-references, OT-in-NT quotations, Qumran/Philo/Josephus cross-references,
and apparatus comparison (Comma Johanneum).

Each scenario maps to 1-3 test methods. Tests gracefully skip when
dependencies are unavailable.
"""

from __future__ import annotations

import pytest


# =============================================================================
# IMPORT GUARDS
# =============================================================================

# --- Bible API (network-dependent) ---
try:
    from accordance_parser.bible_api import get_verse
    _has_bible_api = True
except Exception:
    _has_bible_api = False

# --- MCP server tools ---
_has_mcp_server = False
_mcp_server_import_error = ""
try:
    from accordance_mcp_server import (
        list_tools, browse_tool, get_tool_section,
        list_commentaries, get_commentary_text, search_commentaries,
        search_commentary_text,
        search_dictionary_content, get_dictionary_entry, compare_translations, get_verse_text, get_nabre_verse,
        find_cross_references, list_acc2_modules,
    )
    _has_mcp_server = True
except Exception as e:
    _mcp_server_import_error = str(e)

# --- acc2 tools (standalone) ---
try:
    from accordance_services.tools.acc2 import (
        find_parallel_passages, find_ot_quotations,
    )
    _has_acc2_tools = True
except Exception:
    _has_acc2_tools = False

# --- Network detection ---
_has_network = False
if _has_bible_api:
    try:
        r = get_verse("Genesis 1:1", "KJV")
        if r and r.text:
            _has_network = True
    except Exception:
        pass


# =============================================================================
# HELPERS
# =============================================================================

def _no_traceback(text: str) -> bool:
    """Ensure the text does not contain a Python traceback."""
    return "Traceback (most recent call last)" not in text


def _str_ok(val) -> bool:
    """Return True if val is a non-empty string with no traceback."""
    return isinstance(val, str) and len(val.strip()) > 0 and _no_traceback(val)


def _mcp_commentaries_loaded() -> bool:
    """Return True if MCP server's _commentary_modules dict is populated."""
    if not _has_mcp_server:
        return False
    try:
        import accordance_mcp_server as _srv
        cm = getattr(_srv, "_commentary_modules", None)
        return isinstance(cm, dict) and len(cm) > 0
    except Exception:
        return False


def _mcp_lexicons_loaded() -> bool:
    """Return True if MCP server's _lexicon_modules dict is populated."""
    if not _has_mcp_server:
        return False
    try:
        import accordance_mcp_server as _srv
        lm = getattr(_srv, "_lexicon_modules", None)
        return isinstance(lm, dict) and len(lm) > 0
    except Exception:
        return False


# =============================================================================
# S1: Type 6 Grammar Tool for Homily Greek Study
# =============================================================================


@pytest.mark.skipif(not _has_mcp_server, reason="MCP server not importable")
class TestS1Type6GrammarTools:
    """S1: Verify type-6 generic tools: list_tools, browse_tool, get_tool_section."""

    def test_list_tools_returns_nonempty(self):
        """list_tools() returns at least one type-6 module."""
        result = list_tools()
        assert _str_ok(result)
        assert len(result) > 0

    def test_list_tools_includes_grammar_or_homiletic(self):
        """list_tools() output includes recognizable type-6 grammar/homiletic names."""
        result = list_tools()
        assert _str_ok(result)
        indicators = ["Grammar", "Greek", "Guide", "Maps", "Timeline",
                      "Classic", "Parables", "Archaeology", "Outlines"]
        found = [name for name in indicators if name in result]
        assert len(found) >= 2, (
            f"Expected >= 2 type-6 indicators, found {found}. Output: {result[:300]}"
        )

    def _first_tool_abbrev(self) -> str | None:
        """Return the first tool abbreviation from list_tools()."""
        out = list_tools()
        if not out.strip():
            return None
        return out.strip().split("\n")[0][:25].strip() or None

    def test_browse_tool_lists_sections(self):
        """browse_tool() for first type-6 module returns section markers."""
        abbr = self._first_tool_abbrev()
        if abbr is None:
            pytest.skip("No type-6 tools available")
        result = browse_tool(abbr)
        assert _str_ok(result), f"browse_tool('{abbr}') failed: {result[:200]}"
        assert any(c in result for c in ("\u00a7", "\u00b6", "section", "1:")), (
            f"No section markers in browse_tool('{abbr}'): {result[:200]}"
        )

    def test_get_tool_section_returns_content(self):
        """get_tool_section(module, 1) returns >= 40 chars."""
        abbr = self._first_tool_abbrev()
        if abbr is None:
            pytest.skip("No type-6 tools available")
        result = get_tool_section(abbr, 1)
        assert _str_ok(result), f"get_tool_section('{abbr}',1) failed: {result[:200]}"
        assert len(result) >= 40, (
            f"Expected >=40 chars, got {len(result)}: {result[:200]}"
        )


# =============================================================================
# S2: English Dictionary Topical Research
# =============================================================================


@pytest.mark.skipif(not _mcp_lexicons_loaded(), reason="Lexicon modules not loaded")
class TestS2EnglishDictionaryResearch:
    """S2: Easton/ISBE/Nave's — search_dictionary_content and get_dictionary_entry."""

    def test_search_covenant_english_returns_results(self):
        """search_dictionary_content('covenant', language='english') returns results."""
        result = search_dictionary_content("covenant", language="english", max_results=10)
        assert _str_ok(result)
        assert "covenant" in result.lower() or "Covenant" in result

    def test_get_easton_covenant_returns_text(self):
        """get_dictionary_entry('Easton', 'Covenant') returns entry text."""
        result = get_dictionary_entry("Easton", "Covenant")
        assert _str_ok(result), f"Easton Covenant lookup failed: {result[:200]}"

    def test_get_isbe_covenant_returns_text(self):
        """get_dictionary_entry('ISBE', 'Covenant') returns entry text."""
        result = get_dictionary_entry("ISBE", "Covenant")
        assert _str_ok(result), f"ISBE Covenant lookup failed: {result[:200]}"


# =============================================================================
# S3: Text-Critical Apparatus Modules
# =============================================================================


@pytest.mark.skipif(not _mcp_commentaries_loaded(), reason="Commentary modules not loaded")
class TestS3ApparatusModules:
    """S3: NA28 Apparatus and LXX Apparatus in commentary module list."""

    def test_apparatus_in_commentary_list(self):
        """list_commentaries() includes NA28 Apparatus or LXX Apparatus."""
        result = list_commentaries()
        assert _str_ok(result)
        assert ("NA28 Apparatus" in result or "LXX Apparatus" in result
                or "MT-ETCBC-A" in result), (
            f"No apparatus modules in commentary list: {result[:300]}"
        )

    def test_get_na28_apparatus_verse(self):
        """get_commentary_text on NA28 Apparatus for Matthew 1:1 returns content."""
        result = get_commentary_text("NA28 Apparatus", "Matthew 1:1")
        assert _str_ok(result), f"NA28 Apparatus Matt 1:1 failed: {result[:200]}"

    def test_search_commentaries_includes_apparatus(self):
        """search_commentaries('Matthew 1:1') returns apparatus content."""
        result = search_commentaries("Matthew 1:1")
        assert _str_ok(result)
        lower = result.lower()
        assert any(w in lower for w in ("apparatus", "na28", "na 28", "lxx")), (
            f"search_commentaries missing apparatus: {result[:300]}"
        )

    def test_search_commentary_text_apparatus(self):
        """search_commentary_text in commentaries returns results for apparatus query."""
        result = search_commentary_text("manuscript", max_results=10)
        assert _str_ok(result), f"search_commentary_text('manuscript') failed: {result[:200]}"


# =============================================================================
# S4: Spanish Language Ministry
# =============================================================================


@pytest.mark.skipif(not _mcp_commentaries_loaded(), reason="Commentary modules not loaded")
class TestS4SpanishLanguageMinistry:
    """S4: LBLA Notes and NBLH Notes discoverability and access."""

    def test_lbla_notes_in_commentary_list(self):
        """list_commentaries() includes LBLA Notes or LBLA-related module."""
        result = list_commentaries()
        assert _str_ok(result)
        assert "LBLA" in result or "NBLH" in result, (
            f"Missing Spanish modules: {result[:300]}"
        )

    def test_lbla_notes_john_6_53(self):
        """get_commentary_text('LBLA Notes', 'John 6:53') returns text."""
        if "LBLA Notes" not in list_commentaries():
            pytest.skip("LBLA Notes not installed")
        result = get_commentary_text("LBLA Notes", "John 6:53")
        assert _str_ok(result), f"LBLA Notes John 6:53 failed: {result[:200]}"

    def test_compare_translations_spanish(self):
        """compare_translations includes LBLA and NBLH in comparison set."""
        if not _has_network:
            pytest.skip("Network not available")
        result = compare_translations("John 6:53", translations="KJV,NASB,LBLA,NBLH")
        assert _str_ok(result), f"compare_trans Spanish failed: {result[:200]}"
        assert "LBLA" in result or "NBLH" in result, (
            f"Spanish translations not in output: {result[:200]}"
        )


# =============================================================================
# S5: .acc2 Gospel Harmony for Passion Narrative
# =============================================================================


@pytest.mark.skipif(not _has_acc2_tools, reason="acc2 tools not importable")
class TestS5Acc2GospelHarmony:
    """S5: .acc2 modules — Synoptics, Gospels, Harmony for Matthew 27:45."""

    def test_list_acc2_at_least_3_modules(self):
        """list_acc2_modules() returns at least 3 module entries."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = list_acc2_modules()
        assert _str_ok(result)
        lines = [l for l in result.strip().split("\n") if l.strip()]
        assert len(lines) >= 3, (
            f"Expected >= 3 acc2 modules, got {len(lines)}: {result[:300]}"
        )

    def test_find_parallel_passages_matthew_27_45(self):
        """find_parallel_passages('Matthew 27:45') returns data with Mark/Luke refs."""
        result = find_parallel_passages("Matthew 27:45")
        assert _str_ok(result), f"find_parallel_passages Matt 27:45 failed: {result[:200]}"
        has_mark = "Mark" in result or "Mk" in result
        has_luke = "Luke" in result or "Lk" in result
        assert has_mark or has_luke, (
            f"Expected Mark or Luke parallels, got: {result[:300]}"
        )

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_chain_parallel_with_nabre(self):
        """Chain find_parallel_passages result with get_nabre_verse for a parallel ref."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = find_parallel_passages("Matthew 27:45")
        if "Mark 15:33" in result:
            nabre = get_nabre_verse("Mark 15:33")
            assert _str_ok(nabre), f"NABRE Mark 15:33 failed: {nabre[:200]}"
        else:
            # Try any Mark ref found in the output
            import re as _re
            mk_match = _re.search(r"Mark \d+:\d+", result)
            if mk_match:
                nabre = get_nabre_verse(mk_match.group())
                assert _str_ok(nabre)


# =============================================================================
# S6: Comprehensive Crossrefs for Doctrine Tracing
# =============================================================================


@pytest.mark.skipif(not _mcp_commentaries_loaded(), reason="Commentary modules not loaded")
class TestS6ComprehensiveCrossrefs:
    """S6: Cross-reference modules and find_cross_references for Genesis 3:15."""

    def test_crossref_modules_in_commentary_list(self):
        """list_commentaries() includes cross-reference modules."""
        result = list_commentaries()
        assert _str_ok(result)
        indicators = ["AF Crossrefs", "Comprehensive Crossrefs", "Crossrefs"]
        found = [name for name in indicators if name in result]
        assert len(found) >= 1, (
            f"Expected >=1 crossref module, found {found}. Output: {result[:300]}"
        )

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_find_cross_references_genesis_3_15(self):
        """find_cross_references('Genesis 3:15') returns results with book names."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = find_cross_references("Genesis 3:15")
        assert _str_ok(result), f"cross-refs Gen 3:15 failed: {result[:200]}"
        assert any(bk in result for bk in
                   ("Genesis", "Revelation", "Romans", "Galatians", "Matthew")), (
            f"No expected book names in cross-refs: {result[:300]}"
        )

    def test_find_ot_quotations_revelation_12_1(self):
        """find_ot_quotations('Revelation 12:1') returns data."""
        if not _has_acc2_tools:
            pytest.skip("acc2 tools not importable")
        result = find_ot_quotations("Revelation 12:1")
        assert _str_ok(result), f"OT quotations Rev 12:1 failed: {result[:200]}"

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_chain_crossref_with_nabre(self):
        """Chain find_cross_references result with get_nabre_verse."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = find_cross_references("Genesis 3:15")
        if not _str_ok(result):
            pytest.skip("cross-refs unavailable")
        # Grab any verse reference from the output
        import re as _re
        ref_match = _re.search(r"Genesis \d+:\d+", result)
        if ref_match:
            nabre = get_nabre_verse(ref_match.group())
            assert _str_ok(nabre)


# =============================================================================
# S7: OT-in-NT Quotation Detection (Hebrews 10:5)
# =============================================================================


@pytest.mark.skipif(not _has_acc2_tools, reason="acc2 tools not importable")
class TestS7OTinNTQuotations:
    """S7: Hebrews 10:5 → Psalm 40 via OT-in-NT module."""

    def test_find_ot_quotations_hebrews_10_5(self):
        """find_ot_quotations('Hebrews 10:5') returns valid OT quotation data."""
        result = find_ot_quotations("Hebrews 10:5")
        assert _str_ok(result), f"OT quotations Heb 10:5 failed: {result[:200]}"
        # Should return paragraph entries, not an error. Titles may not
        # explicitly name Psalm 40 but content relates to OT quotations.
        assert "paragraph" in result.lower() or "OT in NT" in result, (
            f"Expected OT-in-NT references, got: {result[:300]}"
        )

    def test_otinnt_in_acc2_modules(self):
        """list_acc2_modules() includes 'OT in NT' or 'Old Testament' module."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = list_acc2_modules()
        assert _str_ok(result)
        assert "OT in NT" in result or "Old Testament" in result, (
            f"Missing OT-in-NT module: {result[:300]}"
        )

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_chain_ot_quote_with_verse(self):
        """Chain find_ot_quotations result with get_verse_text for the OT ref."""
        result = find_ot_quotations("Hebrews 10:5")
        if not _str_ok(result):
            pytest.skip("No OT quotation results")
        import re as _re
        ps_match = _re.search(r"Psalm(?:s)?\s+\d+:\d+", result)
        if ps_match and _has_mcp_server:
            verse = get_verse_text("KJVS", ps_match.group())
            assert _str_ok(verse), f"get_verse_text Psalm failed: {verse[:200]}"


# =============================================================================
# S8: Qumran/Philo/Josephus Cross-References
# =============================================================================


@pytest.mark.skipif(not _mcp_commentaries_loaded(), reason="Commentary modules not loaded")
class TestS8QumranPhiloJosephus:
    """S8: Qumran, Philo, Josephus cross-reference modules."""

    def test_qumran_philo_josephus_modules(self):
        """list_commentaries() includes Qumran, Philo, or Josephus Crossrefs."""
        result = list_commentaries()
        assert _str_ok(result)
        indicators = ["Qumran Crossrefs", "Philo Crossrefs", "Josephus Crossrefs"]
        found = [name for name in indicators if name in result]
        assert len(found) >= 1, (
            f"Expected >=1 Qumran/Philo/Josephus module, found {found}: {result[:300]}"
        )

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_find_cross_references_isaiah_40_3(self):
        """find_cross_references('Isaiah 40:3') returns data."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = find_cross_references("Isaiah 40:3")
        assert _str_ok(result), f"cross-refs Isa 40:3 failed: {result[:200]}"

    def test_search_commentary_text_qumran(self):
        """search_commentary_text('Qumran') returns results across modules."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = search_commentary_text("Qumran", max_results=10)
        assert _str_ok(result), f"search_commentary_text('Qumran') failed: {result[:200]}"


# =============================================================================
# S9: Text-Critical Apparatus Comparison (1 John 5:7 — Comma Johanneum)
# =============================================================================


@pytest.mark.skipif(not _mcp_commentaries_loaded(), reason="Commentary modules not loaded")
class TestS9ApparatusComparisonComma:
    """S9: NA28 Apparatus on 1 John 5:7, Comma Johanneum variant across translations."""

    def test_na28_apparatus_1john_5_7(self):
        """get_commentary_text on NA28 Apparatus for 1 John 5:7 returns apparatus content."""
        if "NA28 Apparatus" not in list_commentaries():
            pytest.skip("NA28 Apparatus not installed")
        result = get_commentary_text("NA28 Apparatus", "1 John 5:7")
        assert _str_ok(result), f"NA28 1 John 5:7 failed: {result[:200]}"
        # Apparatus text often contains sigla, manuscript abbreviations, or verse numbers
        assert any(w in result for w in
                   ("\u03a0", "\u0391", "\u0392", "P", "A", "B", "\u2120")), (
            f"Expected apparatus sigla/manuscript marks, got: {result[:300]}"
        )

    @pytest.mark.skipif(not _has_network, reason="Network not available")
    def test_compare_translations_comma_johanneum(self):
        """compare_translations 1 John 5:7 across KJV, ESV, NABRE surfaces variant."""
        if not _has_mcp_server:
            pytest.skip("MCP server not importable")
        result = compare_translations("1 John 5:7", translations="KJV,ESV,NABRE")
        assert _str_ok(result), f"compare_trans Comma failed: {result[:200]}"
        # KJV will have Trinitarian formula, ESV/NABRE will be shorter or bracketed
        assert "KJV" in result and ("ESV" in result or "NABRE" in result), (
            f"Missing translations in Comma comparison: {result[:300]}"
        )


# =============================================================================
# Runner
# =============================================================================

if __name__ == "__main__":
    pytest.main([__file__, "-v"])
