#!/usr/bin/env python3
"""Unit-economics calculator for task F-02 (runbooks/R2-cost-measurement.md).

Turns the two numbers you read off the platform dashboard -- total spent and total
seconds of call time -- into the measured $/min, and runs that rate through the tier
table so the margin-floor verdict is arithmetic rather than opinion.

The floor (infra <= 40% of the monthly fee) is docs/PRICING.md section 3, binding.

    python3 runbooks/cost_calc.py --spent 2.47 --seconds 1380
    python3 runbooks/cost_calc.py --spent 2.47 --seconds 1380 --fee 199 --cap 800
    python3 runbooks/cost_calc.py --rate 0.10          # model a rate without calls

No dependencies. Run the tests with:  python3 runbooks/cost_calc.py --test
"""

import argparse
import sys

MARGIN_FLOOR = 0.40  # docs/PRICING.md section 3 -- infra must be <= 40% of fee

# (name, monthly fee, included minutes, number + misc fixed cost) -- PRICING.md section 1
TIERS = [
    ("Starter", 99, 250, 4.0),
    ("Growth", 149, 500, 4.0),
    ("Pro", 199, 800, 5.0),
]


def measured_rate(spent, seconds):
    """Measured all-in dollars per call minute."""
    if seconds <= 0:
        raise ValueError("seconds must be > 0 -- you cannot measure a rate from zero call time")
    if spent < 0:
        raise ValueError("spent must not be negative")
    return spent / (seconds / 60.0)


def tier_economics(rate, fee, cap_minutes, fixed):
    """Infra cost and margin-floor verdict for one tier at full cap utilisation."""
    if fee <= 0:
        raise ValueError("fee must be > 0")
    infra = rate * cap_minutes + fixed
    share = infra / fee
    return {
        "infra": infra,
        "share": share,
        "passes": share <= MARGIN_FLOOR,
        "gross_margin": fee - infra,
        # Largest cap that would satisfy the floor at this rate, for the re-cut option.
        "max_cap": int((fee * MARGIN_FLOOR - fixed) / rate) if rate > 0 else 0,
    }


def report(rate, tiers, source):
    lines = []
    lines.append("")
    lines.append(f"  Measured all-in rate:  ${rate:.4f}/min   ({source})")
    lines.append(f"  Planning assumption:   $0.1000/min   (docs/PRICING.md section 2)")
    delta = (rate - 0.10) / 0.10 * 100
    verdict = "better than plan" if rate < 0.10 else ("on plan" if abs(delta) < 1 else "WORSE than plan")
    lines.append(f"  Difference:            {delta:+.1f}%   -- {verdict}")
    lines.append("")
    lines.append(f"  Margin floor: infra <= {MARGIN_FLOOR:.0%} of fee, at full cap utilisation")
    lines.append("")
    lines.append(f"  {'Tier':<9} {'Fee':>6} {'Cap':>6} {'Infra':>9} {'% of fee':>9}  {'':<5} {'Max cap @floor':>15}")
    lines.append(f"  {'-' * 9} {'-' * 6} {'-' * 6} {'-' * 9} {'-' * 9}  {'-' * 5} {'-' * 15}")

    any_fail = False
    for name, fee, cap, fixed in tiers:
        e = tier_economics(rate, fee, cap, fixed)
        mark = "PASS" if e["passes"] else "FAIL"
        if not e["passes"]:
            any_fail = True
        lines.append(
            f"  {name:<9} ${fee:>5} {cap:>6} ${e['infra']:>8.2f} {e['share']:>8.1%}  {mark:<5} {e['max_cap']:>15}"
        )

    lines.append("")
    if any_fail:
        lines.append("  >> At least one tier breaches the 40% floor at its cap.")
        lines.append("     Options (founder decision -- see R2 step 4):")
        lines.append("       (a) cut the cap to the 'Max cap @floor' figure above")
        lines.append("       (b) raise the price for that tier")
        lines.append("       (c) accept that the floor governs ACTUAL usage, and a")
        lines.append("           capped-out client triggers a re-tier by design")
    else:
        lines.append("  >> All tiers hold the 40% floor at full cap utilisation.")

    if rate > 0.13:
        lines.append("")
        lines.append("  >> RATE ABOVE $0.13/min. R2 step 4 says stop before pitching clients:")
        lines.append("     evaluate the Vapi fallback (D1) or re-cut the caps first.")
    lines.append("")
    return "\n".join(lines)


