#!/usr/bin/env python3
"""Validate synthetic arithmetic and its exact presentation in the memo. No I/O beyond reads."""

import json
from decimal import Decimal, ROUND_CEILING
from pathlib import Path
import unittest

ROOT = Path(__file__).resolve().parent
D = Decimal


def load_inputs():
    return json.loads((ROOT / "inputs.json").read_text(encoding="utf-8"))


def calculate(data, option):
    n = D(option["gpu_workers"])
    h = D(option["billed_hours_per_day"])
    q = D(option["accepted_documents_per_gpu_hour"])
    volume = D(data["documents_per_day"])
    days = D(data["days_per_month"])
    window = min(h, D(data["deadline_hours"])) - D(option["warmup_hours"])
    compute = n * h * days * D(option["usd_per_gpu_hour"])
    recurring = compute + D(option["storage_usd_per_month"]) + D(option["network_usd_per_month"])
    transition = D(option["transition_engineering_hours"]) * D(data["engineering_usd_per_hour"])
    return {
        "workers": n, "billed_gpu_hours": n * h, "window": window,
        "productive_gpu_hours": volume / q,
        "utilization_pct": volume / q / (n * h) * 100,
        "normal_capacity": n * q * window,
        "loss_capacity": (n - 1) * q * window,
        "compute": compute, "recurring": recurring,
        "unit_cost": recurring / (volume * days / 1000),
        "transition": transition,
        "transition_total": recurring * D(data["transition_months"]) + transition,
        "normalized": recurring + transition / D(data["transition_months"]),
    }


def money(value):
    return f"${value:,.0f}"


def table(data, results):
    rows = [
        ("GPU workers", "workers", lambda v: f"{v:.0f}"),
        ("Billed GPU-hours/day", "billed_gpu_hours", lambda v: f"{v:.0f}"),
        ("Processing hours available before stop/deadline", "window", lambda v: f"{v:.0f}"),
        ("Productive GPU-hours/day needed", "productive_gpu_hours", lambda v: f"{v:.0f}"),
        ("Productive time / billed GPU time", "utilization_pct", lambda v: f"{v:.1f}%"),
        ("Capacity/day with one worker lost at start", "loss_capacity", lambda v: f"{v:,.0f}"),
        ("Meets demand with that worker loss?", "loss_capacity", lambda v: "Yes" if v >= data["documents_per_day"] else "No"),
        ("Recurring compute/month", "compute", money),
        ("Recurring compute + storage + network/month", "recurring", money),
        ("Nominal recurring cost/1,000 accepted documents", "unit_cost", lambda v: f"${v:.2f}"),
        ("One-time transition allowance", "transition", money),
        ("First-three-month cost / 3", "normalized", money),
    ]
    lines = ["| Metric | Baseline | A: Tuned | B: Elastic |", "|---|---:|---:|---:|"]
    for label, key, formatter in rows:
        lines.append(f"| {label} | " + " | ".join(formatter(r[key]) for r in results) + " |")
    return "\n".join(lines)


def findings(data, results):
    baseline, tuned, elastic = results
    a, b = data["options"][1:]
    demand = D(data["documents_per_day"])
    saving = baseline["recurring"] - tuned["recurring"]
    pct = saving / baseline["recurring"] * 100
    minimum_q = (demand / ((D(a["gpu_workers"]) - 1) * tuned["window"])).quantize(D("0.01"), rounding=ROUND_CEILING)
    downside = tuned["loss_capacity"] * (1 - D(data["throughput_drop_fraction"]))
    resilient_hours = demand / ((D(b["gpu_workers"]) - 1) * D(b["accepted_documents_per_gpu_hour"])) + D(b["warmup_hours"])
    resilient = calculate(data, dict(b, billed_hours_per_day=resilient_hours))
    return "\n".join([
        f"- A saves {money(saving)}/month, or {pct:.1f}%, versus the modeled baseline before transition.",
        f"- A needs at least {minimum_q:,.2f} accepted documents/GPU-hour with one worker lost (threshold rounded up to 0.01 precision).",
        f"- A at {D(data['throughput_drop_fraction']) * 100:.0f}% lower throughput delivers {downside:,.0f} documents with one worker lost: {demand - downside:,.0f} short.",
        f"- B needs {resilient_hours:.0f} billed hours/day to survive that worker loss, including warmup. Booking all four slots for that window costs {money(resilient['recurring'])}/month, not {money(elastic['recurring'])}.",
        f"- A's first-three-month total is {money(tuned['transition_total'])}; B's original 14-hour plan is {money(elastic['transition_total'])}, just {money(elastic['transition_total'] - tuned['transition_total'])} more and still fails the worker-loss gate.",
    ])


