"""Triage support tickets by calling a governed agent like a typed function.

Python 3.12+, standard library only. Each ticket becomes one application
request with a JSON Schema result contract. A local SQLite ledger records the
intent (idempotency key) before submitting and the request id after, so a crash
at any point is recovered by simply running the command again.
"""

import argparse
import getpass
import json
import os
from pathlib import Path
import sqlite3
import sys
import uuid

from agent_function import AgentCallError, AgentFunction, ApiError, Client


HERE = Path(__file__).parent
CHARTER = (
    "You are the first-line support triage desk for Acme, a B2B payments product. For each ticket, judge "
    "severity from customer impact (sev1: many customers cannot transact; sev4: a question or idea), route it to "
    "the owning team, and draft a short, accurate first reply. Use only the ticket text. Flag needs_human when "
    "money may be lost, security may be involved, or the customer is threatening to leave."
)


def config():
    api_base = os.environ.get("DT_API_BASE", "http://localhost:8000/api/v1")
    api_key = os.environ.get("DT_API_KEY") or getpass.getpass("API key: ")
    return Client(api_base, api_key)


def open_ledger(path):
    db = sqlite3.connect(path)
    db.execute("""CREATE TABLE IF NOT EXISTS calls (
        ticket_id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL, request_id TEXT,
        state TEXT NOT NULL DEFAULT 'intended', value TEXT, error TEXT)""")
    return db


def ticket_prompt(ticket):
    return (f"Triage support ticket {ticket['id']}.\n\n"
            f"From: {ticket['customer']} ({ticket['plan']} plan)\nSubject: {ticket['subject']}\n\n{ticket['body']}")


def cmd_create_agent(client, args):
    agent, _ = client.request("POST", "/agents/", {"name": args.name, "charter": CHARTER, "schedule": None},
                              headers={"Idempotency-Key": str(uuid.uuid4())})
    print(f"Created {agent['name']}.\nexport DT_AGENT_ID={agent['id']}")


def cmd_run(client, args):
    schema = json.loads((HERE / "triage.schema.json").read_text())
    tickets = [json.loads(line) for line in Path(args.tickets).read_text().splitlines() if line.strip()]
    triage = AgentFunction(client, os.environ.get("DT_AGENT_ID") or sys.exit("Set DT_AGENT_ID"), schema)
    db = open_ledger(args.ledger)

    # Fan out: every ticket is durably accepted in milliseconds; the agent works in the background.
    for number, ticket in enumerate(tickets, 1):
        row = db.execute("SELECT idempotency_key, request_id FROM calls WHERE ticket_id = ?", (ticket["id"],)).fetchone()
        if row and row[1]:
            continue  # Already accepted on an earlier run; reconcile below by request id.
        if not row:
            # Record the intent first. If we crash after the POST, the rerun reuses this key.
            db.execute("INSERT INTO calls (ticket_id, idempotency_key) VALUES (?, ?)", (ticket["id"], str(uuid.uuid4())))
            db.commit()
        key = db.execute("SELECT idempotency_key FROM calls WHERE ticket_id = ?", (ticket["id"],)).fetchone()[0]
        accepted, replayed = triage.submit(ticket_prompt(ticket), idempotency_key=key, client_reference=ticket["id"])
        note = "replayed original acceptance" if replayed else "accepted"
        print(f"  {ticket['id']}  {note:<29} request {accepted['request_id']}")
        if args.crash_after == number:
            print(f"  Simulated crash after submitting {ticket['id']}, before recording its request id.", flush=True)
            os._exit(1)
        db.execute("UPDATE calls SET request_id = ?, state = 'accepted' WHERE ticket_id = ?",
                   (accepted["request_id"], ticket["id"]))
        db.commit()

    # Fan in: wait for each typed result. Timing out here never cancels the agent's work.
    for ticket_id, request_id, state in db.execute(
            "SELECT ticket_id, request_id, state FROM calls WHERE request_id IS NOT NULL ORDER BY ticket_id").fetchall():
        if state in {"validated", "needs_attention"}:
            continue
        try:
            value = triage.result(request_id, timeout_seconds=args.timeout)
            db.execute("UPDATE calls SET state = 'validated', value = ?, error = NULL WHERE ticket_id = ?",
                       (json.dumps(value), ticket_id))
            print(f"  {ticket_id}  validated")
        except AgentCallError as exc:
            db.execute("UPDATE calls SET state = 'needs_attention', error = ? WHERE ticket_id = ?", (exc.code, ticket_id))
            print(f"  {ticket_id}  needs attention: {exc.code}")
        except TimeoutError:
            print(f"  {ticket_id}  still running; run again later to collect it")
        db.commit()
    report(db)


def report(db):
    rows = db.execute("SELECT ticket_id, state, value, error FROM calls ORDER BY ticket_id").fetchall()
    print(f"\n{'ticket':<8} {'sev':<5} {'team':<9} {'human':<6} summary")
    for ticket_id, state, value, error in rows:
        if state != "validated":
            print(f"{ticket_id:<8} {'-':<5} {'-':<9} {'-':<6} [{state}{': ' + error if error else ''}]")
            continue
        v = json.loads(value)
        print(f"{ticket_id:<8} {v['severity']:<5} {v['team']:<9} {('yes' if v['needs_human'] else 'no'):<6} {v['summary']}")


def cmd_show(client, args):
    db = open_ledger(args.ledger)
    row = db.execute("SELECT value FROM calls WHERE ticket_id = ?", (args.ticket_id,)).fetchone()
    print(json.dumps(json.loads(row[0]), indent=2) if row and row[0] else "No validated result for that ticket.")


def main():
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--ledger", default="triage-ledger.sqlite3")
    commands = parser.add_subparsers(dest="command", required=True)
    create = commands.add_parser("create-agent", help="Create the triage agent")
    create.add_argument("--name", default="Acme Ticket Triage")
    create.set_defaults(handler=cmd_create_agent)
    run = commands.add_parser("run", help="Submit tickets and collect typed results")
    run.add_argument("--tickets", default=str(HERE / "tickets.jsonl"))
    run.add_argument("--timeout", type=int, default=600, help="Seconds to wait per ticket before moving on")
    run.add_argument("--crash-after", type=int, metavar="N", help="Exit abruptly right after submitting ticket N")
    run.set_defaults(handler=cmd_run)
    show = commands.add_parser("show", help="Print one ticket's validated result")
    show.add_argument("ticket_id")
    show.set_defaults(handler=cmd_show)
    args = parser.parse_args()
    try:
        args.handler(None if args.command == "show" else config(), args)  # show reads only the ledger.
    except ApiError as exc:
        sys.exit(f"Request failed: {exc}")


if __name__ == "__main__":
    main()
