"""Tests for lectionary structure integrity, Strong's pipeline, and Catholic
translation comparison — Session 75 (Cycle 2).

Scenarios:
  S75-1: Lectionary Structure Integrity — verify 2026 liturgical dates return
         real readings (not fallback) across the full liturgical year.
  S75-2: Strong's Pipeline — chain get_strongs_for_verse, get_verse (local KJV),
         parse_reference (OT/NT), search_verses_by_term, and get_verse_range.
  S75-3: Catholic Translation Comparison — compare_catholic_translations across
         NABRE/NRSVCE/DRB/KJV for key verses including deuterocanonical.
"""
from __future__ import annotations

import pytest


# =============================================================================
# IMPORT GUARDS — import everything with try/except, following project conventions
# =============================================================================

# --- Canon (local only) ---
try:
    from accordance_parser.canon import (
        parse_reference,
    )
    _has_canon = True
except Exception:
    _has_canon = False

# --- Bible API (local caches for KJV, network for others) ---
try:
    from accordance_parser.bible_api import get_verse, get_verse_range
    _has_bible_api = True
except Exception:
    _has_bible_api = False

# --- Lectionary (local) ---
try:
    from accordance_parser.lectionary import (
        get_lectionary_for_date_string,
    )
    _has_lectionary = True
except Exception:
    _has_lectionary = False

# --- Morphology (local) ---
try:
    _has_morphology = True
except Exception:
    _has_morphology = False

# --- MCP search tools (depends on running MCP server) ---
try:
    from accordance_services.tools.search import search_verses_by_term
    _has_mcp_search = True
except Exception:
    _has_mcp_search = False
    search_verses_by_term = None  # type: ignore[assignment]

# --- MCP server tools (depends on FastMCP/uvicorn availability) ---
_has_mcp_server = False
_mcp_server_import_error = ""
try:
    from accordance_mcp_server import (
        compare_catholic_translations,
        get_strongs_for_verse,
    )
    _has_mcp_server = True
except Exception as e:
    _mcp_server_import_error = str(e)


# =============================================================================
# NETWORK DETECTION
# =============================================================================

_has_network = False
try:
    from accordance_parser.bible_api import get_verse as _gv
    r = _gv("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 _assert_valid_lectionary_entry(
    date_str: str,
    expected_season_substring: str = "",
    expect_second: bool = False,
) -> None:
    """Shared assertion: lectionary entry is valid (not fallback, has keys).
    
    Args:
        date_str: YYYY-MM-DD date string.
        expected_season_substring: Optional substring expected in the season name.
        expect_second: If True, assert a non-empty "second" reading is present.
    """
    result = get_lectionary_for_date_string(date_str)
    assert result is not None, f"No lectionary data for {date_str}"
    season, readings = result

    # Season is non-empty
    assert isinstance(season, str) and season.strip(), (
        f"Empty season for {date_str}"
    )

    if expected_season_substring:
        assert expected_season_substring.lower() in season.lower(), (
            f"Expected season containing {expected_season_substring!r} "
            f"for {date_str}, got: {season!r}"
        )

    # Not a fallback placeholder
    for key in ["first", "gospel"]:
        assert key in readings, f"Missing '{key}' key for {date_str}"
        val = readings[key]
        assert val.strip(), f"Empty '{key}' for {date_str}"
        assert "See USCCB.org" not in val, (
            f"Fallback placeholder in '{key}' for {date_str}: {val!r}"
        )

    # "psalm" key always present
    assert "psalm" in readings, f"Missing 'psalm' key for {date_str}"

    # Optional "second" reading
    if expect_second:
        assert "second" in readings, f"Missing 'second' key for {date_str}"
        assert readings["second"].strip(), f"Empty 'second' for {date_str}"
        assert "See USCCB.org" not in readings["second"], (
            f"Fallback placeholder in 'second' for {date_str}"
        )


# =============================================================================
# S75-1: LECTIONARY STRUCTURE INTEGRITY (12 tests, local — no network needed)
# =============================================================================