def block(memo, name):
    start, end = f"<!-- validated-{name}:start -->", f"<!-- validated-{name}:end -->"
    return memo.split(start, 1)[1].split(end, 1)[0].strip()


class Validation(unittest.TestCase):
    def setUp(self):
        self.data = load_inputs()
        self.results = [calculate(self.data, o) for o in self.data["options"]]
        self.memo = (ROOT / "decision-memo.md").read_text(encoding="utf-8")

    def test_input_bounds(self):
        self.assertIs(self.data["synthetic"], True)
        self.assertEqual([o["id"] for o in self.data["options"]], ["baseline", "tuned", "elastic"])
        for key in ("days_per_month", "documents_per_day", "transition_months", "engineering_usd_per_hour"):
            self.assertGreater(D(self.data[key]), 0)
        self.assertTrue(0 < D(self.data["deadline_hours"]) <= 24)
        self.assertTrue(0 <= D(self.data["throughput_drop_fraction"]) < 1)
        for option in self.data["options"]:
            self.assertGreater(option["gpu_workers"], 1)
            self.assertTrue(0 < option["billed_hours_per_day"] <= 24)
            self.assertTrue(0 <= option["warmup_hours"] < min(option["billed_hours_per_day"], self.data["deadline_hours"]))
            self.assertGreater(option["accepted_documents_per_gpu_hour"], 0)
            self.assertGreater(D(option["usd_per_gpu_hour"]), 0)
            for key in ("storage_usd_per_month", "network_usd_per_month", "transition_engineering_hours"):
                self.assertGreaterEqual(D(option[key]), 0)

    def test_independent_golden_costs_and_capacity(self):
        # Hand-reconciled totals prevent a shared formula/rendering bug passing unnoticed.
        self.assertEqual([r["recurring"] for r in self.results], [18080, 9440, 7448])
        self.assertEqual([r["transition_total"] for r in self.results], [54240, 34320, 34344])
        self.assertEqual([r["loss_capacity"] for r in self.results], [126000, 81000, 58500])
        self.assertEqual([r["normal_capacity"] for r in self.results], [144000, 108000, 78000])
        self.assertEqual(self.data["documents_per_day"] * self.data["days_per_month"], 2160000)

    def test_memo_table_matches_every_calculated_cell(self):
        self.assertEqual(block(self.memo, "table"), table(self.data, self.results))

    def test_memo_sensitivity_matches_calculations(self):
        self.assertEqual(block(self.memo, "findings"), findings(self.data, self.results))
        self.assertIn("$8,640/month (47.8%)", self.memo)

    def test_boundary_and_counterexample(self):
        elastic = dict(self.data["options"][2], billed_hours_per_day=17)
        resilient = calculate(self.data, elastic)
        self.assertEqual(resilient["loss_capacity"], 72000)
        self.assertEqual(resilient["recurring"], 8744)
        elastic["billed_hours_per_day"] = 16
        self.assertLess(calculate(self.data, elastic)["loss_capacity"], 72000)
        tuned = dict(self.data["options"][1], accepted_documents_per_gpu_hour=1200)
        downside = calculate(self.data, tuned)
        self.assertEqual(downside["loss_capacity"], 64800)
        self.assertGreaterEqual(downside["normal_capacity"], 72000)

    def test_minimum_throughput_rounds_up_safely(self):
        tuned = dict(self.data["options"][1], accepted_documents_per_gpu_hour="1333.33")
        self.assertLess(calculate(self.data, tuned)["loss_capacity"], 72000)
        tuned["accepted_documents_per_gpu_hour"] = "1333.34"
        self.assertGreaterEqual(calculate(self.data, tuned)["loss_capacity"], 72000)


if __name__ == "__main__":
    unittest.main(verbosity=2)
