"""
Audit ~14 Type 6 generic tool modules in the Accordance Port project.

Tests:
  1. Loading: CommentaryModule(path) — no crash, has entries
  2. Format: All should be generic format with section IDs
  3. Content access: list_sections() returns list, get_section_text(N) returns text
  4. Text quality: No null bytes, readable text
  5. Hermeneia AT: Non-0xFF magic module — verify format detection
  6. Spanish modules: Keener-Contexto — verify Spanish content
  7. Edge cases: Empty query, large section numbers, section 0
"""

import os
import sys

import pytest

sys.path.insert(0, os.path.dirname(__file__))

from accordance_parser.commentary_module import CommentaryModule
from accordance_parser.discovery import discover_modules

# ---------------------------------------------------------------------------
# Module discovery
# ---------------------------------------------------------------------------

_TARGET_ABBRS = {
    "Guide to BH Vocab", "Guide to LXX Vocab", "Guide to NT Vocab",
    "Halper-Post Biblical", "Hermeneia AT", "JPS Haggadah",
    "Keener-Context", "Keener-Contexto", "LXX Grammar",
    "Maps Sampler", "Parables & Miracles", "People & Places of Bible",
    "Robertson Greek", "Timeline Sampler",
}

_MODULES = {}
for abbr, info in discover_modules().items():
    if abbr in _TARGET_ABBRS and info["type"] == 6:
        _MODULES[abbr] = info["path"]

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _try_load(path: str):
    """Attempt to load a module; return (mod_or_None, error_or_None)."""
    if not os.path.exists(path):
        return None, f"file not found: {path}"
    try:
        return CommentaryModule(path), None
    except Exception as e:
        return None, f"{type(e).__name__}: {e}"


def _has_no_null_bytes(text: str) -> bool:
    return "\x00" not in text


def _has_readable_text(text: str) -> bool:
    return any(c.isalpha() for c in text)


# ---------------------------------------------------------------------------
# Test 1: Discovery found all 14
# ---------------------------------------------------------------------------

def test_all_14_modules_found():
    assert len(_MODULES) == 14, (
        f"Expected 14 type-6 modules, found {len(_MODULES)}. "
        f"Missing: {_TARGET_ABBRS - set(_MODULES.keys())}"
    )


