#!/usr/bin/env python3
"""MSC-P-001 deterministic protocol-contract validator. MIT; standard library only."""

from __future__ import annotations

import argparse
import csv
import hashlib
import math
from pathlib import Path


DEFAULT_DATA = Path(__file__).resolve().parents[1] / "datasets" / "msc-p001-testable-question.csv"
REQUIRED = (
    "question_id", "population", "eligibility", "unit_of_assignment", "unit_of_analysis",
    "intervention", "comparator", "outcome_label", "outcome_definition", "outcome_window_days",
    "estimand_formula", "intercurrent_event_strategy", "missing_outcome_rule", "estimator",
    "ci_method", "ci_bias_correction", "ci_solver_tolerance", "ci_solver_max_iterations",
    "confidence_level_percent", "alpha_two_sided", "multiplicity_rule",
    "minimum_useful_effect_pp", "direction", "success_bound", "success_operator",
    "falsifier_bound", "falsifier_operator", "inconclusive_rule", "protocol_deviation_rule",
    "data_status", "license",
)


def canonical_record(row: dict[str, str]) -> str:
    return "\n".join(f"{field}={row[field].strip()}" for field in REQUIRED)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("dataset", nargs="?", type=Path, default=DEFAULT_DATA)
    return parser.parse_args()


def validate(row: dict[str, str]) -> None:
    missing = [field for field in REQUIRED if not row.get(field, "").strip()]
    if missing:
        raise ValueError(f"missing required fields: {', '.join(missing)}")
    horizon = int(row["outcome_window_days"])
    threshold = float(row["minimum_useful_effect_pp"])
    tolerance = float(row["ci_solver_tolerance"])
    iterations = int(row["ci_solver_max_iterations"])
    confidence = float(row["confidence_level_percent"])
    alpha = float(row["alpha_two_sided"])
    if not all(math.isfinite(value) for value in (threshold, tolerance, confidence, alpha)):
        raise ValueError("threshold, tolerance, confidence, and alpha must be finite")
    if horizon <= 0 or threshold <= 0:
        raise ValueError("outcome window and minimum useful effect must be positive")
    if tolerance <= 0 or iterations <= 0:
        raise ValueError("confidence-interval solver controls must be positive")
    if not 0 < confidence < 100 or not 0 < alpha < 1:
        raise ValueError("confidence and alpha must lie in their open probability ranges")
    if abs(confidence - 100 * (1 - alpha)) > 1e-12:
        raise ValueError("confidence level and two-sided alpha are inconsistent")
    if row["direction"] != "increase":
        raise ValueError("this reference contract supports direction=increase only")
    expected = ("lower", ">=", "upper", "<")
    actual = tuple(row[field] for field in ("success_bound", "success_operator", "falsifier_bound", "falsifier_operator"))
    if actual != expected:
        raise ValueError(f"direction and decision bounds are inconsistent: expected {expected}, got {actual}")
    if "every assigned eligible subscriber as denominator" not in row["estimator"].lower():
        raise ValueError("the ITT denominator must include every assigned eligible subscriber")
    if "regardless of delivery" not in row["intercurrent_event_strategy"].lower():
        raise ValueError("the treatment-policy strategy must address non-delivery and post-assignment exposure")
    if "otherwise stop confirmatory analysis" not in row["missing_outcome_rule"].lower():
        raise ValueError("the missing-outcome rule must fail closed when logging coverage is incomplete")
    if "one primary outcome" not in row["multiplicity_rule"].lower():
        raise ValueError("the confirmatory multiplicity scope must be declared")
    if row["ci_bias_correction"] != "N/(N-1)" or "inverting the constrained score statistic" not in row["ci_method"].lower():
        raise ValueError("the Miettinen-Nurminen interval algorithm and bias correction must be explicit")
    if "post-opening deviation" not in row["protocol_deviation_rule"].lower():
        raise ValueError("post-opening protocol deviations must be disclosed")
    if "no observed outcome" not in row["data_status"].lower():
        raise ValueError("the protocol fixture must not masquerade as observed evidence")
    if row["license"] != "CC0-1.0":
        raise ValueError("the distributed synthetic record must use CC0-1.0")


def question(row: dict[str, str]) -> str:
    return (
        f"Among {row['population'].lower()}, does {row['intervention'].lower()} versus "
        f"{row['comparator'].lower()} increase {row['outcome_label'].lower()} within "
        f"{row['outcome_window_days']} days by at least "
        f"{row['minimum_useful_effect_pp']} percentage point?"
    )


def main() -> None:
    data_path = parse_args().dataset.resolve()
    with data_path.open(encoding="utf-8", newline="") as handle:
        reader = csv.DictReader(handle)
        if tuple(reader.fieldnames or ()) != REQUIRED:
            raise ValueError("dataset header must exactly match the protocol contract")
        rows = list(reader)
    if len(rows) != 1:
        raise ValueError("the reference dataset must contain exactly one protocol record")
    row = rows[0]
    validate(row)
    digest = hashlib.sha256(canonical_record(row).encode("utf-8")).hexdigest()
    print(f"question_id={row['question_id']}")
    print("protocol_complete=true")
    print(f"testable_question={question(row)}")
    print(f"estimand={row['estimand_formula']}")
    print(f"estimator={row['estimator']}")
    print(f"confidence_interval={row['ci_method']}; bias correction={row['ci_bias_correction']}; tolerance={row['ci_solver_tolerance']}; max iterations={row['ci_solver_max_iterations']}; {row['confidence_level_percent']}%; two-sided alpha={row['alpha_two_sided']}")
    print(f"success_rule={row['success_bound']} bound {row['success_operator']} +{row['minimum_useful_effect_pp']} percentage point")
    print(f"falsifier={row['falsifier_bound']} bound {row['falsifier_operator']} +{row['minimum_useful_effect_pp']} percentage point")
    print(f"data_status={row['data_status']}")
    print(f"protocol_sha256={digest}")


if __name__ == "__main__":
    main()
