"""Tests for 12 Catholic layperson scenarios for the Accordance Port project.

S-L-01 through S-L-12: covers Sunday Mass prep, daily readings, apologetics,
word studies, lectionary, deuterocanonicals, sacrament preparation, and
daily devotional patterns for a Catholic layperson using the tool suite.
"""
from __future__ import annotations
import pytest

# ===========================================================================
# Guard variables — try importing every dependency
# ===========================================================================

_canon = False
_bible_api = False
_mcp = False
_acc2 = False
_strongs_tools = False
_search_tools = False
_multi_tools = False
_network = False

# --- Canon ---
try:
    from accordance_parser.canon import (
        parse_reference, resolve_book,
    )
    _canon = True
except Exception:
    pass

# --- Bible API ---
try:
    from accordance_parser.bible_api import get_verse
    _bible_api = True
except Exception:
    pass

# --- MCP server functions (defined directly in accordance_mcp_server.py) ---
try:
    from accordance_mcp_server import (
        get_nabre_verse,
        daily_mass_readings,
        get_strongs_for_verse,
        interlinear_display,
        lookup_strongs_definition,
        study_strongs_number,
        word_study,
        search_commentaries_for_topic,
        search_commentaries,
        search_dictionary,
        search_dictionary_content,
        compare_translations,
        compare_catholic_translations,
        find_cross_references,
    )
    _mcp = True
except Exception:
    pass

# --- Acc2 tools (find_ot_quotations, find_parallel_passages) ---
try:
    from accordance_services.tools.acc2 import find_ot_quotations, find_parallel_passages
    _acc2 = True
except Exception:
    pass

# --- Strong's search tools ---
try:
    from accordance_services.tools.strongs import search_verses_by_strongs
    _strongs_tools = True
except Exception:
    pass

# --- Search tools (search_verses_by_term) ---
try:
    from accordance_services.tools.search import search_verses_by_term
    _search_tools = True
except Exception:
    pass

# --- Multi-tools (get_book_list) ---
try:
    from accordance_services.tools.text import get_book_list
    _multi_tools = True
except Exception:
    pass

# --- Network detection (some tests need bolls.life API) ---
try:
    r = get_verse("Genesis 1:1", "KJV")
    if r and r.text:
        _network = True
except Exception:
    pass


# ===========================================================================
# Helper: check output is a valid string (no traceback, non-empty)
# ===========================================================================

def _str_ok(text: str) -> bool:
    """Return True if text is a non-empty string with no Python traceback."""
    if not isinstance(text, str):
        return False
    if not text.strip():
        return False
    if "Traceback (most recent call last)" in text:
        return False
    return True


# ===========================================================================
# S-L-01: Sunday Mass Prep with Greek
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL01SundayMassPrep:
    """S-L-01: Sunday Mass prep — daily readings + Strong's + commentary on Real Presence."""

    def test_daily_mass_readings_returns_structure(self):
        """Daily Mass readings should return a non-empty string with structured content."""
        result = daily_mass_readings("")
        assert _str_ok(result)
        # Should contain heading marks or reading labels
        assert any(
            mark in result
            for mark in (
                "**Season:**", "**Season", "Reading", "reading",
                "Gospel", "gospel", "# Daily Mass",
            )
        ), f"No reading structure found in: {result[:200]}"

    def test_strongs_for_gospel_john6(self):
        """get_strongs_for_verse on John 6:53 should return Strong's numbers."""
        result = get_strongs_for_verse("John 6:53")
        assert _str_ok(result)
        # Should contain Strong's brackets or a graceful message
        assert "[" in result or "G" in result or "No Strong" in result

    def test_interlinear_john6(self):
        """interlinear_display on John 6:53 should return a word table."""
        result = interlinear_display("John 6:53")
        assert _str_ok(result)
        assert any(
            word in result.lower() for word in ("word", "strong", "interlinear", "|", "transliteration")
        )

    def test_search_commentaries_real_presence(self):
        """search_commentaries_for_topic('Real Presence') should not crash."""
        result = search_commentaries_for_topic("Real Presence", max_results=5)
        assert isinstance(result, str)
        # Either has results or a graceful no-results message
        assert len(result) > 0


