#!/usr/bin/env python3
"""Recount the pinned English KJV input with Python 3's standard library.

Run from any directory: python count-jesus.py --output-dir results
Optional offline input: --input kjv.jsonl.gz
"""

import argparse
import csv
import gzip
import hashlib
import io
import json
from pathlib import Path
import re
import urllib.error
import urllib.request


SOURCE_URL = "https://trustbible.org/data/kjv.jsonl.gz"
SOURCE_SHA256 = "0c3b8afefa4efaa8bd72576682c8ad060e4ccb499e81943a9678de49ab9416ad"
NAME_PATTERN = r"(?<![\w-])jesus(?![\w-])"
NAME = re.compile(NAME_PATTERN, re.IGNORECASE)
WORD_BOUNDARY = re.compile(r"\bjesus\b", re.IGNORECASE)
EXCLUSIONS = {
    ("Acts", 7, 45): "Joshua: entry into the land; excluded only in the three-declared-exclusions variant.",
    ("Hebrews", 4, 8): "Joshua: giving rest; excluded only in the three-declared-exclusions variant.",
    ("Colossians", 4, 11): "Jesus called Justus; excluded only in the three-declared-exclusions variant.",
}
COMPOUND = ("Acts", 13, 6)
FIELDS = {
    "name_occurrences": "Number of standalone, case-insensitive Jesus matches in this verse, including possessives.",
    "matching_verse": "1 if name_occurrences is positive, otherwise 0; sum for matching verses.",
    "name_occurrences_after_three_declared_exclusions": "Occurrences after zeroing Acts 7:45, Hebrews 4:8 and Colossians 4:11 only.",
    "matching_verse_after_three_declared_exclusions": "1 if the preceding occurrence count is positive, otherwise 0; sum for matching verses.",
    "uppercase_occurrences": "Matches spelled JESUS; already included in name_occurrences.",
    "possessive_occurrences": "Matches immediately followed by a straight or curly apostrophe; already included in name_occurrences.",
    "reference": "English book name, chapter and verse; unique within this ledger.",
    "note": "Declared contextual exclusion or compound-name explanation; blank for other rows.",
    "kjv_text": "Complete unchanged verse text from the pinned English KJV input.",
    "context_url": "Public English KJV chapter URL anchored to this verse.",
}


def require(condition, message):
    if not condition:
        raise ValueError(message)


def reference(key):
    return f"{key[0]} {key[1]}:{key[2]}"


def context_url(key):
    slug = key[0].lower().replace(" ", "-")
    return f"https://trustbible.org/en/bible/{slug}/{key[1]}/#verse-{key[2]}"


