#!/usr/bin/env python3
"""Independent standard-library verifier for the PKO branch-value bundle."""

from __future__ import annotations

import csv
import hashlib
import json
import math
import sys
from fractions import Fraction
from pathlib import Path

if not __debug__:
    raise RuntimeError("verify.py must run without -O because its checks use assertions")

ROOT = Path(__file__).resolve().parent
EXPECTED_MANIFEST_NAMES = {
    "reference.json",
    "branch-scenarios.csv",
    "sensitivity.csv",
    "calculator.html",
    "generate.mjs",
    "verify.py",
    "verification-report.json",
    "README.md",
    "pko-bounty-value-branches.svg",
    "pko-bounty-value.webp",
}


def close(actual: float, expected: float) -> bool:
    return math.isclose(actual, expected, rel_tol=1e-11, abs_tol=1e-11)


def nearly_equal(left: float, right: float) -> bool:
    assert all(math.isfinite(value) for value in [left, right])
    difference = abs(left - right)
    assert math.isfinite(difference)
    scale = max(float.fromhex("0x0.0000000000001p-1022"), abs(left), abs(right))
    tolerance = 64 * sys.float_info.epsilon * scale
    return difference <= tolerance


def call_ev(q: float, w: float, b: float, h: float, loss: float) -> float:
    assert 0 <= q <= 1
    assert b >= 0
    assert all(math.isfinite(value) for value in [q, w, b, h, loss])
    return q * (w + b + h) + (1 - q) * loss


def threshold(fold: float, w: float, b: float, h: float, loss: float) -> dict:
    numerator = fold - loss
    denominator = w + b + h - loss
    if denominator == 0:
        if loss == fold:
            feasibility = "equal_at_all_probabilities"
        elif loss > fold:
            feasibility = "call_better_at_all_probabilities"
        else:
            feasibility = "fold_better_at_all_probabilities"
        return {"denominator": 0.0, "threshold": None, "inequality": None, "feasibility": feasibility}
    raw = numerator / denominator
    if denominator > 0:
        feasibility = "call_not_worse_at_all_probabilities" if raw <= 0 else (
            "no_feasible_call_probability" if raw > 1 else "bounded"
        )
        return {"denominator": denominator, "threshold": raw, "inequality": "q >= threshold", "feasibility": feasibility}
    feasibility = "no_feasible_call_probability" if raw < 0 else (
        "call_not_worse_at_all_probabilities" if raw >= 1 else "bounded"
    )
    return {"denominator": denominator, "threshold": raw, "inequality": "q <= threshold", "feasibility": feasibility}


def read_csv(name: str) -> list[dict[str, str]]:
    with (ROOT / name).open(newline="", encoding="utf-8") as handle:
        return list(csv.DictReader(handle))


def parse_optional_float(value: str) -> float | None:
    return None if value == "" else float(value)


def verify_manifest() -> None:
    lines = (ROOT / "MANIFEST.sha256").read_text(encoding="utf-8").strip().splitlines()
    assert len(lines) == 10
    seen = set()
    for line in lines:
        digest, name = line.split("  ", 1)
        assert len(digest) == 64 and all(character in "0123456789abcdef" for character in digest)
        assert name in EXPECTED_MANIFEST_NAMES
        assert name not in seen
        seen.add(name)
        target = (ROOT / name).resolve()
        assert target.is_file()
        assert hashlib.sha256(target.read_bytes()).hexdigest() == digest
    assert seen == EXPECTED_MANIFEST_NAMES