# ===========================================================================
# S-L-02: Daily Mass Unity
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL02DailyMassUnity:
    """S-L-02: Daily Mass readings weekday + verse search + OT quotations + translation compare."""

    def test_daily_readings_weekday(self):
        """Daily Mass readings for a weekday should return structure."""
        result = daily_mass_readings("2026-05-26")  # Tuesday
        assert isinstance(result, str)
        assert len(result) > 0
        # Should not crash; structure may vary by date coverage

    def test_compare_catholic_james(self):
        """compare_catholic_translations for James 1:2-4 should return a table."""
        result = compare_catholic_translations("James 1:2-4")
        assert _str_ok(result)
        # Should contain NABRE, NRSVCE, or DRB labels
        has_translation = any(
            label in result
            for label in ("NABRE", "NRSVCE", "DRB", "KJV", "Translation", "|")
        )
        assert has_translation, f"No translation labels in: {result[:200]}"

    @pytest.mark.skipif(not _acc2, reason="acc2 tools not importable")
    def test_find_ot_quotations_mark8(self):
        """find_ot_quotations on Mark 8:12 should not crash."""
        result = find_ot_quotations("Mark 8:12")
        assert isinstance(result, str)
        assert len(result) > 0

    @pytest.mark.skipif(not _search_tools, reason="search tools not importable")
    def test_search_verses_term_test(self):
        """search_verses_by_term('test') should return results."""
        result = search_verses_by_term("test", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# S-L-03: Real Presence — John 6
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL03RealPresenceJohn6:
    """S-L-03: Real Presence in John 6 — NABRE, Strong's, word study, translations."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_john6_54(self):
        """get_nabre_verse('John 6:54') should return the Bread of Life discourse."""
        result = get_nabre_verse("John 6:54")
        assert _str_ok(result)
        # Should mention eating/drinking/flesh/blood — Jesus's Real Presence words
        assert any(
            word in result.lower()
            for word in ("flesh", "blood", "eat", "drink", "life", "bread")
        ), f"Unexpected content: {result[:200]}"

    def test_strongs_john6_54(self):
        """get_strongs_for_verse on John 6:54 should return Strong's numbers."""
        result = get_strongs_for_verse("John 6:54")
        assert _str_ok(result)

    def test_study_G5176(self):
        """study_strongs_number('G5176' - trogo/gnaw) should return definition."""
        result = study_strongs_number("G5176")
        assert _str_ok(result)
        # Should contain a definition or graceful message about missing lexicon
        assert "G5176" in result or "G5176" in result or "definition" in result.lower()

    @pytest.mark.skipif(not _network, reason="Requires network")
    def test_compare_john6_54(self):
        """compare_translations on John 6:54 should return multiple translations."""
        result = compare_translations("John 6:54", "NABRE,ESV,NIV,KJV")
        assert _str_ok(result)
        assert any(
            label in result for label in ("NABRE", "ESV", "NIV", "KJV", "Translation")
        )


# ===========================================================================
# S-L-04: Mary — Full of Grace (Luke 1:28)
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL04MaryFullOfGrace:
    """S-L-04: Luke 1:28 — kecharitomene study across NABRE, interlinear, Strong's, translations."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_luke1_28(self):
        """get_nabre_verse('Luke 1:28') should return the Annunciation."""
        result = get_nabre_verse("Luke 1:28")
        assert _str_ok(result)
        assert any(
            word in result.lower()
            for word in ("hail", "favored", "lord", "gabriel", "mary", "grace")
        ), f"Unexpected content: {result[:200]}"

    def test_interlinear_luke1_28(self):
        """interlinear_display on Luke 1:28 should show word-by-word."""
        result = interlinear_display("Luke 1:28")
        assert _str_ok(result)
        # Should contain interlinear table or a graceful message

    def test_lookup_G5487(self):
        """lookup_strongs_definition('G5487' - charitoo) should return definition."""
        result = lookup_strongs_definition("G5487")
        assert _str_ok(result)
        # G5487 = χαριτόω — "to grace, favor highly"
        # Should return either a definition or "No definition found"
        assert "G5487" in result or "G5487" in result or "No definition" in result

    @pytest.mark.skipif(not _strongs_tools, reason="Strong's search not importable")
    def test_search_by_G5487(self):
        """search_verses_by_strongs('G5487') should find Ephesians 1:6."""
        result = search_verses_by_strongs("G5487", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_compare_catholic_luke1_28(self):
        """compare_catholic_translations on Luke 1:28 should render Catholic versions."""
        result = compare_catholic_translations("Luke 1:28")
        assert _str_ok(result)
        # Should show NABRE, NRSVCE, or DRB
        has_label = any(
            label in result for label in ("NABRE", "NRSVCE", "DRB", "KJV")
        )
        assert has_label, f"No Catholic translation labels: {result[:200]}"


# ===========================================================================
# S-L-05: Lectio Divina — Psalm 23
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL05LectioDivinaPsalm23:
    """S-L-05: Lectio Divina on Psalm 23 — NABRE, interlinear, Strong's, shepherd study."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_psalm23_1(self):
        """get_nabre_verse('Psalm 23:1') should return 'The Lord is my shepherd'."""
        result = get_nabre_verse("Psalm 23:1")
        assert _str_ok(result)
        assert any(
            word in result.lower()
            for word in ("lord", "shepherd", "psalm", "want")
        ), f"Unexpected content: {result[:200]}"

    def test_interlinear_psalm23_1(self):
        """interlinear_display on Psalm 23:1 should show Hebrew-based interlinear."""
        result = interlinear_display("Psalm 23:1")
        assert _str_ok(result)
        # Should contain interlinear markdown or words

    def test_strongs_psalm23_1(self):
        """get_strongs_for_verse on Psalm 23:1 should return H-prefix Strong's."""
        result = get_strongs_for_verse("Psalm 23:1")
        assert _str_ok(result)
        # Should have H-prefix numbers (Hebrew) or graceful message

    @pytest.mark.skipif(not _strongs_tools, reason="Strong's search not importable")
    def test_search_H7462(self):
        """search_verses_by_strongs('H7462' - ra'ah/shepherd) should find Psalm 23."""
        result = search_verses_by_strongs("H7462", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_search_commentaries_psalm23_5(self):
        """search_commentaries('Psalm 23:5') should not crash."""
        result = search_commentaries("Psalm 23:5")
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# S-L-06: Divine Mercy Word Study
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL06DivineMercyWordStudy:
    """S-L-06: Divine Mercy — mercy word study, eleos (G1656), OT quotations on Luke 1:50."""

    def test_search_dictionary_mercy(self):
        """search_dictionary('mercy') should return headwords across lexicons."""
        result = search_dictionary("mercy")
        assert isinstance(result, str)
        assert len(result) > 0
        # May return results or "No headwords matching" — both valid

    def test_search_dictionary_content_mercy(self):
        """search_dictionary_content('mercy') should search inside definitions."""
        result = search_dictionary_content("mercy", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_study_G1656(self):
        """study_strongs_number('G1656' - eleos/mercy) should return definition."""
        result = study_strongs_number("G1656")
        assert _str_ok(result)
        assert "G1656" in result or "No definition" in result

    @pytest.mark.skipif(not _strongs_tools, reason="Strong's search not importable")
    def test_search_by_G1656(self):
        """search_verses_by_strongs('G1656') should find mercy verses."""
        result = search_verses_by_strongs("G1656", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0

    @pytest.mark.skipif(not _acc2, reason="acc2 tools not importable")
    def test_ot_quotations_luke1_50(self):
        """find_ot_quotations on Luke 1:50 (Magnificat mercy) should not crash."""
        result = find_ot_quotations("Luke 1:50")
        assert isinstance(result, str)
        assert len(result) > 0

    def test_word_study_luke1_50(self):
        """word_study on Luke 1:50 should return word-by-word Strong's."""
        result = word_study("Luke 1:50", max_words=8)
        assert _str_ok(result)
        # Should contain "Word Study" header or graceful fallback
        assert any(
            phrase in result.lower()
            for phrase in ("word study", "strong", "g1656", "luke")
        ) or "unavailable" in result.lower()


# ===========================================================================
# S-L-07: Purgatory — 2 Maccabees 12 + 1 Corinthians 3
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL07PurgatoryMaccabeesCorinthians:
    """S-L-07: Purgatory — 2 Maccabees 12:45, 1 Corinthians 3:15, translation comparison."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_2macc12_45(self):
        """2 Maccabees 12:45 (prayer for the dead) should return text."""
        result = get_nabre_verse("2 Maccabees 12:45")
        assert _str_ok(result)
        # Should mention prayer/dead/atonement/resurrection
        assert any(
            word in result.lower()
            for word in ("pray", "dead", "atonement", "resurrection", "sin", "holy")
        ), f"Unexpected content: {result[:200]}"

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_1cor3_15(self):
        """1 Corinthians 3:15 (saved through fire) should return text."""
        result = get_nabre_verse("1 Corinthians 3:15")
        assert _str_ok(result)
        assert any(
            word in result.lower()
            for word in ("fire", "saved", "loss", "suffer", "work")
        ), f"Unexpected content: {result[:200]}"

    def test_compare_1cor3_15(self):
        """compare_translations on 1 Corinthians 3:15 should show multiple renderings."""
        result = compare_translations("1 Corinthians 3:15", "NABRE,ESV,KJV,DRB")
        assert _str_ok(result)

    @pytest.mark.skipif(not _acc2, reason="acc2 tools not importable")
    def test_ot_quotations_1cor3_13(self):
        """find_ot_quotations on 1 Corinthians 3:13 (fire tests works) should not crash."""
        result = find_ot_quotations("1 Corinthians 3:13")
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# S-L-08: Call No Man Father — Matthew 23:9
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL08CallNoManFather:
    """S-L-08: Matthew 23:9 vs. 1 Cor 4:15 — 'father' terminology in context."""

    def test_compare_matthew23_9(self):
        """compare_translations on Matthew 23:9 should show variation."""
        result = compare_translations("Matthew 23:9", "NABRE,ESV,KJV,NIV")
        assert _str_ok(result)
        assert "father" in result.lower()

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_1cor4_15(self):
        """1 Corinthians 4:15 (Paul calls himself father in Christ) should return text."""
        result = get_nabre_verse("1 Corinthians 4:15")
        assert _str_ok(result)
        assert "father" in result.lower()

    def test_search_commentaries_matthew23_9(self):
        """search_commentaries on Matthew 23:9 should return commentary."""
        result = search_commentaries("Matthew 23:9")
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# S-L-09: Why Sirach in Catholic Bible?
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL09SirachInCatholicBible:
    """S-L-09: Sirach/Ecclesiasticus — Catholic canon, NABRE, cross-references, translations."""

    @pytest.mark.skipif(not _multi_tools, reason="multi-tools not importable")
    def test_get_book_list_has_sirach(self):
        """get_book_list('catholic') should include Sirach."""
        result = get_book_list("catholic")
        assert isinstance(result, str)
        assert len(result) > 0
        # Should mention Sirach or Sir (OSIS abbreviation)
        assert any(
            name in result for name in ("Sirach", "Sir", "Ecclesiasticus")
        ), f"Sirach not found in: {result[:300]}"

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_sirach1_1(self):
        """Sirach 1:1-10 should return wisdom text."""
        result = get_nabre_verse("Sirach 1:1-10")
        assert _str_ok(result)
        # Should contain wisdom themes
        assert any(
            word in result.lower()
            for word in ("wisdom", "lord", "god", "fear", "knowledge")
        ), f"Unexpected content: {result[:200]}"

    def test_cross_refs_sirach51_23(self):
        """find_cross_references on Sirach 51:23 should not crash."""
        result = find_cross_references("Sirach 51:23")
        assert isinstance(result, str)
        assert len(result) > 0
        # May not have Strong's for deuterocanonical verse — that's OK
        # Accept either cross-references or graceful unavailable message

    def test_compare_translations_sirach1_1(self):
        """compare_translations on Sirach 1:1 should work."""
        result = compare_translations("Sirach 1:1", "NABRE,DRB")
        assert _str_ok(result)


# ===========================================================================
# S-L-10: Tobit / Raphael — Intercessory Prayer
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL10TobitRaphael:
    """S-L-10: Tobit 12:12 — Raphael presents prayers; intercessory angelic prayer."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_tobit12_12(self):
        """Tobit 12:12 (Raphael presents prayers) should return text."""
        result = get_nabre_verse("Tobit 12:12")
        assert _str_ok(result)
        # Should mention prayer / Raphael / angel / God
        assert any(
            word in result.lower()
            for word in ("prayer", "raphael", "angel", "god", "lord", "present")
        ), f"Unexpected content: {result[:200]}"

    def test_cross_refs_tobit12_12(self):
        """find_cross_references on Tobit 12:12 should not crash."""
        result = find_cross_references("Tobit 12:12")
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# S-L-11: First Communion Prep — Last Supper
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL11FirstCommunionLastSupper:
    """S-L-11: First Communion prep — Last Supper parallels, NABRE, Catholic translations."""

    @pytest.mark.skipif(not _acc2, reason="acc2 tools not importable")
    def test_parallel_passages_matthew26_26(self):
        """find_parallel_passages('Matthew 26:26') should find Last Supper parallels."""
        result = find_parallel_passages("Matthew 26:26")
        assert isinstance(result, str)
        assert len(result) > 0
        # If Gospel parallel modules are installed, should mention Last Supper
        # If not, should return graceful unavailable message
        # Either is valid

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_luke22_19(self):
        """Luke 22:19 (this is my body) should return Eucharist institution."""
        result = get_nabre_verse("Luke 22:19")
        assert _str_ok(result)
        assert any(
            word in result.lower()
            for word in ("body", "bread", "remembrance", "given", "thanks")
        ), f"Unexpected content: {result[:200]}"

    def test_compare_catholic_luke22_19(self):
        """compare_catholic_translations on Luke 22:19 should show Catholic versions."""
        result = compare_catholic_translations("Luke 22:19")
        assert _str_ok(result)
        assert "body" in result.lower() or "NABRE" in result


# ===========================================================================
# S-L-12: Good Samaritan Bedtime
# ===========================================================================

@pytest.mark.skipif(not _mcp, reason="MCP server not importable")
class TestSL12GoodSamaritanBedtime:
    """S-L-12: Good Samaritan — Luke 10:30-35, splagchnizomai (G4697), OT quot. on Luke 10:27."""

    @pytest.mark.skipif(not _network, reason="Requires network for NABRE")
    def test_nabre_luke10_30_35(self):
        """Luke 10:30-35 (Good Samaritan) should return the parable."""
        result = get_nabre_verse("Luke 10:30-35")
        assert _str_ok(result)
        assert any(
            word in result.lower()
            for word in ("samaritan", "compassion", "jericho", "jerusalem", "inn", "robbers")
        ), f"Unexpected content: {result[:200]}"

    def test_strongs_luke10_33(self):
        """get_strongs_for_verse on Luke 10:33 should include G4697 (splagchnizomai)."""
        result = get_strongs_for_verse("Luke 10:33")
        assert _str_ok(result)
        # G4697 = σπλαγχνίζομαι — moved with compassion
        # May or may not appear in Strong's, but result should be valid

    @pytest.mark.skipif(not _strongs_tools, reason="Strong's search not importable")
    def test_search_by_G4697(self):
        """search_verses_by_strongs('G4697' - splagchnizomai/compassion) should find occurrences."""
        result = search_verses_by_strongs("G4697", max_results=10)
        assert isinstance(result, str)
        assert len(result) > 0

    @pytest.mark.skipif(not _acc2, reason="acc2 tools not importable")
    def test_ot_quotations_luke10_27(self):
        """find_ot_quotations on Luke 10:27 (love God/neighbor) should find OT sources."""
        result = find_ot_quotations("Luke 10:27")
        assert isinstance(result, str)
        assert len(result) > 0


# ===========================================================================
# LOCAL-ONLY TESTS — Canon reference parsing (no MCP server needed)
# ===========================================================================

@pytest.mark.skipif(not _canon, reason="Canon module not available")
class TestLayScenarioReferenceParsing:
    """Local-only: verify all scenario references parse correctly in Catholic canon."""

    def test_parse_john_6_54(self):
        r = parse_reference("John 6:54", "catholic")
        assert r is not None
        assert r.book_osis == "John"
        assert r.chapter == 6
        assert r.verse == 54

    def test_parse_luke_1_28(self):
        r = parse_reference("Luke 1:28", "catholic")
        assert r is not None
        assert r.book_osis == "Luke"

    def test_parse_psalm_23_1(self):
        r = parse_reference("Psalm 23:1", "catholic")
        assert r is not None
        assert r.book_osis == "Ps"

    def test_parse_2macc_12_45(self):
        r = parse_reference("2 Maccabees 12:45", "catholic")
        assert r is not None
        assert r.book_osis == "2Macc"

    def test_parse_1cor_3_15(self):
        r = parse_reference("1 Corinthians 3:15", "catholic")
        assert r is not None
        assert r.book_osis == "1Cor"

    def test_parse_matthew_23_9(self):
        r = parse_reference("Matthew 23:9", "catholic")
        assert r is not None
        assert r.book_osis == "Matt"

    def test_parse_1cor_4_15(self):
        r = parse_reference("1 Corinthians 4:15", "catholic")
        assert r is not None
        assert r.book_osis == "1Cor"

    def test_parse_sirach_1_1(self):
        r = parse_reference("Sirach 1:1", "catholic")
        assert r is not None
        assert r.book_osis == "Sir"

    def test_parse_sirach_51_23(self):
        r = parse_reference("Sirach 51:23", "catholic")
        assert r is not None
        assert r.book_osis == "Sir"

    def test_parse_tobit_12_12(self):
        r = parse_reference("Tobit 12:12", "catholic")
        assert r is not None
        assert r.book_osis == "Tob"

    def test_parse_luke_22_19(self):
        r = parse_reference("Luke 22:19", "catholic")
        assert r is not None
        assert r.book_osis == "Luke"

    def test_parse_luke_10_33(self):
        r = parse_reference("Luke 10:33", "catholic")
        assert r is not None
        assert r.book_osis == "Luke"

    def test_deuterocanonical_all_resolve(self):
        """All deuterocanonical books referenced in scenarios should resolve."""
        for book_name in ["Tobit", "Sirach", "2 Maccabees"]:
            b = resolve_book(book_name)
            assert b is not None, f"Failed to resolve {book_name}"
