"""CLI: ``python -m ocr_sprint.tools.export_ground_truth``.

Dumps the HITL ground-truth corpus to a local JSONL file. The HTTP
endpoint is fine for small queries, but weekly / monthly snapshots are
easier to schedule on the host via cron than via curl.

Usage
-----
    python -m ocr_sprint.tools.export_ground_truth --out corpus.jsonl
    python -m ocr_sprint.tools.export_ground_truth --out corpus.jsonl \
        --since 2025-01-01 --has-corrections

The database URL is read from the same ``DATABASE_URL`` env var the API
uses, so this script works out of the box on any box that can run the
service.
"""

from __future__ import annotations

import argparse
import sys
from datetime import datetime
from pathlib import Path
from typing import Protocol

from ocr_sprint.db.base import session_scope
from ocr_sprint.ground_truth import (
    GroundTruthFilters,
    ground_truth_stats,
    iter_ground_truth_samples,
    serialize_sample_to_jsonl,
)


def _parse_iso(raw: str | None) -> datetime | None:
    if raw is None:
        return None
    # ``fromisoformat`` supports dates too, so ``--since 2025-01-01``
    # behaves like ``2025-01-01T00:00:00``.
    return datetime.fromisoformat(raw)


def _build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="ocr_sprint.tools.export_ground_truth",
        description="Dump HITL ground-truth corpus to JSONL.",
    )
    p.add_argument(
        "--out",
        required=True,
        type=Path,
        help="Output JSONL file. Use '-' to write to stdout.",
    )
    p.add_argument("--since", type=str, default=None, help="ISO date/datetime lower bound.")
    p.add_argument("--until", type=str, default=None, help="ISO date/datetime upper bound.")
    p.add_argument(
        "--include-unapproved",
        action="store_true",
        help=(
            "Include jobs that haven't been approved yet. Not recommended "
            "for training data — use only for debugging exports."
        ),
    )
    group = p.add_mutually_exclusive_group()
    group.add_argument(
        "--has-corrections",
        action="store_true",
        help="Only include jobs that had at least one correction.",
    )
    group.add_argument(
        "--no-corrections",
        action="store_true",
        help="Only include pristine jobs (no corrections). Useful for sanity sets.",
    )
    p.add_argument("--limit", type=int, default=None, help="Maximum samples to emit.")
    p.add_argument(
        "--print-stats",
        action="store_true",
        help="Also print dataset summary to stderr after the dump.",
    )
    return p


def main(argv: list[str] | None = None) -> int:
    args = _build_parser().parse_args(argv)

    has_corrections: bool | None
    if args.has_corrections:
        has_corrections = True
    elif args.no_corrections:
        has_corrections = False
    else:
        has_corrections = None

    filters = GroundTruthFilters(
        since=_parse_iso(args.since),
        until=_parse_iso(args.until),
        approved_only=not args.include_unapproved,
        has_corrections=has_corrections,
        limit=args.limit,
    )

    out: Path = args.out
    if str(out) == "-":
        count = _write_stream(sys.stdout.buffer, filters)
    else:
        out.parent.mkdir(parents=True, exist_ok=True)
        with out.open("wb") as fh:
            count = _write_stream(fh, filters)

    print(f"wrote {count} sample(s) to {out}", file=sys.stderr)

    if args.print_stats:
        with session_scope() as session:
            stats = ground_truth_stats(session)
        print(stats.model_dump_json(indent=2), file=sys.stderr)

    return 0


class _BinaryWriter(Protocol):
    def write(self, data: bytes) -> int: ...


def _write_stream(fh: _BinaryWriter, filters: GroundTruthFilters) -> int:
    """Write JSONL to ``fh``. Returns the number of samples written."""
    count = 0
    with session_scope() as session:
        for sample in iter_ground_truth_samples(session, filters):
            fh.write(serialize_sample_to_jsonl(sample).encode("utf-8"))
            count += 1
    return count


if __name__ == "__main__":
    sys.exit(main())
