"""Unit tests for TheoLing concordance generator.

These tests mock `search_verses_by_strongs` and `study_strongs_number`
so no running MCP server is required.
"""

from __future__ import annotations

import pytest

from theoling.concordance import (
    ConcordanceEntry,
    generate_concordance,
    _parse_verses,
    _parse_lexicons,
    _parse_total_from_study,
)


# ---------------------------------------------------------------------------
# Sample markdown for mocking the two source functions
# ---------------------------------------------------------------------------

MOCK_VERSES_H2617 = """# Verses with Strong's [H2617]

**Total occurrences in KJV:** 248

## Genesis
**Gen 24:12** — And he said, O LORD God of my master Abraham, I pray thee, send me good speed this day, and shew kindness unto my master Abraham.
**Gen 24:14** — And let it come to pass, that the damsel to whom I shall say, Let down thy pitcher, I pray thee, that I may drink; and she shall say, Drink, and I will give thy camels drink also: let the same be she that thou hast appointed for thy servant Isaac; and thereby shall I know that thou hast shewed kindness unto my master.
**Gen 24:27** — And he said, Blessed be the LORD God of my master Abraham, who hath not left destitute my master of his mercy and his truth: I being in the way, the LORD led me to the house of my master's brethren.

## Exodus
**Exod 15:13** — Thou in thy mercy hast led forth the people which thou hast redeemed: thou hast guided them in thy strength unto thy holy habitation.

*(+244 more occurrences not shown. Increase `max_results` to see more.)*
"""

MOCK_STUDY_H2617 = """# Word Study: H2617

## Lexicon Definition

**Brown-Driver-Briggs (BDB):**

H2617  חֶסֶד  checed

1) goodness, kindness, faithfulness
2) a reproach, shame

**Strong's Hebrew (H):**

(H2617) חֶסֶד cheçed, kheh'-sed

From H2616; kindness; by implication (towards God) piety; rarely (by opprobrium) reproof, or (subjectively) beauty:—favour, good deed (-liness, -ness), kindly, (loving-) kindness, merciful (kindness), mercy, pity, reproach, wicked thing.

## Usage in KJV: 248 total occurrences

**Book distribution:**
- Psalms: 127×
- Genesis: 11×
- Isaiah: 8×
- Jeremiah: 8×
- Proverbs: 8×
  ...(+20 more books)

**First 10 occurrences:**
- Gen 24:12
- Gen 24:14
- Gen 24:27
- Gen 32:10
- Gen 39:21
"""

MOCK_VERSES_G26 = """# Verses with Strong's [G0026]

**Total occurrences in KJV:** 116

## John
**John 3:16** — For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life.
**John 3:35** — The Father loveth the Son, and hath given all things into his hand.

## Romans
**Rom 5:8** — But God commendeth his love toward us, in that, while we were yet sinners, Christ died for us.
**Rom 8:37** — Nay, in all these things we are more than conquerors through him that loved us.
"""

MOCK_STUDY_G26 = """# Word Study: G0026

## Lexicon Definition

**Thayer (THAYER):**

G26  ἀγάπη  agape

1) brotherly love, affection, good will, love, benevolence
2) love feasts

**Strong's Greek (G):**

(G26) ἀγάπη agapē, ag-ah'-pay

From G25; love, that is, affection or benevolence; specifically (plural) a love feast:—(feast of) charity ([-ably]), dear, love.

## Usage in KJV: 116 total occurrences

**Book distribution:**
- 1 John: 18×
- John: 7×
- Romans: 7×
  ...(+14 more books)

**First 10 occurrences:**
- Matt 24:12
- Luke 11:42
- John 5:42
- John 13:35
- John 15:9
"""

MOCK_NO_RESULTS = """# Verses with Strong's [G9999]

**Total occurrences in KJV:** 0

No verses found.
"""

MOCK_STUDY_NO_LEXICON = """# Word Study: G9999

## Lexicon Definition

*(No Greek lexicon definition found for G9999)*

## Usage in KJV: 0 total occurrences
"""


# ---------------------------------------------------------------------------
# Test helpers
# ---------------------------------------------------------------------------