# --------------------------------------------------------------------------- tests

def _test():
    failures = []

    def check(label, got, want, tol=1e-6):
        ok = abs(got - want) <= tol if isinstance(want, float) else got == want
        if not ok:
            failures.append(f"{label}: got {got!r}, want {want!r}")

    # $2.47 over 1380s = 23 minutes -> $0.1074/min
    check("rate from spend/seconds", measured_rate(2.47, 1380), 2.47 / 23.0)
    check("rate, exactly one minute", measured_rate(0.10, 60), 0.10)

    # Starter at the planning rate: 250 * 0.10 + 4 = $29 on $99 = 29.3% -> passes
    s = tier_economics(0.10, 99, 250, 4.0)
    check("starter infra", s["infra"], 29.0)
    check("starter passes floor", s["passes"], True)

    # Pro at the planning rate on the budget stack: 800 * 0.10 + 5 = $85 on $199
    # = 42.7% -> BREACHES. This is the open finding recorded in R2 step 4; if this
    # assertion ever flips, PRICING.md section 3 has been changed and R2 needs revisiting.
    p = tier_economics(0.10, 199, 800, 5.0)
    check("pro infra", p["infra"], 85.0)
    check("pro breaches floor at cap", p["passes"], False)
    check("pro share", round(p["share"], 3), 0.427)
    # Floor-compliant Pro cap at $0.10: (199*0.40 - 5) / 0.10 = 746
    check("pro max cap at floor", p["max_cap"], 746)

    # A cheap rate rescues Pro: 800 * 0.085 + 5 = $73 on $199 = 36.7%
    check("pro passes at $0.085", tier_economics(0.085, 199, 800, 5.0)["passes"], True)

    # Guard rails
    for bad, label in (
        ((lambda: measured_rate(1.0, 0)), "zero seconds must raise"),
        ((lambda: measured_rate(-1.0, 60)), "negative spend must raise"),
        ((lambda: tier_economics(0.1, 0, 250, 4.0)), "zero fee must raise"),
    ):
        try:
            bad()
            failures.append(label)
        except ValueError:
            pass

    if failures:
        print("FAILED:")
        for f in failures:
            print("  -", f)
        return 1
    print("All cost_calc tests passed.")
    return 0


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--spent", type=float, help="total dollars spent, from the dashboard")
    ap.add_argument("--seconds", type=float, help="total call seconds across the test calls")
    ap.add_argument("--rate", type=float, help="use a known $/min instead of measuring")
    ap.add_argument("--fee", type=float, help="check a single custom tier: monthly fee")
    ap.add_argument("--cap", type=float, help="check a single custom tier: included minutes")
    ap.add_argument("--number-cost", type=float, default=4.0, help="fixed number + misc cost (default 4.00)")
    ap.add_argument("--test", action="store_true", help="run the self-tests")
    args = ap.parse_args()

    if args.test:
        return _test()

    if args.rate is not None:
        rate, source = args.rate, "supplied rate, not measured"
    elif args.spent is not None and args.seconds is not None:
        rate = measured_rate(args.spent, args.seconds)
        source = f"${args.spent:.2f} over {args.seconds:.0f}s = {args.seconds / 60:.1f} min"
    else:
        ap.error("give either --rate, or both --spent and --seconds (or --test)")

    if args.fee and args.cap:
        tiers = [("Custom", args.fee, args.cap, args.number_cost)]
    else:
        tiers = TIERS

    print(report(rate, tiers, source))
    return 0


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