def main() -> None:
    model = json.loads((ROOT / "reference.json").read_text(encoding="utf-8"))
    assert model["schemaVersion"] == 1
    assert model["generatedOn"] == "2026-09-04"
    assert model["formula"]["callExpectedValue"] == "EV(call) = q * (W + B + H) + (1 - q) * L"
    assert model["formula"]["breakEvenProbability"] == "q* = (F - L) / (W + B + H - L)"
    assert "one declared value unit" in model["unitRule"]

    worked = model["workedCase"]
    assert worked["unit"] == "synthetic value units"
    assert worked["probabilityWinAndEliminate"] == 0.60
    assert worked["foldValue"] == 120
    assert worked["winContinuationValue"] == 165
    assert worked["immediateBountyValue"] == 50
    assert worked["addedHeadValue"] == 0
    assert worked["loseContinuationValue"] == 0
    assert close(worked["winBranchValue"], 215)
    assert close(worked["callExpectedValue"], 129)
    assert close(worked["callMinusFold"], 9)
    assert worked["decision"] == "call_higher_value"
    assert close(worked["threshold"]["threshold"], 120 / 215)
    assert Fraction(120, 215) == Fraction(24, 43)
    assert worked["threshold"]["exactFraction"] == "24/43"

    fixture_rows = model["fixtures"]
    assert len(fixture_rows) == 5
    for row in fixture_rows:
        assert row["unit"]
        expected_ev = call_ev(
            row["probabilityWinAndEliminate"],
            row["winContinuationValue"],
            row["immediateBountyValue"],
            row["addedHeadValue"],
            row["loseContinuationValue"],
        )
        expected_threshold = threshold(
            row["foldValue"],
            row["winContinuationValue"],
            row["immediateBountyValue"],
            row["addedHeadValue"],
            row["loseContinuationValue"],
        )
        assert close(row["callExpectedValue"], expected_ev)
        assert close(row["winBranchValue"], row["winContinuationValue"] + row["immediateBountyValue"] + row["addedHeadValue"])
        difference = expected_ev - row["foldValue"]
        expected_decision = "tie" if nearly_equal(expected_ev, row["foldValue"]) else (
            "call_higher_value" if difference > 0 else "fold_higher_value"
        )
        assert row["decision"] == expected_decision
        assert row["threshold"]["feasibility"] == expected_threshold["feasibility"]
        assert row["threshold"]["inequality"] == expected_threshold["inequality"]
        if expected_threshold["threshold"] is None:
            assert row["threshold"]["threshold"] is None
        else:
            assert close(row["threshold"]["threshold"], expected_threshold["threshold"])

    ledger = model["branchLedger"]
    assert len(ledger) == 4
    assert [row["outcome"] for row in ledger] == [
        "Fold benchmark", "Win and eliminate", "Lose", "Blended call EV"
    ]
    assert close(ledger[0]["probabilityWeightedValue"], 120)
    assert close(ledger[1]["probabilityWeightedValue"], 129)
    assert close(ledger[2]["probabilityWeightedValue"], 0)
    assert close(ledger[3]["probabilityWeightedValue"], 129)
    assert [row["rowType"] for row in ledger] == ["benchmark", "outcome", "outcome", "subtotal"]
    assert [row["isSubtotal"] for row in ledger] == [False, False, False, True]
    assert [row["contributesToCallEv"] for row in ledger] == [False, True, True, False]
    assert close(sum(
        row["probabilityWeightedValue"] for row in ledger if row["contributesToCallEv"]
    ), ledger[3]["probabilityWeightedValue"])
    assert "non-additive subtotal" in model["branchLedgerRule"]

    published_ledger = read_csv("branch-scenarios.csv")
    assert len(published_ledger) == 4
    for expected, published in zip(ledger, published_ledger):
        assert int(published["row"]) == expected["row"]
        assert published["row_type"] == expected["rowType"]
        assert (published["is_subtotal"] == "true") == expected["isSubtotal"]
        assert published["choice"] == expected["choice"]
        assert published["outcome"] == expected["outcome"]
        assert parse_optional_float(published["probability"]) == expected["probability"]
        assert parse_optional_float(published["branch_value"]) == expected["branchValue"]
        assert close(float(published["probability_weighted_value"]), expected["probabilityWeightedValue"])
        assert published["unit"] == "synthetic value units"
        assert (published["contributes_to_call_ev"] == "true") == expected["contributesToCallEv"]
        assert published["synthetic"] == "true"

    sensitivity = model["sensitivity"]["rows"]
    assert len(sensitivity) == 4
    assert [row["immediateBountyValue"] for row in sensitivity] == [0, 25, 50, 75]
    expected_fractions = [Fraction(8, 11), Fraction(12, 19), Fraction(24, 43), Fraction(1, 2)]
    for row, expected_fraction in zip(sensitivity, expected_fractions):
        expected = float(expected_fraction)
        assert row["synthetic"] is True
        assert row["addedHeadValue"] == 0
        assert close(row["breakEvenProbability"], expected)
        assert row["breakEvenFraction"] == str(expected_fraction)
        assert close(row["breakEvenPercent"], expected * 100)
    assert all(
        sensitivity[index]["breakEvenProbability"] > sensitivity[index + 1]["breakEvenProbability"]
        for index in range(len(sensitivity) - 1)
    )

    published_sensitivity = read_csv("sensitivity.csv")
    assert len(published_sensitivity) == 4
    for expected, published in zip(sensitivity, published_sensitivity):
        assert float(published["immediate_bounty_value_B"]) == expected["immediateBountyValue"]
        assert float(published["added_head_value_H"]) == 0
        assert close(float(published["win_branch_value_W_plus_B_plus_H"]), expected["winBranchValue"])
        assert close(float(published["break_even_probability"]), expected["breakEvenProbability"])
        assert published["break_even_fraction_exact"] == expected["breakEvenFraction"]
        assert close(float(published["break_even_percent"]), expected["breakEvenPercent"])
        assert published["unit"] == "synthetic value units"
        assert published["synthetic"] == "true"

    boundary_cases = [
        (threshold(10, 20, 0, 0, 0), "bounded", "q >= threshold"),
        (threshold(0, 20, 0, 0, 5), "call_not_worse_at_all_probabilities", "q >= threshold"),
        (threshold(30, 20, 0, 0, 0), "no_feasible_call_probability", "q >= threshold"),
        (threshold(10, 10, 0, 0, 10), "equal_at_all_probabilities", None),
        (threshold(12, 10, 0, 0, 10), "fold_better_at_all_probabilities", None),
        (threshold(8, 0, 0, 0, 10), "bounded", "q <= threshold"),
        (threshold(1.0000000000004, 1, 0, 0, 0), "no_feasible_call_probability", "q >= threshold"),
        (threshold(4e-13, 1, 0, 0, 0), "bounded", "q >= threshold"),
    ]
    for result, expected_feasibility, expected_inequality in boundary_cases:
        assert result["feasibility"] == expected_feasibility
        assert result["inequality"] == expected_inequality
    assert boundary_cases[-2][0]["threshold"] > 1
    assert boundary_cases[-1][0]["threshold"] > 0

    sources = model["sources"]
    assert len(sources) == 3
    assert all(source["accessed"] == "2026-09-04" for source in sources)
    assert any(source["url"] == "https://www.pokerstars.com/poker/tournaments/types/" for source in sources)
    assert any(source["url"] == "https://www.pokerstars.com/help/articles/trn-knockout/213397/" for source in sources)
    assert any(source["url"] == "https://arxiv.org/abs/0911.3100" for source in sources)
    assert all("synthetic" in item.lower() for item in model["boundaries"][:1])
    assert any("not automatically raw showdown equity" in item for item in model["boundaries"])
    assert any("zero incremental-transfer baseline" in item for item in model["boundaries"])

    report = json.loads((ROOT / "verification-report.json").read_text(encoding="utf-8"))
    assert report["recordType"] == "verification-specification"
    assert report["status"] == "not-executed"
    assert report["specifiedAt"] == "2026-09-04T19:57:17Z"
    assert "not proof" in report["statusBoundary"]
    assert len(report["implementations"]) == 2
    assert report["tieComparisonTolerance"]["relativeNumberEpsilonMultiplier"] == 64
    assert report["tieComparisonTolerance"]["scaleFloor"] == float.fromhex("0x0.0000000000001p-1022")
    assert report["checks"] == {
        "syntheticFixtures": 5,
        "branchLedgerRows": 4,
        "sensitivityRows": 4,
        "exactRationalThresholds": 4,
        "thresholdBoundaryCases": 8,
        "sourceRecords": 3,
        "sha256Artifacts": 10,
    }

    verify_manifest()
    print(
        "Verified 5 synthetic fixtures, 4 branch-ledger rows, 4 exact rational thresholds, "
        "4 sensitivity rows, "
        "8 threshold boundary cases, 3 source records, and 10 SHA-256 artifacts."
    )


if __name__ == "__main__":
    main()
