"""
Edge-case and extreme scenario tests for accordance_parser/canon.py.

Tests scenarios NOT already covered by test_canon.py:
  - Bogus/malformed references (SQL injection, unicode homoglyphs, double/mixed dashes)
  - Extreme/boundary verse numbers (chapter 0, verse 0, out-of-range, negative)
  - Deuterocanonical books under Protestant canon
  - Reading plan / canon structural validation (ordering, duplicates)
"""

from accordance_parser.canon import (
    CANON_PROTESTANT,
    CANON_CATHOLIC,
    DEFAULT_CANON,
    resolve_book,
    parse_reference,
    canon_by_name,
    chapter_count,
    last_verse_of_chapter,
)


# =============================================================================
# Category 1: Bogus/malformed references (A1-7)
# =============================================================================

def test_parse_reference_whitespace_only():
    """Whitespace-only strings return None (not just empty string)."""
    assert parse_reference("   ") is None
    assert parse_reference("\t\t") is None
    assert parse_reference(" \n \t ") is None


def test_parse_reference_no_book_name():
    """Strings with chapter:verse but no book name return None."""
    assert parse_reference("1:1") is None
    assert parse_reference(" 1:1") is None
    assert parse_reference("12:34") is None


def test_parse_reference_sql_injection_style():
    """SQL-injection-style strings must not crash; semicolons now delimit
    the first valid reference (Session 74 lectionary support)."""
    # Semicolons separate lectionary references; first segment parses correctly
    result = parse_reference("Genesis 1:1; DROP TABLE verses")
    assert result is not None
    assert result.book_osis == "Gen"
    assert result.chapter == 1
    assert result.verse == 1
    assert parse_reference("Genesis 1:1--DROP") is None
    assert parse_reference("Gen 1:1'; SELECT * FROM users--") is None
    assert parse_reference("Genesis 1:1;--comment") is None


def test_parse_reference_unicode_homoglyphs():
    """Unicode homoglyphs in book names must not match real books."""
    # Cyrillic 'e' (U+0435) looks like Latin 'e' but is distinct
    assert parse_reference("G\u0435nesis 1:1") is None
    # Cyrillic 'а' (U+0430) looks like Latin 'a'
    assert parse_reference("M\u0430tthew 1:1") is None


def test_parse_reference_double_dash():
    """Double dash as range separator returns None (no crash)."""
    assert parse_reference("Gen 1:1--2") is None
    assert parse_reference("Gen 1:1--3") is None
    assert parse_reference("John 3:16--17") is None


def test_parse_reference_mixed_dashes():
    """En-dash (U+2013) and em-dash (U+2014) as range separators."""
    # En-dash between verse and end-verse
    r = parse_reference("Gen 1:1\u20132")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 1
    assert r.verse == 1
    assert r.end_verse == 2

    # Em-dash between verse and end-verse
    r = parse_reference("Gen 1:1\u20142")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 1
    assert r.verse == 1
    assert r.end_verse == 2

    # Standard hyphen for comparison
    r = parse_reference("Gen 1:1-2")
    assert r is not None
    assert r.end_verse == 2


# =============================================================================
# Category 2: Extreme/boundary verse numbers (A1-10)
# =============================================================================

def test_parse_reference_chapter_zero():
    """Chapter 0 parses numerically (no out-of-range validation in parser)."""
    r = parse_reference("Genesis 0:1")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 0
    assert r.verse == 1


def test_parse_reference_verse_zero():
    """Verse 0 parses numerically."""
    r = parse_reference("Genesis 1:0")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 1
    assert r.verse == 0


def test_parse_reference_out_of_range_chapter():
    """Very large chapter (beyond known count) parses numerically."""
    r = parse_reference("Genesis 99:1")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 99
    assert r.verse == 1


def test_parse_reference_out_of_range_verse():
    """Very large verse (beyond known count) parses numerically."""
    r = parse_reference("Genesis 1:999")
    assert r is not None
    assert r.book_osis == "Gen"
    assert r.chapter == 1
    assert r.verse == 999


def test_parse_reference_negative_numbers():
    """Negative numbers do not match the \\d+ regex → return None."""
    assert parse_reference("Genesis -1:1") is None
    assert parse_reference("Genesis 1:-1") is None
    assert parse_reference("Genesis -1:-1") is None


def test_last_verse_boundary_values():
    """Verify last_verse_of_chapter at extreme canonical boundaries."""
    # Longest psalm
    assert last_verse_of_chapter("Ps", 119, CANON_PROTESTANT) == 176
    # Last verse of the Protestant Bible
    assert last_verse_of_chapter("Rev", 22, CANON_PROTESTANT) == 21
    # Single-chapter book boundary
    assert last_verse_of_chapter("Obad", 1, CANON_PROTESTANT) == 21
    # First chapter maximum
    assert last_verse_of_chapter("Gen", 1, CANON_PROTESTANT) == 31
    # Last chapter of the OT
    assert last_verse_of_chapter("Mal", 4, CANON_PROTESTANT) == 6


def test_canon_by_name_error_message():
    """canon_by_name with unknown canon raises ValueError listing available canons."""
    try:
        canon_by_name("bogus_canon")
        assert False, "Should have raised ValueError"
    except ValueError as e:
        msg = str(e)
        assert "bogus_canon" in msg
        assert "protestant" in msg
        assert "catholic" in msg


# =============================================================================
# Category 3: Deuterocanonical under Protestant canon (A1-8)
# =============================================================================