# ---------------------------------------------------------------------------
# Test 2: Loading — no crash
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_loading(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    assert not mod.skipped, f"{name}: module was skipped"
    assert len(mod._entries) > 0, f"{name}: no entries indexed"
    mod.close()


# ---------------------------------------------------------------------------
# Test 3: Format is generic
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_format_is_generic(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    fmt = mod.format
    assert fmt == "generic", f"{name}: expected 'generic', got '{fmt}'"
    mod.close()


# ---------------------------------------------------------------------------
# Test 4: list_sections() returns list
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_list_sections(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    sections = mod.list_sections()
    assert isinstance(sections, list), f"{name}: list_sections() not a list"
    assert len(sections) > 0, f"{name}: list_sections() empty"
    for item in sections[:3]:
        assert isinstance(item, tuple) and len(item) == 2, \
            f"{name}: bad section entry: {item!r}"
    mod.close()


# ---------------------------------------------------------------------------
# Test 5: get_section_text works
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_get_section_text(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    sections = mod.list_sections()
    n = len(sections)

    # Section 1
    t1 = mod.get_section_text(1)
    assert t1, f"{name}: section 1 empty"
    assert _has_no_null_bytes(t1), f"{name}: section 1 has null bytes"
    assert _has_readable_text(t1), f"{name}: section 1 no readable text"

    # Middle section
    mid = max(2, n // 2)
    if mid <= n:
        tm = mod.get_section_text(mid)
        assert tm, f"{name}: section {mid} empty"
        assert _has_no_null_bytes(tm), f"{name}: section {mid} has null bytes"

    # Last section
    if n >= 2:
        tl = mod.get_section_text(n)
        assert tl, f"{name}: section {n} empty"
        # Last section null bytes are a known bug — report but don't fail here
        # (tested separately in test_text_quality)
    mod.close()


# ---------------------------------------------------------------------------
# Test 6: Edge cases
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_edge_cases(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    sections = mod.list_sections()
    n = len(sections)

    # Empty string: should return error message, not crash
    try:
        result_empty = mod.get_section("")
        assert isinstance(result_empty, str)
    except ValueError:
        pytest.fail(f"{name}: get_section('') raised ValueError (should handle gracefully)")

    # Section 0
    t0 = mod.get_section_text(0)
    assert isinstance(t0, str)

    # Huge section number
    th = mod.get_section_text(n + 100000)
    assert th == "", f"{name}: get_section_text(huge) expected '', got {th[:60]!r}"

    # Bogus string query
    try:
        rb = mod.get_section("bogus query")
        assert isinstance(rb, str)
    except ValueError:
        pytest.fail(f"{name}: get_section('bogus query') raised ValueError")

    mod.close()


# ---------------------------------------------------------------------------
# Test 7: Text quality — null bytes
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("name,path", sorted(_MODULES.items()))
def test_text_quality(name, path):
    mod, err = _try_load(path)
    assert mod is not None, f"{name}: load crashed — {err}"
    sections = mod.list_sections()
    if not sections:
        mod.close()
        pytest.skip(f"{name}: no sections (format bug)")

    bad = []
    empty = []
    for snum, _ in sections:
        t = mod.get_section_text(snum)
        if "\x00" in t:
            bad.append(snum)
        elif not _has_readable_text(t):
            empty.append(snum)
    mod.close()

    assert len(bad) == 0, (
        f"{name}: {len(bad)}/{len(sections)} sections have null bytes "
        f"(last few: {bad[-5:]})"
    )

    pct = len(empty) / len(sections) * 100
    assert pct < 20, (
        f"{name}: {len(empty)}/{len(sections)} ({pct:.1f}%) empty/illegible"
    )


# ---------------------------------------------------------------------------
# Test 8: Hermeneia AT — non-0xFF magic
# ---------------------------------------------------------------------------

def test_hermeneia_at_magic():
    path = _MODULES.get("Hermeneia AT")
    assert path, "Hermeneia AT not found"
    with open(path, "rb") as f:
        magic0 = f.read(1)[0]
    assert magic0 != 0xFF, f"Hermeneia AT first byte is 0xFF ({magic0:#x}) — expected non-0xFF"

    mod, err = _try_load(path)
    assert mod is not None, f"Hermeneia AT load crashed: {err}"
    assert mod.format == "generic", f"Hermeneia AT format: {mod.format}"
    assert len(mod.list_sections()) > 0, "Hermeneia AT no sections"
    t = mod.get_section_text(1)
    assert t and _has_readable_text(t), "Hermeneia AT section 1 bad"
    mod.close()


# ---------------------------------------------------------------------------
# Test 9: Keener-Contexto — Spanish
# ---------------------------------------------------------------------------

def test_keener_contexto_spanish():
    path = _MODULES.get("Keener-Contexto")
    assert path, "Keener-Contexto not found"

    mod, err = _try_load(path)
    assert mod is not None, f"Keener-Contexto load crashed: {err}"
    assert mod.format == "generic", f"Keener-Contexto format: {mod.format}"
    sections = mod.list_sections()
    assert len(sections) > 0

    spanish = [" de ", " la ", " el ", " los ", " las ", " del ", " que ",
               " en ", " por ", " con ", " para ", "Dios", "Jes\u00fas",
               "\u00f3", "\u00e9", "\u00ed", "\u00fa", "\u00f1"]
    hits = 0
    checked = 0
    for snum, _ in sections[:min(50, len(sections))]:
        checked += 1
        t = mod.get_section_text(snum)
        if any(s in t for s in spanish):
            hits += 1
    mod.close()

    assert hits > 0, f"Keener-Contexto: no Spanish in first {checked} sections"
    pct = hits / checked * 100
    assert pct >= 60, (
        f"Keener-Contexto: only {hits}/{checked} ({pct:.1f}%) Spanish"
    )


# ---------------------------------------------------------------------------
# Test 10: JPS Haggadah — large module sanity
# ---------------------------------------------------------------------------

def test_jps_haggadah_large():
    path = _MODULES.get("JPS Haggadah")
    assert path, "JPS Haggadah not found"
    mod, err = _try_load(path)
    assert mod is not None, f"JPS Haggadah load crashed: {err}"
    sections = mod.list_sections()
    assert len(sections) > 10, f"JPS Haggadah: {len(sections)} sections (expected >10)"
    for s in [1, len(sections) // 2, len(sections)]:
        t = mod.get_section_text(s)
        assert t, f"JPS Haggadah section {s} empty"
        assert _has_readable_text(t), f"JPS Haggadah section {s} no readable text"
    mod.close()


# ---------------------------------------------------------------------------
# Test 11: Parables & Miracles — tiny module
# ---------------------------------------------------------------------------

def test_parables_miracles_small():
    path = _MODULES.get("Parables & Miracles")
    assert path, "Parables & Miracles not found"
    mod, err = _try_load(path)
    assert mod is not None, f"Parables & Miracles load crashed: {err}"
    sections = mod.list_sections()
    assert len(sections) > 0, f"Parables & Miracles: {len(sections)} sections"
    for snum, _ in sections:
        t = mod.get_section_text(snum)
        assert t, f"Parables & Miracles section {snum} empty"
        assert _has_readable_text(t), f"Parables & Miracles section {snum} no readable text"
    mod.close()
