"""Tests for 8 QA-engineer Catholic scenarios — error recovery and graceful degradation.

Scenarios S-Q-03 through S-Q-10: verse lookup failures, invalid Strong's,
Catholic/Protestant canon boundaries, Sirach chapter boundaries, cross-translation
deuterocanonical comparisons, and NABRE vs KJV Strong's consistency.

All tests follow the project convention: validate return types, check for
absence of Python tracebacks, accept error strings as valid output on failure
paths, and document rather than fix bugs.
"""
from __future__ import annotations

import pytest


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

_has_canon = False
try:
    from accordance_parser.canon import (
        resolve_book, CANON_CATHOLIC, CANON_PROTESTANT,
        chapter_count, last_verse_of_chapter,
    )
    _has_canon = True
except Exception:
    pass

_has_bible_api = False
try:
    from accordance_parser.bible_api import get_verse
    _has_bible_api = True
except Exception:
    pass

_has_mcp_server = False
_mcp_server_import_error = ""
try:
    from accordance_mcp_server import (
        get_nabre_verse,
        get_verse_text,
        compare_catholic_translations,
        compare_translations,
        get_strongs_for_verse,
        lookup_strongs_definition,
        study_strongs_number,
    )
    _has_mcp_server = True
except Exception as e:
    _mcp_server_import_error = str(e)

_has_multi_tools = False
try:
    from accordance_services.tools.text import get_book_list
    _has_multi_tools = True
except Exception:
    pass


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

_has_network = False
try:
    from accordance_parser.bible_api import get_verse
    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 _is_error_like(text: str) -> bool:
    """Check if text looks like an error/fallback message (not a crash)."""
    lower = text.lower()
    error_markers = [
        "error", "could not retrieve", "invalid", "no definition found",
        "not available", "not found", "module", "fallback",
    ]
    return any(m in lower for m in error_markers)


# =============================================================================
# S-Q-03: Verse Lookup Failure → Fallback Chain
# =============================================================================

@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ03_FallbackChain:
    """S-Q-03: Test error recovery when tools in a chain return errors."""

    def test_psalm_151_nabre_accepts_either(self):
        """Psalm 151 may return text or error — accept either without crash."""
        result = get_nabre_verse("Psalm 151")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Either valid text or graceful error — both acceptable

    def test_compare_catholic_psalm_151_no_crash(self):
        """compare_catholic_translations for Psalm 151 must not crash."""
        result = compare_catholic_translations("Psalm 151")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)

    def test_daniel_13_1_nabre_returns_susanna(self):
        """Daniel 13:1 (Susanna) must return text in NABRE."""
        result = get_nabre_verse("Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 50  # Should have real content
        assert _no_traceback(result)
        # Should contain Susanna-related text
        assert not _is_error_like(result) or "susanna" in result.lower()

    def test_kjv_daniel_13_1_returns_error_string(self):
        """KJV has no Daniel 13 — must return error/fallback string, not crash/traceback."""
        result = get_verse_text("KJV", "Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should be an error/fallback, NOT a Python traceback
        # NOTE: bolls.life KJV may include Apocrypha — accept either error or text

    def test_system_not_in_error_state_after_failures(self):
        """After all failures above, John 3:16 in NABRE must still work."""
        # Run the failure-path tests first (order within class is arbitrary,
        # but this test explicitly validates recovery after potential failures)
        result = get_verse_text("NABRE", "John 3:16")
        assert isinstance(result, str)
        assert len(result) > 50
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"System should have recovered; got error: {result[:200]}"
        )


# =============================================================================
# S-Q-04: Invalid Strong's → Graceful Degradation
# =============================================================================

@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ04_InvalidStrongs:
    """S-Q-04: Invalid/fake Strong's numbers must not crash any tool."""

    def test_nabre_tobit_with_strongs_returns_text(self):
        """NABRE Tobit 4:15 with include_strongs=True — text OK, Strong's optional."""
        result = get_verse_text("NABRE", "Tobit 4:15", include_strongs=True)
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Must contain verse text (Strong's may be empty — that's OK)

    def test_kjv_strongs_for_tobit_returns_error(self):
        """KJV has no Tobit — get_strongs_for_verse must return error, no crash.

        NOTE: bolls.life KJV may include Apocrypha. Accept error or text."""
        # Arg order is (reference, translation), translation defaults to "kjv"
        result = get_strongs_for_verse("Tobit 4:15", "kjv")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # KJV Apocrypha may exist — accept either error or valid Strong's data
        # If it returned real Strong's data, that means bolls.life KJV
        # includes Apocrypha, which is fine. The key assertion is no crash.

    def test_lookup_fake_hebrew_strongs_no_crash(self):
        """lookup_strongs_definition('H99999') — fake, must not crash."""
        result = lookup_strongs_definition("H99999")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should return a "not found" or "no definition" message

    def test_study_fake_greek_strongs_graceful(self):
        """study_strongs_number('G9999999') — fake, must handle gracefully."""
        result = study_strongs_number("G9999999")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should not crash; may return "not found" or partial report

    def test_system_still_works_after_fake_strongs(self):
        """After all fake Strong's lookups, John 3:16 NABRE must still work."""
        result = get_nabre_verse("John 3:16")
        assert isinstance(result, str)
        assert len(result) > 50
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"System should still work; got error: {result[:200]}"
        )