@pytest.mark.skipif(not _has_lectionary, reason="Lectionary module not available")
class TestLectionaryStructureIntegrity:
    """Verify that key 2026 liturgical dates return actual structured readings
    (not fallback placeholders) across the full liturgical year."""

    # -- Sample dates across the liturgical year --

    def test_baptism_of_lord_2026_01_11(self):
        """2026-01-11 Baptism of the Lord returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-01-11", "Baptism", expect_second=True,
        )

    def test_6th_sunday_ordinary_time_2026_02_15(self):
        """2026-02-15 6th Sunday Ordinary Time returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-02-15", "Sixth Sunday", expect_second=True,
        )

    def test_ash_wednesday_2026_02_18(self):
        """2026-02-18 Ash Wednesday returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-02-18", "Ash Wednesday", expect_second=True,
        )

    def test_easter_sunday_2026_04_05(self):
        """2026-04-05 Easter Sunday returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-04-05", "Easter Sunday", expect_second=True,
        )

    def test_pentecost_sunday_2026_05_24(self):
        """2026-05-24 Pentecost Sunday returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-05-24", "Pentecost", expect_second=True,
        )

    def test_assumption_2026_08_15(self):
        """2026-08-15 Assumption (Saturday solemnity) returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-08-15", "Assumption", expect_second=True,
        )

    def test_all_saints_2026_11_01(self):
        """2026-11-01 All Saints returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-11-01", "All Saints", expect_second=True,
        )

    def test_christmas_2026_12_25(self):
        """2026-12-25 Christmas returns real readings."""
        _assert_valid_lectionary_entry(
            "2026-12-25", "Christmas", expect_second=True,
        )

    # -- Specific structural assertions --

    def test_easter_sunday_has_non_empty_second_reading(self):
        """Easter Sunday must have a 'second' reading with non-empty value."""
        result = get_lectionary_for_date_string("2026-04-05")
        assert result is not None
        season, readings = result
        assert "second" in readings, "Easter Sunday missing 'second' reading"
        second = readings["second"]
        assert second.strip(), "Easter Sunday 'second' reading is empty"
        assert "See USCCB.org" not in second, \
            f"Easter Sunday 'second' is a fallback: {second!r}"
        # Verify actual expected reference
        assert second == "Colossians 3:1-4", (
            f"Unexpected Easter second reading: {second!r}"
        )

    def test_out_of_range_date_2025_12_25_fallback_graceful(self):
        """2025-12-25 (outside lectionary coverage) returns fallback without crash."""
        result = get_lectionary_for_date_string("2025-12-25")
        assert result is not None, "Fallback should not be None"
        season, readings = result
        assert isinstance(season, str) and season.strip()
        # Fallback entries have the required keys
        for key in ["first", "psalm", "gospel"]:
            assert key in readings, f"Missing '{key}' in fallback for 2025-12-25"
            assert isinstance(readings[key], str)

    def test_lent_weekday_2026_02_19(self):
        """First weekday of Lent (Thursday after Ash Wed) returns real readings."""
        _assert_valid_lectionary_entry("2026-02-19", "Thursday after Ash")

    def test_lent_weekday_2026_02_20(self):
        """Second weekday of Lent (Friday after Ash Wed) returns real readings."""
        _assert_valid_lectionary_entry("2026-02-20", "Friday after Ash")

    def test_lent_weekday_2026_02_21(self):
        """Third weekday of Lent (Saturday after Ash Wed) returns real readings."""
        _assert_valid_lectionary_entry("2026-02-21", "Saturday after Ash")


# =============================================================================
# S75-2: STRONG'S PIPELINE (8 tests)
# =============================================================================

class TestStrongsPipeline:
    """Chain get_strongs_for_verse → get_verse → parse_reference →
    search_verses_by_term → get_verse_range for both OT and NT."""

    # -- Strong's via MCP server (uses local KJV cache) --

    @pytest.mark.skipif(not _has_mcp_server, reason="MCP server tools not available")
    def test_get_strongs_for_verse_genesis_1_1(self):
        """get_strongs_for_verse('Genesis 1:1') returns pairings with H7225."""
        result = get_strongs_for_verse("Genesis 1:1")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        # Should contain the Strong's number for "beginning" (H7225)
        assert "H7225" in result, (
            f"Expected H7225 in Strong's output, got: {result[:200]}"
        )
        # Should also mention the verse text
        assert "beginning" in result.lower(), (
            f"Expected 'beginning' in Strong's output, got: {result[:200]}"
        )

    # -- Local verse retrieval (KJV cache, no network) --

    @pytest.mark.skipif(not _has_bible_api, reason="Bible API not available")
    def test_get_verse_genesis_1_1_kjv_local(self):
        """get_verse('Genesis 1:1', 'KJV') returns text with 'beginning'."""
        result = get_verse("Genesis 1:1", "KJV")
        assert result is not None
        assert "beginning" in result.text.lower(), (
            f"Expected 'beginning' in Genesis 1:1, got: {result.text[:100]}"
        )
        assert result.strongs, "Genesis 1:1 should have Strong's numbers"
        assert "H7225" in result.strongs, \
            f"Expected H7225 in Strong's, got: {result.strongs}"

    # -- parse_reference through Strong's index (OT and NT) --

    @pytest.mark.skipif(not _has_canon, reason="Canon module not available")
    def test_parse_reference_ot_resolves(self):
        """parse_reference('Genesis 1:1') resolves OT book correctly."""
        ref = parse_reference("Genesis 1:1", "catholic")
        assert ref is not None
        assert ref.book_osis == "Gen"
        assert ref.chapter == 1
        assert ref.verse == 1

    @pytest.mark.skipif(not _has_canon, reason="Canon module not available")
    def test_parse_reference_nt_resolves(self):
        """parse_reference('John 1:1') resolves NT book correctly."""
        ref = parse_reference("John 1:1", "catholic")
        assert ref is not None
        assert ref.book_osis == "John"
        assert ref.chapter == 1
        assert ref.verse == 1

    @pytest.mark.skipif(not _has_canon, reason="Canon module not available")
    def test_parse_reference_john_1_1_detailed(self):
        """parse_reference('John 1:1') returns correct structure (chapter, verse, end_verse)."""
        ref = parse_reference("John 1:1", "catholic")
        assert ref is not None
        assert ref.book_osis == "John"
        assert ref.chapter == 1
        assert ref.verse == 1
        assert ref.end_verse is None  # single verse, no range

    # -- Search verses by term (MCP search tools) --

    @pytest.mark.skipif(
        not _has_mcp_search,
        reason="MCP search tools not available (accordance_parser.mcp_search_tools)",
    )
    def test_search_verses_by_term_all_have_sinned(self):
        """search_verses_by_term('all have sinned', kjv) returns results."""
        result = search_verses_by_term("all have sinned", max_results=10, translation="kjv")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        # Should contain Romans 3:23 or at minimum have meaningful output
        has_romans = "Romans 3:23" in result or "Rom 3:23" in result
        has_content = len(result) > 20
        assert has_romans or has_content, (
            f"search_verses_by_term returned insufficient content: {result[:200]}"
        )

    # -- Multi-verse range retrieval (requires network) --

    @pytest.mark.skipif(not _has_network, reason="Requires network for multi-verse range")
    def test_get_verse_range_genesis_1_1_to_1_5(self):
        """get_verse_range('Genesis 1:1-5', 'KJVS') returns 5 distinct verses."""
        results = get_verse_range("Genesis 1:1-5", "KJVS")
        assert len(results) == 5, (
            f"Expected 5 verses for Genesis 1:1-5, got {len(results)}"
        )
        # Verify ascending verse order
        for i, vr in enumerate(results):
            assert vr.verse == i + 1, (
                f"Expected verse {i + 1} at position {i}, got verse {vr.verse}"
            )
        # All verses have text
        for vr in results:
            assert vr.text.strip(), f"Empty text for verse {vr.verse}"

    @pytest.mark.skipif(not _has_network, reason="Requires network for multi-verse range")
    def test_get_verse_range_john_3_16_to_3_18(self):
        """get_verse_range('John 3:16-18', 'KJVS') returns 3 verses."""
        results = get_verse_range("John 3:16-18", "KJVS")
        assert len(results) == 3, (
            f"Expected 3 verses for John 3:16-18, got {len(results)}"
        )
        # John 3:16 should contain "world"
        verse_16 = results[0]
        assert verse_16.verse == 16
        assert "world" in verse_16.text.lower(), (
            f"John 3:16 should mention 'world': {verse_16.text}"
        )
        # John 3:17 should contain "condemn" or "save"
        verse_17 = results[1]
        assert verse_17.verse == 17


# =============================================================================
# S75-3: CATHOLIC TRANSLATION COMPARISON + CROSS-CHECK (6 tests)
# =============================================================================

@pytest.mark.skipif(not _has_network, reason="Requires network")
class TestCatholicTranslationComparison:
    """compare_catholic_translations across NABRE, NRSVCE, DRB, and KJV for
    key Catholic-apologetic and deuterocanonical verses."""

    def _skip_if_no_mcp(self):
        if not _has_mcp_server:
            pytest.skip("MCP server tools not available")

    def test_genesis_3_15_all_three_return_text(self):
        """compare_catholic_translations('Genesis 3:15') — NABRE/NRSVCE/DRB all return text."""
        self._skip_if_no_mcp()
        result = compare_catholic_translations("Genesis 3:15")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        for label in ["NABRE", "NRSVCE", "DRB"]:
            assert label in result, (
                f"Missing {label} translation in output: {result[:300]}"
            )
        # All three rows should have content (not "(not available)")
        assert "(not available)" not in result, (
            f"At least one translation unavailable for Genesis 3:15: {result[:300]}"
        )

    def test_luke_1_28_nabre_vs_drb_differ(self):
        """compare_catholic_translations('Luke 1:28') — NABRE vs DRB show translation differences."""
        self._skip_if_no_mcp()
        result = compare_catholic_translations("Luke 1:28")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        # DRB has "full of grace"; NABRE has "favored one" or similar
        text_lower = result.lower()
        assert "grace" in text_lower or "favored" in text_lower, (
            f"Expected 'grace' or 'favored' in Luke 1:28 comparison: {result[:300]}"
        )
        assert "NABRE" in result
        assert "DRB" in result

    def test_john_6_53_contains_flesh_or_blood(self):
        """compare_catholic_translations('John 6:53') — all three mention flesh/blood."""
        self._skip_if_no_mcp()
        result = compare_catholic_translations("John 6:53")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        text_lower = result.lower()
        assert "flesh" in text_lower or "blood" in text_lower, (
            f"Expected 'flesh' or 'blood' in John 6:53: {result[:300]}"
        )
        for label in ["NABRE", "NRSVCE", "DRB"]:
            assert label in result

    def test_2_maccabees_12_46_deuterocanonical(self):
        """compare_catholic_translations('2 Maccabees 12:46') — deuterocanonical purgatory text."""
        self._skip_if_no_mcp()
        try:
            result = compare_catholic_translations("2 Maccabees 12:46")
        except Exception as e:
            pytest.skip(f"MCP server call failed: {e}")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        # Deuterocanonical book should not crash and should return text
        for label in ["NABRE", "NRSVCE", "DRB"]:
            assert label in result, (
                f"Missing {label} for deuterocanonical 2 Macc 12:46"
            )

    def test_tobit_12_15_mentions_raphael(self):
        """compare_catholic_translations('Tobit 12:15') — text mentions Raphael."""
        self._skip_if_no_mcp()
        result = compare_catholic_translations("Tobit 12:15")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        text_lower = result.lower()
        assert "raphael" in text_lower, (
            f"Expected 'Raphael' in Tobit 12:15: {result[:300]}"
        )
        for label in ["NABRE", "NRSVCE", "DRB"]:
            assert label in result

    def test_comparison_includes_kjv_row(self):
        """compare_catholic_translations includes a KJV reference row."""
        self._skip_if_no_mcp()
        result = compare_catholic_translations("Genesis 3:15")
        assert isinstance(result, str) and len(result) > 0
        assert _no_traceback(result)
        assert "KJV" in result, (
            f"Expected KJV reference row in comparison output: {result[:300]}"
        )
        # Verify it's a table row (markdown pipe format)
        assert "| **KJV**" in result, (
            f"KJV should appear as a markdown table row: {result[:500]}"
        )
