"""Summarize an attempt ledger; sample data is hypothetical, not provider pricing.

Usage: python3 calculate_cost.py attempts.example.csv --hourly-rate 90
Cash/allocated costs and list-rate estimates are deliberately separate.
All amounts must use the same currency. Review and repair minutes must not overlap.
"""
import argparse
import csv
from collections import defaultdict
from decimal import Decimal, InvalidOperation
import json

MONEY = ("billed_api_usd", "allocated_subscription_usd", "infra_usd")
NUMERIC = (*MONEY, "list_estimate_usd", "review_minutes", "repair_minutes")


def nonnegative(raw, name):
    try:
        value = Decimal(raw)
    except (InvalidOperation, TypeError):
        raise ValueError(f"{name} must be a number") from None
    if not value.is_finite() or value < 0:
        raise ValueError(f"{name} must be finite and nonnegative")
    return value


def summarize(rows, hourly_rate):
    policies = defaultdict(list)
    seen = set()
    for row in rows:
        for name in ("policy", "task_id", "trial_id", "attempt_id"):
            if not row.get(name, "").strip():
                raise ValueError(f"missing {name}")
        key = (row["policy"], row["trial_id"], row["attempt_id"])
        if key in seen:
            raise ValueError(f"duplicate attempt: {key}")
        seen.add(key)
        if row["accepted"] not in ("true", "false"):
            raise ValueError("accepted must be true or false")
        try:
            attempt = int(row["attempt_id"])
        except ValueError:
            raise ValueError("attempt_id must be a positive integer") from None
        if attempt < 1:
            raise ValueError("attempt_id must be a positive integer")
        normalized = {name: nonnegative(row[name], name) for name in NUMERIC}
        normalized.update(task=row["task_id"], trial=row["trial_id"],
                          attempt=attempt, accepted=row["accepted"] == "true")
        policies[row["policy"]].append(normalized)

    output = {}
    for policy, attempts in sorted(policies.items()):
        trials = defaultdict(list)
        for row in attempts:
            trials[row["trial"]].append(row)
        for trial_id, trial_rows in trials.items():
            if len({r["task"] for r in trial_rows}) != 1:
                raise ValueError(f"{policy}/{trial_id} names multiple tasks")
            ordered = sorted(trial_rows, key=lambda r: r["attempt"])
            if [r["attempt"] for r in ordered] != list(range(1, len(ordered) + 1)):
                raise ValueError(f"{policy}/{trial_id} has missing/duplicate attempt numbers")
            if any(r["accepted"] for r in ordered[:-1]):
                raise ValueError(f"{policy}/{trial_id} continues after acceptance")
        accepted = sum(any(r["accepted"] for r in rs) for rs in trials.values())
        cash = sum((sum((r[k] for k in MONEY), Decimal(0)) for r in attempts), Decimal(0))
        minutes = sum((r["review_minutes"] + r["repair_minutes"] for r in attempts), Decimal(0))
        labor = minutes * hourly_rate / 60
        reference = sum((r["list_estimate_usd"] for r in attempts), Decimal(0))
        fmt = lambda value: str(value.quantize(Decimal("0.01")))
        output[policy] = {
            "tasks": len({r["task"] for r in attempts}),
            "trials": len(trials), "attempts": len(attempts),
            "accepted_trials": accepted,
            "cash_and_allocated_usd": fmt(cash),
            "human_minutes": str(minutes),
            "modeled_labor_usd": fmt(labor),
            "total_with_labor_usd": fmt(cash + labor),
            "cash_per_accepted_trial_usd": fmt(cash / accepted) if accepted else None,
            "total_per_accepted_trial_usd": fmt((cash + labor) / accepted) if accepted else None,
            "list_rate_reference_usd_not_added": fmt(reference)
        }
    return output


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("csv_file")
    parser.add_argument("--hourly-rate", required=True)
    args = parser.parse_args()
    try:
        rate = nonnegative(args.hourly_rate, "hourly-rate")
        with open(args.csv_file, newline="", encoding="utf-8") as handle:
            result = summarize(list(csv.DictReader(handle)), rate)
    except (ValueError, KeyError, OSError) as error:
        parser.error(str(error))
    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
