"""Preview supplied rule bodies from recorded decisions; no network or execution."""
import hashlib
import json
import math
import re
import sys
from pathlib import Path

LABELS = ["Apply rule", "Skip rule", "Missing context"]


def preview_context(fixture, results):
    if fixture.get("kind") != "original-rule-applicability-fixture" or fixture.get("version") != 1:
        raise ValueError("Unknown fixture version")
    rules = fixture.get("rules")
    if not isinstance(rules, list) or not 1 <= len(rules) <= 100:
        raise ValueError("Expected a bounded rule list")
    by_rule = {}
    for rule in rules:
        identifier = rule.get("id")
        if not isinstance(identifier, str) or not re.fullmatch(r"[a-z0-9-]{1,80}", identifier) or identifier in by_rule:
            raise ValueError("Invalid or duplicate rule ID")
        if any(not isinstance(rule.get(k), str) or not rule[k].strip() for k in ("description", "body")):
            raise ValueError("Rule scope and body are required")
        by_rule[identifier] = rule
    samples = fixture.get("samples")
    observations = results.get("results")
    if not isinstance(samples, list) or not 1 <= len(samples) <= 1000 or not isinstance(observations, list):
        raise ValueError("Expected bounded samples and observations")
    if len(observations) != len(samples):
        raise ValueError("Every sample needs exactly one recorded result")
    by_result = {}
    for result in observations:
        identifier = result.get("id")
        if identifier in by_result:
            raise ValueError("Duplicate observation")
        by_result[identifier] = result
    mandatory = fixture.get("mandatoryRules")
    if not isinstance(mandatory, list) or any(not isinstance(r, dict) or not isinstance(r.get("body"), str) or not r["body"].strip() for r in mandatory):
        raise ValueError("Mandatory context must be explicit text")
    previews, seen = [], set()
    for sample in samples:
        identifier = sample.get("id")
        if not isinstance(identifier, str) or not re.fullmatch(r"[a-z0-9-]{1,80}", identifier) or identifier in seen:
            raise ValueError("Invalid or duplicate sample ID")
        seen.add(identifier)
        rule = by_rule.get(sample.get("ruleId"))
        result = by_result.get(identifier)
        if rule is None or result is None:
            raise ValueError("Unknown rule or missing observation")
        request = sample["request"]
        if request.get("choices") != LABELS or not isinstance(request.get("question"), str):
            raise ValueError("Unexpected rubric or labels")
        context = json.loads(request["context"])
        if context.get("candidateRule", {}).get("description") != rule["description"]:
            raise ValueError("Request scope does not match the delivered rule")
        digest = hashlib.sha256(json.dumps(request, separators=(",", ":"), ensure_ascii=False).encode()).hexdigest()
        if result.get("requestSha256") != digest:
            raise ValueError("Stale result: request identity changed")
        probabilities = result.get("probabilities", {})
        if set(probabilities) != set(LABELS):
            raise ValueError("Unexpected probability labels")
        values = list(probabilities.values())
        if any(type(v) not in (int, float) or not math.isfinite(v) or not 0 <= v <= 1 for v in values):
            raise ValueError("Invalid probability")
        if not math.isclose(sum(values), 1, abs_tol=0.02):
            raise ValueError("Distribution must sum to one")
        leaders = [label for label in LABELS if probabilities[label] == max(values)]
        if result.get("choice") not in leaders:
            raise ValueError("Observed choice is not a leading label")
        outcome = "Tie review" if len(leaders) > 1 else leaders[0]
        previews.append({"sampleId": identifier, "ruleId": sample["ruleId"], "decision": outcome,
                         "mandatoryContext": list(mandatory),
                         "optionalContext": [rule["body"]] if outcome == "Apply rule" else [],
                         "pending": outcome in ("Missing context", "Tie review"),
                         "probabilities": probabilities})
    return {"mode": "preview-only", "previews": previews}


if __name__ == "__main__":
    if len(sys.argv) != 3:
        raise SystemExit("Usage: python build-rule-context.py rule-samples.json saved-api-results.json")
    fixture, results = [json.loads(Path(path).read_text(encoding="utf-8")) for path in sys.argv[1:]]
    print(json.dumps(preview_context(fixture, results), indent=2, allow_nan=False))
