"""Run the rules and the deadline register over mail already collected.

Collection and classification are separate steps, and only the daily brief ever ran the
classifier — over messages *new since the last cursor*. A historical sweep therefore
lands thousands of messages that nothing ever reads. This walks the whole store and
classifies it as if it had arrived normally.

Touches no mailbox. Moves no cursor. Sends nothing. Safe to run twice.
"""

from __future__ import annotations

import argparse
import collections
import json
import sys
import traceback
from datetime import date
from pathlib import Path
from typing import Any

from notify import ROOT
from register import append_deadlines
from rules import write_findings

MESSAGES_DIR = ROOT / "data" / "messages"
FINDINGS_DIR = ROOT / "data" / "findings"


def _load_messages(since: date | None) -> list[dict[str, Any]]:
    messages: list[dict[str, Any]] = []
    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
            if since:
                stamp = (message.get("date") or "")[:10]
                if not stamp or stamp < since.isoformat():
                    continue
            messages.append(message)
    return messages


def _already_classified() -> set[tuple[str, str]]:
    seen: set[tuple[str, str]] = set()
    if not FINDINGS_DIR.exists():
        return seen
    for path in 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
            seen.add((record.get("account", "signup"), record.get("message_id", "")))
    return seen


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Classify already-collected mail. Reads no mailbox."
    )
    parser.add_argument("--from", dest="since", help="only messages on or after YYYY-MM-DD")
    args = parser.parse_args()
    since = date.fromisoformat(args.since) if args.since else None

    messages = _load_messages(since)
    if not messages:
        print("No collected messages found. Nothing to do.")
        return 0

    seen = _already_classified()
    print(f"{len(messages)} collected · {len(seen)} already classified")

    # write_findings is the ONE writer of the findings store: it de-duplicates against
    # the whole store and files each row under the message's own date.
    #
    # This used to be a second writer with its own partitioning and its own idea of
    # what counted as already-present. Running the backfill and then the brief wrote
    # every finding twice — 8,758 rows for 4,351 messages, and a deadline register that
    # inherited the duplication.
    findings = write_findings(messages)
    if not findings:
        print("nothing new to classify")
        return 0

    registered = append_deadlines(findings)

    buckets = collections.Counter(finding["bucket"] for finding in findings)
    accounts = collections.Counter(finding.get("account", "?") for finding in findings)

    print()
    print(f"CLASSIFIED  {len(findings)}")
    for bucket in ("ACT", "REVIEW", "NOTE"):
        print(f"  {bucket:<8} {buckets.get(bucket, 0)}")
    print("BY ACCOUNT")
    for account, count in accounts.most_common():
        print(f"  {account:<12} {count}")
    print(f"DEADLINES REGISTERED  {registered if isinstance(registered, int) else 'see register'}")
    print(f"files written to {FINDINGS_DIR}")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as error:
        print(f"Backfill failed: {type(error).__name__}: {error}")
        traceback.print_exc()
        raise SystemExit(1)
