"""Audit all 7 Type 4 Hebrew lexicon modules for loading, lookup, search,
entry counts, and text quality.

Usage:
    cd "/Users/jacobgruber/Projects/Accordance Port"
    .venv/bin/python3 -m pytest test_audit_hebrew_lexicons.py -v
"""

import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

import pytest
from accordance_parser.lexicon_module import LexiconModule
from accordance_parser.discovery import discover_modules


def _is_hebrew_unicode(text: str) -> bool:
    return any('\u0590' <= c <= '\u05FF' for c in text)


def _has_consonants(text: str) -> bool:
    return sum(1 for c in text if '\u05D0' <= c <= '\u05EA') >= 2


_HEBREW_MODULE_NAMES = {
    "BDB Complete", "HALOT", "Hebrew Strong\u2019s", "NAS Hebrew",
    "TDOT", "Jenni-Westermann", "KM Hebrew Dictionary",
}


def _find_hebrew_modules():
    all_mods = discover_modules()
    heb = {}
    for abbr, info in all_mods.items():
        if info["type"] != 4:
            continue
        if abbr in _HEBREW_MODULE_NAMES:
            heb[abbr] = info
    return heb


@pytest.fixture(scope="module")
def heb_modules():
    return _find_hebrew_modules()


# ────────────────────────────────────────────────────────────────────────
# 1. Loading
# ────────────────────────────────────────────────────────────────────────

def test_all_seven_modules_found(heb_modules):
    found = set(heb_modules.keys())
    missing = _HEBREW_MODULE_NAMES - found
    assert len(heb_modules) >= 2, f"Expected >=2 Hebrew modules; found {len(heb_modules)}: {found}"
    if missing:
        print(f"\n  NOTE: {len(missing)} modules not installed: {missing}")


@pytest.mark.parametrize("abbrev", sorted(_HEBREW_MODULE_NAMES))
def test_load_module(abbrev, heb_modules):
    info = heb_modules.get(abbrev)
    if info is None:
        pytest.skip(f"Module '{abbrev}' not installed")
    lex = LexiconModule(info["path"])
    hw_count = len(lex.headwords())
    print(f"\n  {abbrev}: language={lex.language}, entries={hw_count}, format={lex.format}")
    assert lex.language == "Hebrew", f"{abbrev}: expected Hebrew, got '{lex.language}'"


# ────────────────────────────────────────────────────────────────────────
# 2. Lookup correctness
# ────────────────────────────────────────────────────────────────────────

def test_bdb_lookup_h430(heb_modules):
    info = heb_modules.get("BDB Complete")
    if info is None:
        pytest.skip("BDB Complete not installed")
    lex = LexiconModule(info["path"])
    result = lex.lookup("H430")
    assert result is not None, "BDB: H430 returned None"
    assert len(result) > 200, f"BDB H430 entry short: {len(result)} chars"
    lower = result.lower()
    assert ("god" in lower or "elohim" in lower or "deity" in lower), f"BDB H430 not about God. First 200: {result[:200]}"
    assert _is_hebrew_unicode(result), f"BDB H430 has no Hebrew Unicode. First 100: {result[:100]}"


def test_halot_lookup_av(heb_modules):
    info = heb_modules.get("HALOT")
    if info is None:
        pytest.skip("HALOT not installed")
    lex = LexiconModule(info["path"])
    hw = "\u05d0\u05b8\u05d1"  # אָב
    result = lex.lookup(hw)
    assert result is not None, f"HALOT: lookup({hw!r}) returned None"
    assert len(result) > 100, f"HALOT {hw} entry short: {len(result)} chars"
    assert _is_hebrew_unicode(result), f"HALOT {hw} no Hebrew Unicode. First 200: {result[:200]}"


def test_hebrew_strongs_lookup_5650(heb_modules):
    info = heb_modules.get("Hebrew Strong\u2019s")
    if info is None:
        pytest.skip("Hebrew Strong's not installed")
    lex = LexiconModule(info["path"])
    result = lex.lookup("5650")
    assert result is not None, "Hebrew Strong's: lookup('5650') returned None"
    assert len(result) > 20, f"Hebrew Strong's 5650 short: {len(result)} chars"