def _search_mock_h2617(number: str, max_results: int = 50) -> str:
    # Return only first max_results verse lines
    verses = MOCK_VERSES_H2617.split("\n")
    header = []
    verse_lines = []
    in_verse = False
    count = 0
    for line in verses:
        if line.startswith("**Total occurrences"):
            header.append(line); header.append("")
            continue
        if line.startswith("## "):
            header.append(line); header.append("")
            in_verse = True
            continue
        if in_verse and line.startswith("**") and " — " in line:
            if count >= max_results:
                continue
            count += 1
            verse_lines.append(line)
            continue
        if not line.startswith("**") and not line.startswith("("):
            continue
    total = 248
    result = "\n".join(
        ["# Verses with Strong's [H2617]", "",
         f"**Total occurrences in KJV:** {total}", ""]
        + header
        + verse_lines
    )
    return result


def _study_mock_h2617(number: str) -> str:
    return MOCK_STUDY_H2617

def _search_mock_g26(number: str, max_results: int = 50) -> str:
    return MOCK_VERSES_G26

def _study_mock_g26(number: str) -> str:
    return MOCK_STUDY_G26

def _search_mock_empty(number: str, max_results: int = 50) -> str:
    return MOCK_NO_RESULTS

def _study_mock_empty(number: str) -> str:
    return MOCK_STUDY_NO_LEXICON


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestHebrewStrongs:
    """Concordance with a Hebrew Strong's number (H2617 — chesed)."""

    def test_generates_valid_entry(self):
        entry = generate_concordance(
            "H2617", max_verses=4,
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        assert isinstance(entry, ConcordanceEntry)
        assert entry.strongs_number == "H2617"
        assert entry.total_occurrences > 0

    def test_parses_lexicon_definitions(self):
        entry = generate_concordance(
            "H2617",
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        assert len(entry.lexicon_entries) >= 1
        names = [le.name for le in entry.lexicon_entries]
        assert any("Brown-Driver-Briggs" in n for n in names)
        assert any("Strong" in n for n in names)

    def test_parses_verse_occurrences(self):
        entry = generate_concordance(
            "H2617", max_verses=4,
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        assert len(entry.verses) == 4
        assert entry.verses[0].reference == "Gen 24:12"
        assert "mercy" in entry.verses[2].text.lower() or \
               "kindness" in entry.verses[2].text.lower()

    def test_summary_contains_key_info(self):
        entry = generate_concordance(
            "H2617",
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        summary = entry.summary()
        assert "H2617" in summary
        assert "248" in summary or "lexicon" in summary.lower()

    def test_to_dict_is_serializable(self):
        entry = generate_concordance(
            "H2617", max_verses=2,
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        d = entry.to_dict()
        assert d["strongs_number"] == "H2617"
        assert "lexicon_entries" in d
        assert "verses" in d
        assert isinstance(d["verses"], list)


class TestGreekStrongs:
    """Concordance with a Greek Strong's number (G26 — agape)."""

    def test_greek_entry_parses_correctly(self):
        entry = generate_concordance(
            "G26",
            _search_fn=_search_mock_g26,
            _study_fn=_study_mock_g26,
        )
        assert entry.strongs_number == "G26"
        assert entry.total_occurrences == 116
        assert len(entry.verses) == 4
        assert "John 3:16" in entry.verses[0].reference

    def test_lexicon_names_parsed(self):
        entry = generate_concordance(
            "G26",
            _search_fn=_search_mock_g26,
            _study_fn=_study_mock_g26,
        )
        abbreviations = {le.abbreviation for le in entry.lexicon_entries}
        assert "THAYER" in abbreviations or "G" in abbreviations

    def test_markdown_includes_lexicon_and_verses(self):
        entry = generate_concordance(
            "G26",
            _search_fn=_search_mock_g26,
            _study_fn=_study_mock_g26,
        )
        md = entry.markdown
        assert "Concordance: G26" in md
        assert "Lexicon Definitions" in md
        assert "Verse Occurrences" in md
        assert "John 3:16" in md


class TestInvalidInput:
    """Input validation rejects bad Strong's numbers."""

    def test_no_prefix_raises(self):
        with pytest.raises(ValueError, match="Invalid Strong"):
            generate_concordance("3068")

    def test_letters_only_raises(self):
        with pytest.raises(ValueError, match="Invalid Strong"):
            generate_concordance("abc")

    def test_empty_string_raises(self):
        with pytest.raises(ValueError, match="Invalid Strong"):
            generate_concordance("")

    def test_whitespace_only_raises(self):
        with pytest.raises(ValueError, match="Invalid Strong"):
            generate_concordance("   ")

    def test_lowercase_normalizes(self):
        """Lowercase 'h2617' should be normalised to 'H2617'."""
        entry = generate_concordance(
            "h2617",
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        assert entry.strongs_number == "H2617"


class TestEmptyResults:
    """Graceful handling when a Strong's number has no results."""

    def test_empty_search_produces_valid_entry(self):
        entry = generate_concordance(
            "G9999",
            _search_fn=_search_mock_empty,
            _study_fn=_study_mock_empty,
        )
        assert isinstance(entry, ConcordanceEntry)
        assert entry.strongs_number == "G9999"
        assert entry.total_occurrences == 0
        assert len(entry.verses) == 0
        assert len(entry.lexicon_entries) == 0


class TestMaxVerses:
    """max_verses parameter caps output correctly."""

    def test_max_verses_3_limits_output(self):
        entry = generate_concordance(
            "H2617", max_verses=3,
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        assert len(entry.verses) == 3


class TestMarkdownParsers:
    """Unit tests for the internal markdown parsing helpers."""

    def test_parse_verses_from_standard_format(self):
        verses, total = _parse_verses(MOCK_VERSES_H2617)
        assert total == 248
        assert len(verses) == 4
        assert verses[0].reference == "Gen 24:12"
        assert "kindness" in verses[0].text

    def test_parse_verses_from_greek_format(self):
        verses, total = _parse_verses(MOCK_VERSES_G26)
        assert total == 116
        assert len(verses) == 4
        assert verses[0].reference == "John 3:16"

    def test_parse_verses_empty_when_no_results(self):
        verses, total = _parse_verses(MOCK_NO_RESULTS)
        assert total == 0
        assert len(verses) == 0

    def test_parse_lexicons_from_hebrew_study(self):
        entries = _parse_lexicons(MOCK_STUDY_H2617)
        assert len(entries) >= 2
        names = {le.name for le in entries}
        assert "Brown-Driver-Briggs" in names

    def test_parse_lexicons_from_greek_study(self):
        entries = _parse_lexicons(MOCK_STUDY_G26)
        assert len(entries) >= 2
        abbreviations = {le.abbreviation for le in entries}
        assert "THAYER" in abbreviations

    def test_parse_total_from_study(self):
        total = _parse_total_from_study(MOCK_STUDY_H2617)
        assert total == 248

    def test_parse_total_from_greek_study(self):
        total = _parse_total_from_study(MOCK_STUDY_G26)
        assert total == 116

    def test_lexicon_definitions_not_empty(self):
        entries = _parse_lexicons(MOCK_STUDY_H2617)
        for le in entries:
            assert len(le.definition) > 10, \
                f"Definition for {le.name} too short"


class TestEntryConvenienceMethods:
    """ConcordanceEntry convenience methods behave correctly."""

    def test_repr_is_readable(self):
        entry = generate_concordance(
            "H2617", max_verses=2,
            _search_fn=_search_mock_h2617,
            _study_fn=_study_mock_h2617,
        )
        r = repr(entry)
        assert "ConcordanceEntry" in r
        assert "H2617" in r

    def test_to_dict_round_trips_keys(self):
        entry = generate_concordance(
            "G26",
            _search_fn=_search_mock_g26,
            _study_fn=_study_mock_g26,
        )
        d = entry.to_dict()
        for key in ("strongs_number", "lexicon_entries", "total_occurrences", "verses"):
            assert key in d

    def test_to_markdown_returns_string(self):
        entry = generate_concordance(
            "G26",
            _search_fn=_search_mock_g26,
            _study_fn=_study_mock_g26,
        )
        md = entry.to_markdown()
        assert isinstance(md, str)
        assert len(md) > 50