def test_deuterocanonical_resolve_regardless_of_canon():
    """Deuterocanonical books resolve from the 84-book master list regardless of canon."""
    book = resolve_book("Tobit")
    assert book is not None
    assert book.osis == "Tob"

    book = resolve_book("1macc")
    assert book is not None
    assert book.osis == "1Macc"

    book = resolve_book("baruch")
    assert book is not None
    assert book.osis == "Bar"

    book = resolve_book("wisdom")
    assert book is not None
    assert book.osis == "Wis"

    book = resolve_book("sirach")
    assert book is not None
    assert book.osis == "Sir"


def test_deuterocanonical_chapter_counts_catholic():
    """Deuterocanonical books have the expected chapter counts in CANON_CATHOLIC."""
    assert chapter_count("Tob", CANON_CATHOLIC) == 14
    assert chapter_count("Jdt", CANON_CATHOLIC) == 16
    assert chapter_count("1Macc", CANON_CATHOLIC) == 16
    assert chapter_count("2Macc", CANON_CATHOLIC) == 15
    assert chapter_count("Wis", CANON_CATHOLIC) == 19
    assert chapter_count("Sir", CANON_CATHOLIC) == 51
    assert chapter_count("Bar", CANON_CATHOLIC) == 6


def test_deuterocanonical_chapter_counts_protestant():
    """Deuterocanonical books return 0 chapters under CANON_PROTESTANT."""
    assert chapter_count("Tob", CANON_PROTESTANT) == 0
    assert chapter_count("Jdt", CANON_PROTESTANT) == 0
    assert chapter_count("1Macc", CANON_PROTESTANT) == 0
    assert chapter_count("2Macc", CANON_PROTESTANT) == 0
    assert chapter_count("Wis", CANON_PROTESTANT) == 0
    assert chapter_count("Sir", CANON_PROTESTANT) == 0
    assert chapter_count("Bar", CANON_PROTESTANT) == 0


def test_all_catholic_books_have_positive_chapter_counts():
    """Every book in CANON_CATHOLIC (73 books) has chapter_count > 0."""
    for book in CANON_CATHOLIC.books:
        count = chapter_count(book.osis, CANON_CATHOLIC)
        assert count > 0, f"{book.osis} chapter_count = {count}, expected > 0"


def test_all_protestant_books_have_positive_chapter_counts():
    """Every book in CANON_PROTESTANT (66 books) has chapter_count > 0."""
    for book in CANON_PROTESTANT.books:
        count = chapter_count(book.osis, CANON_PROTESTANT)
        assert count > 0, f"{book.osis} chapter_count = {count}, expected > 0"


# =============================================================================
# Category 4: Reading plan / canon structural validation (A3-4)
# =============================================================================

def test_canon_book_counts():
    """Verify exact book counts for each canon."""
    assert len(CANON_PROTESTANT.books) == 66
    assert len(CANON_CATHOLIC.books) == 73


def test_default_canon_is_protestant():
    """DEFAULT_CANON should be CANON_PROTESTANT."""
    assert DEFAULT_CANON is CANON_PROTESTANT


def test_protestant_first_five_books():
    """First five books of Protestant canon are the Torah/Pentateuch in order."""
    first_five = [b.osis for b in CANON_PROTESTANT.books[:5]]
    assert first_five == ["Gen", "Exod", "Lev", "Num", "Deut"]


def test_catholic_deuterocanonical_order():
    """Deuterocanonical books are correctly interspersed in Catholic canon."""
    cath_osis = [b.osis for b in CANON_CATHOLIC.books]

    # Tobit between Nehemiah and Judith
    neh_idx = cath_osis.index("Neh")
    tob_idx = cath_osis.index("Tob")
    jdt_idx = cath_osis.index("Jdt")
    assert tob_idx == neh_idx + 1
    assert jdt_idx == tob_idx + 1

    # 1-2 Maccabees after Esther
    esth_idx = cath_osis.index("Esth")
    m1_idx = cath_osis.index("1Macc")
    m2_idx = cath_osis.index("2Macc")
    assert m1_idx == esth_idx + 1
    assert m2_idx == m1_idx + 1

    # Wisdom, Sirach after Song of Solomon
    song_idx = cath_osis.index("Song")
    wis_idx = cath_osis.index("Wis")
    sir_idx = cath_osis.index("Sir")
    assert wis_idx == song_idx + 1
    assert sir_idx == wis_idx + 1

    # Baruch after Lamentations
    lam_idx = cath_osis.index("Lam")
    bar_idx = cath_osis.index("Bar")
    assert bar_idx == lam_idx + 1


def test_no_duplicate_osis_in_canons():
    """Neither canon should contain duplicate OSIS keys."""
    prot_osis = [b.osis for b in CANON_PROTESTANT.books]
    assert len(prot_osis) == len(set(prot_osis)), (
        "Protestant canon has duplicate OSIS keys"
    )

    cath_osis = [b.osis for b in CANON_CATHOLIC.books]
    assert len(cath_osis) == len(set(cath_osis)), (
        "Catholic canon has duplicate OSIS keys"
    )


def test_daniel_chapter_count_by_canon():
    """Daniel: 12 chapters in Protestant canon, 14 in Catholic canon."""
    assert chapter_count("Dan", CANON_PROTESTANT) == 12
    assert chapter_count("Dan", CANON_CATHOLIC) == 14