def test_nas_hebrew_lookup_h430(heb_modules):
    info = heb_modules.get("NAS Hebrew")
    if info is None:
        pytest.skip("NAS Hebrew not installed")
    lex = LexiconModule(info["path"])
    result = lex.lookup("H430")
    if result is None:
        result = lex.lookup("430")
    assert result is not None, "NAS Hebrew: lookup(H430) and lookup(430) both returned None"
    assert len(result) > 20, f"NAS Hebrew H430 short: {len(result)} chars"
    lower = result.lower()
    assert ("god" in lower or "elohim" in lower or "deity" in lower), f"NAS Hebrew H430 not God. First 200: {result[:200]}"


def test_tdot_first_headword(heb_modules):
    info = heb_modules.get("TDOT")
    if info is None:
        pytest.skip("TDOT not installed")
    lex = LexiconModule(info["path"])
    hws = lex.headwords()
    assert len(hws) > 50, f"TDOT expected >50 entries, got {len(hws)}. Known ~362."
    first = hws[0]
    result = lex.lookup(first)
    assert result is not None, f"TDOT: lookup({first!r}) returned None"
    assert len(result) > 40, f"TDOT {first!r} entry short: {len(result)} chars"


def test_jenni_westermann_zero_entries(heb_modules):
    info = heb_modules.get("Jenni-Westermann")
    if info is None:
        pytest.skip("Jenni-Westermann not installed")
    lex = LexiconModule(info["path"])
    hws = lex.headwords()
    # Jenni-Westermann previously had 0 entries; now has 326
    # Verify it loads and handles lookups gracefully
    assert isinstance(hws, list), f"Jenni-Westermann headwords should be list, got {type(hws)}"
    # lookup for nonexistent should return None
    assert lex.lookup("xyzzy_not_hebrew_99999") is None


def test_km_hebrew_loads(heb_modules):
    info = heb_modules.get("KM Hebrew Dictionary")
    if info is None:
        pytest.skip("KM Hebrew Dictionary not installed")
    lex = LexiconModule(info["path"])
    hws = lex.headwords()
    assert lex.language == "Hebrew", f"KM Hebrew: language={lex.language}, expected Hebrew"
    strong_keys = [k for k in hws if k.startswith("H")]
    if strong_keys:
        result = lex.lookup(strong_keys[0])
        assert result is not None, f"KM Hebrew: lookup({strong_keys[0]!r}) returned None"


# ────────────────────────────────────────────────────────────────────────
# 3. Search (find)
# ────────────────────────────────────────────────────────────────────────

def test_bdb_find(heb_modules):
    info = heb_modules.get("BDB Complete")
    if info is None:
        pytest.skip("BDB Complete not installed")
    lex = LexiconModule(info["path"])
    results = lex.find("\u05d0", max_results=10)
    assert len(results) > 0, "BDB: find('א') returned 0 results"


def test_halot_find(heb_modules):
    info = heb_modules.get("HALOT")
    if info is None:
        pytest.skip("HALOT not installed")
    lex = LexiconModule(info["path"])
    results = lex.find("\u05d0", max_results=10)
    assert len(results) > 0, "HALOT: find('א') returned 0 results"


def test_nas_hebrew_find(heb_modules):
    info = heb_modules.get("NAS Hebrew")
    if info is None:
        pytest.skip("NAS Hebrew not installed")
    lex = LexiconModule(info["path"])
    results = lex.find("56", max_results=10)
    assert len(results) > 0, "NAS Hebrew: find('56') returned 0 results"


# ────────────────────────────────────────────────────────────────────────
# 4. Entry Counts
# ────────────────────────────────────────────────────────────────────────