# =============================================================================
# S-Q-05: Daniel 13-14 Catholic vs Protestant Boundary
# =============================================================================

@pytest.mark.skipif(not _has_multi_tools, reason="get_book_list not available")
@pytest.mark.skipif(not _has_canon, reason="Canon not available")
class TestSQ05_DanielCanonBoundary:
    """S-Q-05: Daniel chapter counts differ between Catholic (14) and Protestant (12)."""

    def test_catholic_daniel_has_14_chapters(self):
        """Catholic canon: Daniel has 14 chapters."""
        result = get_book_list("catholic")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Must mention Daniel with 14 chapters
        assert "Daniel" in result, "Book list should include Daniel"
        # Check for 14 chapters — look for "| Daniel | 14 |" pattern
        assert "14" in result.split("Daniel")[1].split("\n")[0] if "Daniel" in result else False, (
            "Daniel should show 14 chapters in Catholic canon"
        )

    def test_protestant_daniel_has_12_chapters(self):
        """Protestant canon: Daniel has 12 chapters."""
        result = get_book_list("protestant")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        assert "Daniel" in result, "Book list should include Daniel"
        # Daniel row should show 12 chapters
        assert "12" in result.split("Daniel")[1].split("\n")[0] if "Daniel" in result else False, (
            "Daniel should show 12 chapters in Protestant canon"
        )

    def test_resolve_book_daniel_catholic_chapter_count(self):
        """resolve_book('Daniel') + chapter_count in Catholic canon = 14."""
        from accordance_parser.canon import chapter_count, CANON_CATHOLIC
        ch = chapter_count("Dan", CANON_CATHOLIC)
        assert ch == 14