def recount(raw):
    actual_hash = hashlib.sha256(raw).hexdigest()
    require(actual_hash == SOURCE_SHA256,
            f"Source SHA-256 mismatch: expected {SOURCE_SHA256}, got {actual_hash}. No output written.")
    verses = [json.loads(line) for line in gzip.decompress(raw).decode("utf-8").splitlines()]
    require(len(verses) == 31102, "Expected 31,102 source verse records.")
    keys = set()
    rows = []
    casing = {"Jesus": 0, "JESUS": 0}
    without_possessives = [0, 0]
    word_boundary = [0, 0]
    for verse in verses:
        require(isinstance(verse.get("book"), str) and isinstance(verse.get("text"), str),
                "Invalid source book or text field.")
        require(all(type(verse.get(field)) is int and verse[field] > 0 for field in ("chapter", "verse")),
                "Invalid source chapter or verse field.")
        key = (verse["book"], verse["chapter"], verse["verse"])
        require(key not in keys, f"Duplicate source reference: {reference(key)}")
        keys.add(key)
        text = verse["text"]
        matches = list(NAME.finditer(text))
        naive_count = len(WORD_BOUNDARY.findall(text))
        word_boundary[0] += naive_count
        word_boundary[1] += int(naive_count > 0)
        possessives = sum(text[m.end():m.end() + 1] in ("'", "’") for m in matches)
        retained = len(matches) - possessives
        without_possessives[0] += retained
        without_possessives[1] += int(retained > 0)
        for match in matches:
            spelling = match.group()
            casing[spelling] = casing.get(spelling, 0) + 1
        # Keep substring candidates so the nonmatching compound is inspectable.
        if "jesus" not in text.lower():
            continue
        after = 0 if key in EXCLUSIONS else len(matches)
        rows.append({
            "name_occurrences": len(matches),
            "matching_verse": int(bool(matches)),
            "name_occurrences_after_three_declared_exclusions": after,
            "matching_verse_after_three_declared_exclusions": int(after > 0),
            "uppercase_occurrences": sum(m.group() == "JESUS" for m in matches),
            "possessive_occurrences": possessives,
            "reference": reference(key),
            "note": ("Compound name Barjesus; zero standalone Jesus matches. Retained as an audit example."
                     if key == COMPOUND else EXCLUSIONS.get(key, "")),
            "kjv_text": text,
            "context_url": context_url(key),
        })
    totals = {field: sum(row[field] for row in rows) for field in list(FIELDS)[:6]}
    expected = {
        "name_occurrences": 983,
        "matching_verse": 942,
        "name_occurrences_after_three_declared_exclusions": 980,
        "matching_verse_after_three_declared_exclusions": 939,
        "uppercase_occurrences": 6,
        "possessive_occurrences": 10,
    }
    require(totals == expected, f"Unexpected totals: {totals}")
    require(len(rows) == 943 and len({key[0] for key in keys}) == 66,
            "Expected 943 audit candidates and 66 source books.")
    require(casing == {"Jesus": 977, "JESUS": 6}, f"Unexpected casing split: {casing}")
    require(without_possessives == [973, 935], "Unexpected possessive-exclusion result.")
    require(word_boundary == [983, 942], "Unexpected word-boundary result in the pinned source.")
    require([row["reference"] for row in rows if row["name_occurrences"] == 0] == ["Acts 13:6"],
            "Expected Acts 13:6 as the only nonmatching audit candidate.")
    for key in EXCLUSIONS:
        row = next(row for row in rows if row["reference"] == reference(key))
        require(row["name_occurrences"] == 1 and row["name_occurrences_after_three_declared_exclusions"] == 0,
                f"Unexpected declared exclusion: {reference(key)}")
    return rows, totals


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--input", type=Path, help="Optional local copy of the exact pinned .jsonl.gz input.")
    parser.add_argument("--output-dir", type=Path, default=Path("."), help="Output directory (default: current directory).")
    args = parser.parse_args()
    try:
        if args.input:
            raw = args.input.read_bytes()
        else:
            request = urllib.request.Request(SOURCE_URL, headers={"User-Agent": "TrustBible-Jesus-count/1.0"})
            with urllib.request.urlopen(request, timeout=60) as response:
                raw = response.read()
        rows, totals = recount(raw)
        output = io.StringIO(newline="")
        writer = csv.DictWriter(output, fieldnames=list(FIELDS), lineterminator="\n")
        writer.writeheader()
        writer.writerows(rows)
        csv_bytes = output.getvalue().encode("utf-8-sig")
        method = {
            "dataset": "Jesus-name audit of a pinned English KJV text",
            "language": "en",
            "method_version": "1.0",
            "verified_on": "2026-10-26",
            "source": {
                "url": SOURCE_URL,
                "sha256": SOURCE_SHA256,
                "upstream": "https://github.com/aruljohn/Bible-kjv",
                "imported_on": "2026-09-06",
                "printing": "Unknown",
                "scope": "Verse text only, 66-book Protestant canon; no headings, footnotes or metadata.",
                "verse_records": 31102,
                "unique_references": 31102,
            },
            "rules": {
                "standalone_pattern": NAME_PATTERN,
                "flags": "Python re.IGNORECASE (Unicode default)",
                "boundary": "Reject a match adjacent to a Python word character or ASCII hyphen. Possessive apostrophes remain allowed.",
                "possessives": "Include matches immediately followed by U+0027 or U+2019; both are counted as the same name.",
                "candidate_rows": "Include each verse containing the substring jesus, ignoring case; Acts 13:6 remains as a zero-match compound example.",
                "declared_contextual_exclusions": [
                    {"reference": reference(key), "reason": reason, "context_url": context_url(key)}
                    for key, reason in EXCLUSIONS.items()
                ],
                "exclusion_limit": "980/939 is arithmetic after these three declared exclusions, not a separately certified review of every remaining reference to Jesus Christ.",
            },
            "audit": {
                "url": "https://trustbible.org/data/jesus-name-audit.csv",
                "sha256": hashlib.sha256(csv_bytes).hexdigest(),
                "encoding": "UTF-8 with BOM; comma-delimited, LF line endings",
                "candidate_rows": len(rows),
                "nonmatching_rows": ["Acts 13:6"],
                "columns": FIELDS,
                "totals": totals,
            },
            "variants": {
                "casing": {"Jesus": 977, "JESUS": 6},
                "deliberately_drop_possessives": {"name_occurrences": 973, "matching_verses": 935},
                "word_boundary_in_this_source": {"pattern": r"\bjesus\b", "name_occurrences": 983, "matching_verses": 942},
            },
            "comparison": {
                "provider": "eBible.org / CrossWire Bible Society, standardized 1769 KJV",
                "description_url": "https://ebible.org/details.php?id=eng-kjv",
                "archive_url": "https://ebible.org/Scriptures/eng-kjv_usfx.zip",
                "observed_on": "2026-10-26",
                "provider_source_date": "2026-09-26",
                "archive_sha256": "72cffd707cb569f7d3f69de0437c060e04fe3233a57cae9a13060888d437681b",
                "xml_member": "eng-kjv_usfx.xml",
                "xml_sha256": "9f21efb28694a3c9441186da8d792c2ec194e9bf6a1710856a3c26b5cc9ef25a",
                "scope": "The same 66 canonical books and 31,102 unique references, excluding headings, footnotes and deuterocanonical books.",
                "word_boundary_pattern": r"\bjesus\b",
                "word_boundary_counts": {"name_occurrences": 984, "matching_verses": 943},
                "compound_excluding_counts": {"name_occurrences": 983, "matching_verses": 942},
                "sole_per_reference_count_difference": "Acts 13:6: Barjesus in the main input, Bar-jesus in the comparison.",
                "limit": "Separate snapshot comparison; this script recounts only the main input. The mutable archive URL may later have different bytes. No eBible verse text is redistributed here; no historical competitor method is inferred.",
            },
            "reproduction": {
                "script_url": "https://trustbible.org/data/count-jesus.py",
                "script_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
                "requires": "Python 3 standard library only; internet access for the default source download.",
                "command": "python count-jesus.py --output-dir results",
                "offline_command": "python count-jesus.py --input kjv.jsonl.gz --output-dir results",
                "outputs": ["jesus-name-audit.csv", "jesus-count-method.json"],
            },
            "limits": [
                "A name search does not count titles, pronouns, prophecy or every reference to Jesus Christ.",
                "These are English KJV measurements, not totals for Chinese or other translations.",
                "Input identity does not establish a specific KJV printing or certify every person identification.",
            ],
        }
        args.output_dir.mkdir(parents=True, exist_ok=True)
        (args.output_dir / "jesus-name-audit.csv").write_bytes(csv_bytes)
        (args.output_dir / "jesus-count-method.json").write_text(
            json.dumps(method, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    except (OSError, ValueError, KeyError, StopIteration, urllib.error.URLError) as error:
        parser.exit(1, f"Recount failed: {error}\n")
    print("PASS: source SHA-256 verified; 31,102 unique verse records recounted.")
    print("983 occurrences / 942 verses; 977 Jesus + 6 JESUS; 10 possessives included.")
    print("Dropping possessives: 973 / 935. Three declared exclusions: 980 / 939.")
    print(f"943 audit rows (Acts 13:6 counts zero). Files written to {args.output_dir.resolve()}")


if __name__ == "__main__":
    main()