def test_entry_counts_reasonable(heb_modules):
    expected_ranges = {
        "BDB Complete":         (8_000, 14_000),
        "HALOT":                (3_500, 10_000),
        "Hebrew Strong's":      (5_000, 12_000),
        "NAS Hebrew":           (5_000, 15_000),
        "TDOT":                 (200,   1_500),
        "Jenni-Westermann":     (0,      500),  # 326 found — module has entries now
        "KM Hebrew Dictionary": (0,   100_000),
    }
    failures = []
    for abbrev, (lo, hi) in expected_ranges.items():
        info = heb_modules.get(abbrev)
        if info is None:
            continue
        lex = LexiconModule(info["path"])
        count = len(lex.headwords())
        if not (lo <= count <= hi):
            failures.append(f"{abbrev}: expected {lo}-{hi} entries, got {count}")
    if failures:
        pytest.fail("Entry count(s) out of range:\n" + "\n".join(failures))


# ────────────────────────────────────────────────────────────────────────
# 5. Text quality
# ────────────────────────────────────────────────────────────────────────

def test_hebrew_rendering_in_bdb(heb_modules):
    info = heb_modules.get("BDB Complete")
    if info is None:
        pytest.skip("BDB Complete not installed")
    lex = LexiconModule(info["path"])
    for key in ["H430", "H120"]:
        result = lex.lookup(key)
        assert result is not None, f"BDB {key} not found"
        assert _has_consonants(result), f"BDB {key} has no Hebrew consonants. First 200: {result[:200]}"


def test_no_undecoded_control_chars(heb_modules):
    c1_chars = set(chr(i) for i in range(0x80, 0xA0))
    for abbrev in sorted(_HEBREW_MODULE_NAMES):
        info = heb_modules.get(abbrev)
        if info is None:
            continue
        lex = LexiconModule(info["path"])
        hws = lex.headwords()
        if not hws:
            continue
        sample = hws[:min(10, len(hws))]
        for hw in sample:
            result = lex.lookup(hw)
            if result is None:
                continue
            bad = [c for c in result if c in c1_chars]
            assert not bad, f"{abbrev}: entry {hw[:20]!r} has {len(bad)} C1 chars"


# ────────────────────────────────────────────────────────────────────────
# 6. Cross-module consistency
# ────────────────────────────────────────────────────────────────────────

def test_strongs_consistency(heb_modules):
    tests = [
        ("BDB Complete", "H430",  ["god", "elohim", "deity", "divine"]),
        ("BDB Complete", "H1254", ["create", "make", "form"]),
        ("NAS Hebrew", "H430",   ["god", "elohim", "deity", "divine"]),
    ]
    for abbrev, sn, keywords in tests:
        info = heb_modules.get(abbrev)
        if info is None:
            continue
        lex = LexiconModule(info["path"])
        result = lex.lookup(sn)
        if result is None:
            result = lex.lookup(sn[1:])
        if result is None:
            continue
        lower = result.lower()
        matched = [kw for kw in keywords if kw in lower]
        assert matched, f"{abbrev} {sn}: no keywords {keywords} found. First 300: {result[:300]}"


# ────────────────────────────────────────────────────────────────────────
# 7. Edge cases
# ────────────────────────────────────────────────────────────────────────

def test_lookup_empty_string(heb_modules):
    for abbrev, info in heb_modules.items():
        lex = LexiconModule(info["path"])
        assert lex.lookup("") is None, f"{abbrev}: lookup('') should be None"


def test_lookup_garbage(heb_modules):
    for abbrev, info in heb_modules.items():
        lex = LexiconModule(info["path"])
        assert lex.lookup("xyzzy_not_a_real_word_12345") is None, f"{abbrev}: garbage lookup should be None"


def test_find_empty(heb_modules):
    for abbrev, info in heb_modules.items():
        lex = LexiconModule(info["path"])
        assert isinstance(lex.find(""), list), f"{abbrev}: find('') should return list"


def test_find_max_results_zero(heb_modules):
    for abbrev, info in heb_modules.items():
        lex = LexiconModule(info["path"])
        assert lex.find("a", max_results=0) == [], f"{abbrev}: find('a', max_results=0) should be []"