@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ05_DanielNetwork:
    """S-Q-05 network tests: KJV/ESV vs NABRE for Daniel 13."""

    def test_kjv_daniel_13_returns_error(self):
        """KJV Daniel 13:1 must return error (not text from Daniel 3 or other)."""
        result = get_verse_text("KJV", "Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Must be error — not a valid KJV verse
        assert _is_error_like(result), (
            f"KJV Daniel 13:1 should be error, got: {result[:200]}"
        )

    def test_esv_daniel_13_returns_error(self):
        """ESV Daniel 13:1 must return error."""
        result = get_verse_text("ESV", "Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Must be error — ESV has no deuterocanon
        assert _is_error_like(result), (
            f"ESV Daniel 13:1 should be error, got: {result[:200]}"
        )

    def test_nabre_daniel_13_returns_susanna_text(self):
        """NABRE Daniel 13:1 must return Susanna text."""
        result = get_nabre_verse("Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 50  # Verse 1 is short (In Babylon there lived a man named Joakim...)
        assert _no_traceback(result)
        # Should not be an error — NABRE has deuterocanonical Daniel
        if _is_error_like(result):
            pytest.fail(f"NABRE Daniel 13:1 should return text, got error: {result[:200]}")
        # The text should mention Susanna or Joakim (verse 1 introduces Joakim)
        text_lower = result.lower()
        assert any(word in text_lower for word in ["susanna", "joakim"]), (
            f"Expected Susanna content, got: {result[:200]}"
        )

    def test_compare_catholic_daniel_13_returns_comparison(self):
        """compare_catholic_translations for Daniel 13:1 should return comparison."""
        result = compare_catholic_translations("Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 50
        assert _no_traceback(result)
        # Should contain a table with NABRE, NRSVCE, DRB
        assert "NABRE" in result or "NRSVCE" in result or "DRB" in result

    def test_daniel_13_1_not_from_daniel_3(self):
        """Daniel 13:1 must NOT return text from Daniel 3 or other chapter."""
        result = get_nabre_verse("Daniel 13:1")
        assert isinstance(result, str)
        # Daniel 3 is about the fiery furnace; Daniel 13 is about Susanna
        text_lower = result.lower()
        # Susanna content indicators
        susanna_indicators = ["susanna", "joakim", "bath", "elder", "garden"]
        # Daniel 3 content indicators (must not appear)
        daniel3_indicators = ["shadrach", "meshach", "abednego", "furnace", "nebuchadnezzar"]
        has_susanna = any(w in text_lower for w in susanna_indicators)
        has_daniel3 = any(w in text_lower for w in daniel3_indicators)
        if has_daniel3 and not has_susanna:
            pytest.fail(f"Daniel 13:1 appears to return Daniel 3 text: {result[:200]}")


# =============================================================================
# S-Q-06: Sirach Chapter Boundaries
# =============================================================================

@pytest.mark.skipif(not _has_canon, reason="Canon not available")
class TestSQ06_SirachBoundaries:
    """S-Q-06: Sirach (Ecclesiasticus) chapter boundaries and canonical status."""

    def test_resolve_sirach_deuterocanonical(self):
        """resolve_book('Sirach') → deuterocanonical=True, osis='Sir'."""
        b = resolve_book("Sirach")
        assert b is not None, "Failed to resolve 'Sirach'"
        assert b.osis == "Sir", f"Expected osis='Sir', got '{b.osis}'"
        # Check deuterocanonical flag if the Book object has it
        if hasattr(b, "deuterocanonical"):
            assert b.deuterocanonical is True, (
                f"Expected deuterocanonical=True, got {b.deuterocanonical}"
            )

    def test_resolve_ecclesiasticus_same_as_sirach(self):
        """resolve_book('Ecclesiasticus') → same book as Sirach (Vulgate alias)."""
        sirach = resolve_book("Sirach")
        ecclus = resolve_book("Ecclesiasticus")
        assert sirach is not None, "Failed to resolve 'Sirach'"
        assert ecclus is not None, "Failed to resolve 'Ecclesiasticus'"
        assert ecclus.osis == sirach.osis, (
            f"Ecclesiasticus osis={ecclus.osis} should match Sirach osis={sirach.osis}"
        )

    def test_sirach_has_51_chapters(self):
        """Sirach has 51 chapters in Catholic canon."""
        ch = chapter_count("Sir", CANON_CATHOLIC)
        assert ch == 51, f"Expected 51 chapters, got {ch}"

    def test_sirach_51_30_is_valid_verse(self):
        """Sirach 51:30 is the last verse — must be within bounds."""
        last = last_verse_of_chapter("Sir", 51, CANON_CATHOLIC)
        assert last > 0, "Sirach 51 should have verses"
        assert last >= 30, f"Expected at least 30 verses in Sirach 51, got {last}"


@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ06_SirachNetwork:
    """S-Q-06 network tests: Sirach verse retrieval across Catholic/Protestant boundaries."""

    def test_nabre_sirach_51_30_resolves(self):
        """get_nabre_verse('Sirach 51:30') — last verse, must resolve."""
        result = get_nabre_verse("Sirach 51:30")
        assert isinstance(result, str)
        assert len(result) > 30
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"Sirach 51:30 should return text, got error: {result[:200]}"
        )

    def test_kjv_sirach_returns_error(self):
        """KJV has no Sirach in Protestant canon — get_verse_text behavior.

        NOTE: bolls.life API includes KJV Apocrypha, so Sirach 1:1 may
        actually return text (the Prologue of Sirach). This is a known
        behavior, not a bug. Accept either error or text."""
        result = get_verse_text("KJV", "Sirach 1:1")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # KJV Apocrypha exists — either error OR valid text is acceptable.
        # Just verify we didn't crash.

    def test_compare_catholic_sirach_1_1_all_return_text(self):
        """compare_catholic_translations('Sirach 1:1') — NABRE/NRSVCE/DRB all return text."""
        result = compare_catholic_translations("Sirach 1:1")
        assert isinstance(result, str)
        assert len(result) > 100
        assert _no_traceback(result)
        # Should have multiple translations in the comparison table
        assert "NABRE" in result, "Comparison should include NABRE"
        assert "NRSVCE" in result, "Comparison should include NRSVCE"
        # DRB may or may not be available for Sirach — accept either
        # Verify at least one translation returned real text (not just "not available")
        assert "*(not available)*" not in result.replace("NABRE", "").replace("NRSVCE", "")[:500] or \
            "Sirach" in result, (
            "Expected at least some translation text for Sirach 1:1"
        )


# =============================================================================
# S-Q-09: Cross-Translation Deuterocanonical Wisdom 2:24
# =============================================================================

@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ09_WisdomCrossTranslation:
    """S-Q-09: Cross-translation comparison for Wisdom 2:24 (deuterocanonical)."""

    def test_compare_translations_wisdom_2_24(self):
        """compare_translations('Wisdom 2:24', 'NABRE,NRSVCE,DRB') — multi-column."""
        result = compare_translations("Wisdom 2:24", "NABRE,NRSVCE,DRB")
        assert isinstance(result, str)
        assert len(result) > 100
        assert _no_traceback(result)
        # Should be a comparison table
        assert "NABRE" in result or "NRSVCE" in result or "DRB" in result

    def test_compare_catholic_wisdom_2_24(self):
        """compare_catholic_translations('Wisdom 2:24') — NABRE/NRSVCE/DRB comparison."""
        result = compare_catholic_translations("Wisdom 2:24")
        assert isinstance(result, str)
        assert len(result) > 100
        assert _no_traceback(result)

    def test_nabre_wisdom_2_24_devil_envy_death(self):
        """get_nabre_verse('Wisdom 2:24') — must return text about devil/envy/death."""
        result = get_nabre_verse("Wisdom 2:24")
        assert isinstance(result, str)
        assert len(result) > 50
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"Wisdom 2:24 should return text, got error: {result[:200]}"
        )
        text_lower = result.lower()
        # Wisdom 2:24 traditionally says "by the envy of the devil, death entered the world"
        wisdom_keywords = ["devil", "envy", "death", "diabolos", "died"]
        assert any(w in text_lower for w in wisdom_keywords), (
            f"Expected devil/envy/death content in Wisdom 2:24, got: {result[:300]}"
        )

    def test_drb_wisdom_2_24_differs_from_nabre(self):
        """get_verse_text('DRB', 'Wisdom 2:24') — if available, text should differ from NABRE."""
        drb_result = get_verse_text("DRB", "Wisdom 2:24")
        nabre_result = get_nabre_verse("Wisdom 2:24")

        assert isinstance(drb_result, str)
        assert len(drb_result) > 0
        assert _no_traceback(drb_result)

        # If DRB returned error (module not available), skip the comparison
        if _is_error_like(drb_result):
            pytest.skip(f"DRB not available for Wisdom 2:24: {drb_result[:100]}")

        # DRB and NABRE should produce different renderings (different translation eras)
        if drb_result == nabre_result:
            # This is a bug or the same text — flag it but don't fail hard
            # (both may fall back to the same API endpoint)
            pass  # Document only: translations may return identical results
        else:
            # Good — translations differ as expected
            pass

    def test_comparison_outputs_consistent(self):
        """Comparison output from compare_translations and compare_catholic_translations
        must be consistent (both should reference Wisdom 2:24)."""
        cmp_general = compare_translations("Wisdom 2:24", "NABRE,NRSVCE,DRB")
        cmp_catholic = compare_catholic_translations("Wisdom 2:24")

        assert isinstance(cmp_general, str)
        assert isinstance(cmp_catholic, str)
        assert _no_traceback(cmp_general)
        assert _no_traceback(cmp_catholic)

        # Both should reference the same verse
        assert "Wisdom 2:24" in cmp_general or "Wisdom" in cmp_general
        assert "Wisdom 2:24" in cmp_catholic or "Wisdom" in cmp_catholic


# =============================================================================
# S-Q-10: NABRE vs KJV Strong's Consistency
# =============================================================================

@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestSQ10_StrongsConsistency:
    """S-Q-10: NABRE vs KJV Strong's consistency for Isaiah 7:14 (almah/virgin)."""

    def test_compare_translations_isaiah_7_14(self):
        """compare_translations('Isaiah 7:14', 'NABRE,KJV,ESV') — almah/virgin verse."""
        result = compare_translations("Isaiah 7:14", "NABRE,KJV,ESV")
        assert isinstance(result, str)
        assert len(result) > 100
        assert _no_traceback(result)
        # Should have KJV and ESV at minimum
        assert "KJV" in result or "ESV" in result

    def test_kjv_strongs_isaiah_7_14_returns_h5959(self):
        """get_strongs_for_verse('Isaiah 7:14', 'KJV') — should return H5959 (almah)."""
        # NOTE: arg order is (reference, translation), translation defaults to "kjv"
        result = get_strongs_for_verse("Isaiah 7:14", "kjv")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should contain H5959 (almah = young woman/virgin)
        assert "H5959" in result or "5959" in result, (
            f"Expected Strong's H5959 for Isaiah 7:14, got: {result[:300]}"
        )

    def test_nabre_isaiah_7_14_with_strongs(self):
        """get_verse_text('NABRE', 'Isaiah 7:14', include_strongs=True) — correct text.

        Fixed: NABRE binary parser was returning wrong verse text (e.g. "Mockery
        and abuse will befall the arrogant" for Isaiah 7:14). Now routed directly
        to bolls.life API which returns correct text. The Strong's numbers are
        fetched from KJV fallback and should include H5959 (almah)."""
        result = get_verse_text("NABRE", "Isaiah 7:14", include_strongs=True)
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # The verse text should contain the actual Isaiah 7:14 prophecy
        text_lower = result.lower()
        valid = ("young woman" in text_lower or "virgin" in text_lower
                 or "immanuel" in text_lower or "emmanuel" in text_lower
                 or "sign" in text_lower)
        assert valid, (
            f"Expected Isaiah 7:14 prophecy text (virgin/young woman/Immanuel/sign), "
            f"got: {result[:300]}"
        )
        # The Strong's numbers from KJV fallback should include H5959 (almah)
        assert "H5959" in result or "5959" in result, (
            f"Expected Strong's H5959 for Isaiah 7:14, got: {result[:300]}"
        )

    def test_lookup_strongs_h5959_works(self):
        """lookup_strongs_definition('H5959') — must work (almah = young woman)."""
        result = lookup_strongs_definition("H5959")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should return a definition, not a "not found" error
        if _is_error_like(result):
            # Document: H5959 may not be in installed lexicons
            pass

    def test_lookup_strongs_g3933_works(self):
        """lookup_strongs_definition('G3933') — parthenos (virgin in Greek)."""
        result = lookup_strongs_definition("G3933")
        assert isinstance(result, str)
        assert len(result) > 0
        assert _no_traceback(result)
        # Should return a definition, not a "not found" error
        if _is_error_like(result):
            # Document: G3933 may not be in installed lexicons
            pass


# =============================================================================
# S-Q-11: NABRE Verse Text Correctness (bolls.life API)
# =============================================================================
# These tests verify that NABRE verses return the correct text from bolls.life.
# The NABRE binary parser is unreliable (known offset issues), so NABRE is
# routed directly to bolls.life API via _BYPASS_BINARY_PARSER in the MCP server.


@pytest.mark.skipif(not _has_network, reason="Requires network (bolls.life API)")
@pytest.mark.skipif(not _has_mcp_server, reason=f"MCP server not available: {_mcp_server_import_error}")
class TestNABREVerseCorrectness:
    """Verify NABRE verses return correct text from bolls.life (not binary parser)."""

    def test_nabre_genesis_1_1_correct(self):
        """Genesis 1:1 in NABRE should mention heavens and earth."""
        result = get_verse_text("NABRE", "Genesis 1:1")
        assert isinstance(result, str)
        assert len(result) > 20
        assert _no_traceback(result)
        text_lower = result.lower()
        assert "heavens" in text_lower, f"Missing 'heavens' in: {result[:200]}"
        assert "earth" in text_lower, f"Missing 'earth' in: {result[:200]}"

    def test_nabre_isaiah_7_14_correct_text(self):
        """Isaiah 7:14 in NABRE should contain the Immanuel prophecy."""
        result = get_verse_text("NABRE", "Isaiah 7:14")
        assert isinstance(result, str)
        assert len(result) > 30
        assert _no_traceback(result)
        text_lower = result.lower()
        valid = ("young woman" in text_lower or "virgin" in text_lower
                 or "immanuel" in text_lower or "emmanuel" in text_lower)
        assert valid, (
            f"Expected Isaiah 7:14 prophecy, got: {result[:200]}"
        )

    def test_nabre_john_3_16_correct(self):
        """John 3:16 in NABRE should mention God's love and the world."""
        result = get_verse_text("NABRE", "John 3:16")
        assert isinstance(result, str)
        assert len(result) > 30
        assert _no_traceback(result)
        text_lower = result.lower()
        assert "god" in text_lower, f"Missing 'God' in: {result[:200]}"
        assert "world" in text_lower, f"Missing 'world' in: {result[:200]}"
        assert "loved" in text_lower or "love" in text_lower, (
            f"Missing 'love/loved' in: {result[:200]}"
        )

    def test_nabre_psalm_23_1_correct(self):
        """Psalm 23:1 in NABRE should mention shepherd."""
        result = get_verse_text("NABRE", "Psalm 23:1")
        assert isinstance(result, str)
        assert len(result) > 10
        assert _no_traceback(result)
        text_lower = result.lower()
        assert "shepherd" in text_lower or "lord" in text_lower, (
            f"Missing 'shepherd'/'Lord' in: {result[:200]}"
        )

    def test_nabre_tobit_1_1_deuterocanonical(self):
        """Tobit 1:1 (deuterocanonical) should return text in NABRE."""
        result = get_verse_text("NABRE", "Tobit 1:1")
        assert isinstance(result, str)
        assert len(result) > 20
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"Expected Tobit 1:1 text, got error: {result[:200]}"
        )

    def test_nabre_wisdom_2_24_deuterocanonical(self):
        """Wisdom 2:24 (deuterocanonical) should return text in NABRE."""
        result = get_verse_text("NABRE", "Wisdom 2:24")
        assert isinstance(result, str)
        assert len(result) > 20
        assert _no_traceback(result)
        assert not _is_error_like(result), (
            f"Expected Wisdom 2:24 text, got error: {result[:200]}"
        )

    def test_nabre_daniel_13_1_susanna(self):
        """Daniel 13:1 (Susanna, deuterocanonical) in NABRE."""
        result = get_verse_text("NABRE", "Daniel 13:1")
        assert isinstance(result, str)
        assert len(result) > 30
        assert _no_traceback(result)
        assert not _is_error_like(result) or "susanna" in result.lower(), (
            f"Expected Susanna text, got: {result[:200]}"
        )


# =============================================================================
# LOCAL-ONLY TESTS (no network needed) — Canon boundary validation extras
# =============================================================================

@pytest.mark.skipif(not _has_canon, reason="Canon not available")
class TestCanonBoundariesLocal:
    """Additional local canon boundary checks for QA scenarios."""

    def test_daniel_catholic_14_chapters(self):
        ch = chapter_count("Dan", CANON_CATHOLIC)
        assert ch == 14

    def test_daniel_protestant_12_chapters(self):
        ch = chapter_count("Dan", CANON_PROTESTANT)
        assert ch == 12

    def test_sirach_catholic_51_chapters(self):
        ch = chapter_count("Sir", CANON_CATHOLIC)
        assert ch == 51

    def test_wisdom_in_catholic_not_protestant(self):
        """Wisdom is in Catholic canon but not Protestant."""
        cath_names = {b.osis for b in CANON_CATHOLIC.books}
        prot_names = {b.osis for b in CANON_PROTESTANT.books}
        assert "Wis" in cath_names, "Wisdom missing from Catholic canon"
        assert "Wis" not in prot_names, "Wisdom should not be in Protestant canon"

    def test_tobit_in_catholic_not_protestant(self):
        cath_names = {b.osis for b in CANON_CATHOLIC.books}
        prot_names = {b.osis for b in CANON_PROTESTANT.books}
        assert "Tob" in cath_names, "Tobit missing from Catholic canon"
        assert "Tob" not in prot_names, "Tobit should not be in Protestant canon"

    def test_daniel_14_last_verse(self):
        """Daniel 14 (Bel and Dragon) has valid verse count."""
        last = last_verse_of_chapter("Dan", 14, CANON_CATHOLIC)
        assert last > 0, "Daniel 14 should have verses"

    def test_daniel_13_last_verse(self):
        """Daniel 13 (Susanna) has valid verse count."""
        last = last_verse_of_chapter("Dan", 13, CANON_CATHOLIC)
        assert last > 0, "Daniel 13 should have verses"
