#!/usr/bin/env python3
"""Inter-coder re-rating for the IETF AI/agent landscape survey.

Re-rates the clean IETF corpus (source='ietf', not false-positive) with TWO
models (Sonnet + Haiku) using the EXACT pinned production prompt
(``RATE_PROMPT_COMPACT``, abstract[:2000]) via the Anthropic Batch API (50% off).

Safety / reproducibility:
- Does NOT touch the production ``ratings`` table. Output goes to
  ``data/rerate/<model-alias>.jsonl`` (one JSON object per draft).
- Batch IDs are persisted to ``data/rerate/batches.json`` so the run resumes.
- Idempotent: drafts already present in the output JSONL are skipped on re-submit.

Usage:
    PYTHONPATH=src python3 scripts/rerate-intercoder.py --dry-run   # cost estimate, submit nothing
    PYTHONPATH=src python3 scripts/rerate-intercoder.py --submit    # create batches
    PYTHONPATH=src python3 scripts/rerate-intercoder.py --collect   # poll + write results
    PYTHONPATH=src python3 scripts/rerate-intercoder.py --run       # submit then collect (blocking)
"""
from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path

from anthropic import Anthropic

from ietf_analyzer.analyzer import (
    CATEGORIES_SHORT,
    RATE_PROMPT_COMPACT,
    _doc_type_label,
)
from ietf_analyzer.config import Config
from ietf_analyzer.db import Database

OUT_DIR = Path("data/rerate")
BATCH_FILE = OUT_DIR / "batches.json"
MAX_TOKENS = 512

# Anthropic batch pricing is 50% of standard. Standard (USD per 1M tokens):
PRICING = {  # input, output
    "sonnet": (3.00, 15.00),
    "haiku": (1.00, 5.00),
}
MODELS = {
    "sonnet": lambda c: c.claude_model,
    "haiku": lambda c: c.claude_model_cheap,
}


def clean_ietf_drafts(db: Database):
    """The survey corpus: source='ietf', not flagged false-positive."""
    rows = db.conn.execute(
        """SELECT d.name FROM drafts d JOIN ratings r ON d.name = r.draft_name
           WHERE d.source = 'ietf'
             AND (r.false_positive = 0 OR r.false_positive IS NULL)
           ORDER BY d.name"""
    ).fetchall()
    return [r[0] for r in rows]


def build_prompt(db: Database, name: str) -> str | None:
    d = db.get_draft(name)
    if d is None:
        return None
    return RATE_PROMPT_COMPACT.format(
        doc_type=_doc_type_label(d.source),
        name=d.name, title=d.title, time=d.date,
        pages=d.pages or "?",
        abstract=d.abstract[:2000],
        categories=", ".join(CATEGORIES_SHORT),
    )


def already_done(alias: str) -> set[str]:
    p = OUT_DIR / f"{alias}.jsonl"
    if not p.exists():
        return set()
    done = set()
    for line in p.read_text().splitlines():
        if line.strip():
            done.add(json.loads(line)["draft_name"])
    return done


def load_batches() -> dict:
    if BATCH_FILE.exists():
        return json.loads(BATCH_FILE.read_text())
    return {}


def save_batches(b: dict):
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    BATCH_FILE.write_text(json.dumps(b, indent=2))


def cmd_dry_run(db: Database, cfg: Config, names: list[str]):
    total_in = 0
    sample = None
    for n in names:
        p = build_prompt(db, n)
        if p is None:
            continue
        total_in += len(p) // 4  # ~4 chars/token
        if sample is None:
            sample = p
    out_est = len(names) * 350  # observed compact-JSON output size
    print(f"corpus: {len(names)} clean IETF drafts")
    print(f"est input tokens/draft: ~{total_in // max(len(names),1)}")
    print(f"est total input tokens: ~{total_in:,} | output: ~{out_est:,}")
    print("\nestimated cost (Batch API = 50% of standard):")
    grand = 0.0
    for alias, (pin, pout) in PRICING.items():
        c = (total_in / 1e6 * pin + out_est / 1e6 * pout) * 0.5
        grand += c
        print(f"  {alias:7} ({MODELS[alias](cfg)}): ${c:.2f}")
    print(f"  {'BOTH':7}: ${grand:.2f}")
    print(f"\n--- sample prompt ({len(sample)} chars) ---\n{sample[:600]}\n...")


def cmd_submit(db: Database, cfg: Config, names: list[str], client: Anthropic):
    batches = load_batches()
    for alias in MODELS:
        if batches.get(alias, {}).get("id"):
            print(f"[{alias}] batch already submitted: {batches[alias]['id']} — skip")
            continue
        done = already_done(alias)
        todo = [n for n in names if n not in done]
        if not todo:
            print(f"[{alias}] all {len(names)} already collected — nothing to submit")
            continue
        model = MODELS[alias](cfg)
        requests = []
        for n in todo:
            p = build_prompt(db, n)
            if p is None:
                continue
            requests.append({
                "custom_id": n,
                "params": {
                    "model": model,
                    "max_tokens": MAX_TOKENS,
                    "messages": [{"role": "user", "content": p}],
                },
            })
        batch = client.messages.batches.create(requests=requests)
        batches[alias] = {"id": batch.id, "model": model, "count": len(requests)}
        save_batches(batches)
        print(f"[{alias}] submitted {len(requests)} requests → batch {batch.id}")


def cmd_collect(client: Anthropic, poll: bool):
    batches = load_batches()
    if not batches:
        print("no batches submitted yet (run --submit)")
        return False
    all_done = True
    for alias, info in batches.items():
        bid = info["id"]
        b = client.messages.batches.retrieve(bid)
        print(f"[{alias}] {bid}: {b.processing_status} "
              f"(succeeded={b.request_counts.succeeded}, errored={b.request_counts.errored}, "
              f"processing={b.request_counts.processing})")
        if b.processing_status != "ended":
            all_done = False
            continue
        out_path = OUT_DIR / f"{alias}.jsonl"
        done = already_done(alias)
        n_new = 0
        with out_path.open("a") as f:
            for result in client.messages.batches.results(bid):
                cid = result.custom_id
                if cid in done:
                    continue
                if result.result.type != "succeeded":
                    f.write(json.dumps({"draft_name": cid, "error": result.result.type}) + "\n")
                    continue
                msg = result.result.message
                raw = msg.content[0].text
                rec = {
                    "draft_name": cid,
                    "model": info["model"],
                    "raw": raw,
                    "in_tok": msg.usage.input_tokens,
                    "out_tok": msg.usage.output_tokens,
                }
                f.write(json.dumps(rec) + "\n")
                n_new += 1
        print(f"[{alias}] wrote {n_new} new results → {out_path}")
    return all_done


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--dry-run", action="store_true")
    ap.add_argument("--submit", action="store_true")
    ap.add_argument("--collect", action="store_true")
    ap.add_argument("--run", action="store_true", help="submit then poll-collect until done")
    args = ap.parse_args()

    cfg = Config.load()
    db = Database(cfg)
    names = clean_ietf_drafts(db)

    if args.dry_run:
        cmd_dry_run(db, cfg, names)
        return

    client = Anthropic()
    if args.submit or args.run:
        cmd_submit(db, cfg, names, client)
    if args.collect:
        cmd_collect(client, poll=False)
    if args.run:
        while True:
            if cmd_collect(client, poll=True):
                print("all batches ended.")
                break
            print("...waiting 60s for batches to finish")
            time.sleep(60)


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