"""Rebuild the findings store and the deadline register from collected mail.

Reads no mailbox. Moves no cursor. Sends nothing.

Why this exists: on 2026-07-29 the store held 8,758 finding rows for 4,351 distinct
messages, because two writers partitioned it two different ways and one of them
deduplicated against a single day file instead of the whole store. The register
inherited the duplication (591 rows, 284 distinct) and ~80% of its dated entries were
scraped out of marketing bodies by a date parser that invented years.

Both are derived artefacts. The message store is the source of truth, so the honest
repair is to rebuild them from it under the current rules rather than patch the
duplicates out — a deduplicated pile of findings from the old rules is still findings
from the old rules.

Everything replaced is copied to a timestamped .bak beside it first. Nothing is deleted.
"""

from __future__ import annotations

import argparse
import collections
import json
import shutil
import sys
from datetime import date, datetime
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import register  # noqa: E402
import rules  # noqa: E402
from notify import ROOT  # noqa: E402

MESSAGES_DIR = ROOT / "data" / "messages"
FINDINGS_DIR = ROOT / "data" / "findings"
REGISTER_PATH = ROOT / "data" / "deadlines.jsonl"


def _load_messages() -> list[dict]:
    seen: set[tuple[str, str]] = set()
    messages: list[dict] = []
    for path in sorted(MESSAGES_DIR.glob("*.jsonl")):
        for line in path.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            try:
                message = json.loads(line)
            except ValueError:
                continue
            identity = (message.get("account", "signup"), message.get("message_id", ""))
            if identity in seen:
                continue
            seen.add(identity)
            messages.append(message)
    return messages


def _audit() -> tuple[int, int]:
    rows = 0
    keys: set[tuple[str, str]] = set()
    for path in sorted(FINDINGS_DIR.glob("*.jsonl")):
        for line in path.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            try:
                record = json.loads(line)
            except ValueError:
                continue
            rows += 1
            keys.add((record.get("account", "signup"), record.get("message_id", "")))
    return rows, len(keys)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--apply",
        action="store_true",
        help="actually rebuild; without it this only reports what it would do",
    )
    args = parser.parse_args()

    messages = _load_messages()
    before_rows, before_keys = _audit()
    print(f"messages (distinct)   {len(messages)}")
    print(f"findings rows before  {before_rows}")
    print(f"findings distinct     {before_keys}")
    if REGISTER_PATH.exists():
        register_rows = len(
            [l for l in REGISTER_PATH.read_text(encoding="utf-8").splitlines() if l.strip()]
        )
        print(f"register rows before  {register_rows}")

    if not args.apply:
        print("\nDry run. Re-run with --apply to rebuild.")
        return 0

    stamp = datetime.now().strftime("%Y%m%dT%H%M%S")
    if FINDINGS_DIR.exists():
        backup = FINDINGS_DIR.with_name(f"findings.bak-{stamp}")
        shutil.copytree(FINDINGS_DIR, backup)
        print(f"\nbacked up findings  -> {backup.name}")
        for path in FINDINGS_DIR.glob("*.jsonl"):
            path.unlink()
    if REGISTER_PATH.exists():
        backup = REGISTER_PATH.with_name(f"deadlines.jsonl.bak-{stamp}")
        shutil.copy2(REGISTER_PATH, backup)
        print(f"backed up register  -> {backup.name}")
        REGISTER_PATH.unlink()

    findings = rules.write_findings(messages)
    registered = register.append_deadlines(findings)

    after_rows, after_keys = _audit()
    buckets = collections.Counter(f["bucket"] for f in findings)
    print()
    print(f"findings rows after   {after_rows}")
    print(f"findings distinct     {after_keys}")
    print(f"single-writer check   {'PASS' if after_rows == after_keys else 'FAIL'}")
    print()
    for bucket in ("ACT", "REVIEW", "NOTE"):
        count = buckets.get(bucket, 0)
        print(f"  {bucket:<8}{count:>6}  {100 * count / max(1, len(findings)):>5.1f}%")
    print(f"\ndeadline rows written {registered}")

    today = date.today()
    horizon: dict[tuple[str, str], str] = {}
    if REGISTER_PATH.exists():
        for line in REGISTER_PATH.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            item = json.loads(line)
            try:
                due = date.fromisoformat(item["date"])
            except (KeyError, ValueError):
                continue
            if 0 <= (due - today).days <= 60:
                horizon[(item["date"], item["source_subject"])] = item["date"]
    print(f"\n60-day horizon: {len(horizon)} entries")
    for due, subject in sorted(horizon):
        print(f"  {due}  {subject[:60]}")
    return 0 if after_rows == after_keys else 1


if __name__ == "__main__":
    raise SystemExit(main())
