#!/usr/bin/env python3
"""
SFT Engine Runner (Template Generator)

A lightweight, reproducible entrypoint:

- reads a small config JSON describing a system context
- outputs either a WSC template (with stability_modulus filled where possible)
  or a WOB stub with correct code and evidence fields.

It produces the correct artifact shape and enforces target correctness and field consistency at the artifact interface.

The runner also emits a minimal validator predicate ledger so the template
is compatible with the completeness audits.
"""

import json
import math
import sys
from pathlib import Path
from typing import Any


def fail(msg: str) -> None:
    print("FAIL:", msg)
    sys.exit(1)


def load_predicate_map() -> dict[str, str]:
    here = Path(__file__).resolve()
    spec = (here.parent / "../contracts/validator_predicates.json").resolve()
    if not spec.exists():
        return {}
    data = json.loads(spec.read_text(encoding="utf-8"))
    m: dict[str, str] = {}
    for p in data.get("predicates", []):
        pid = str(p.get("predicate", "")).strip()
        code = str(p.get("wob_code", "")).strip()
        if pid and code:
            m[pid] = code
    return m


PRED_TO_WOB = load_predicate_map()


def required_predicates(mechanism: str, target_type: str, witness_kind: str) -> list[str]:
    req = ["V01"]
    if mechanism in ("LDW", "HW"):
        req += ["V02", "V04"]
        if witness_kind == "holonomy_multiple":
            req += ["V05"]
    if mechanism == "AWT":
        req += ["V08", "V09"]
    if mechanism == "CMT" or target_type == "cst":
        req += ["V06"]
    if mechanism == "MXC":
        req += ["V07"]
        if target_type == "cst":
            req += ["V06"]
    out = []
    for x in req:
        if x not in out:
            out.append(x)
    return out


def normalize_target_type(x: Any) -> str:
    t = str(x or "").strip().lower()
    if t in ("circle", "torus", "cst", "embedded"):
        return t
    return "circle"


def default_baseline(target_type: str, target: str) -> dict[str, Any]:
    lt = str(target or "").lower()
    if target_type == "cst" or "cst" in lt:
        return {"kind": "coupled_haar", "target_type": "cst", "description": "Coupled Haar on CST"}
    if target_type == "embedded":
        return {"kind": "haar", "target_type": "embedded", "description": "Haar on the induced circle target (embedded subsystem)"}
    return {"kind": "haar", "target_type": target_type, "description": "Haar on the correct target"}


def parse_baseline(b: Any, target_type: str, target: str) -> dict[str, Any]:
    if b is None:
        return default_baseline(target_type, target)
    if isinstance(b, str):
        s = b.strip()
        kind = "coupled_haar" if "coupled" in s.lower() else "haar"
        return {"kind": kind, "target_type": target_type, "description": s}
    if not isinstance(b, dict):
        fail("baseline must be an object, a string description, or null")
    kind = str(b.get("kind", "")).strip()
    bt = str(b.get("target_type", "")).strip().lower()
    if kind not in ("haar", "coupled_haar"):
        fail("baseline.kind must be haar or coupled_haar")
    if bt not in ("circle", "torus", "cst", "embedded"):
        fail("baseline.target_type must be circle/torus/cst/embedded")
    desc = b.get("description", None)
    if desc is not None and not isinstance(desc, str):
        fail("baseline.description must be a string when present")
    # enforce internal compatibility
    if bt != target_type:
        fail("baseline.target_type must match declared target_type")
    if target_type == "cst" and kind != "coupled_haar":
        fail("CST target requires baseline.kind=coupled_haar")
    if target_type != "cst" and kind != "haar":
        fail("Non-CST target requires baseline.kind=haar")
    return {"kind": kind, "target_type": bt, "description": desc}


def main() -> None:
    if len(sys.argv) != 2:
        fail("Usage: engine_runner.py path/to/system_config.json")

    cfg_path = Path(sys.argv[1])
    if not cfg_path.exists():
        fail(f"Config not found: {cfg_path}")
    cfg = json.loads(cfg_path.read_text(encoding="utf-8"))

    mode = cfg.get("mode", "wsc")  # wsc or wob
    sft_version = cfg.get("sft_version", "SFT-0.2.3")
    P = float(cfg.get("period_P", 2 * math.pi))

    tier = cfg.get("witness_tier", "WTR-E")
    mechanism = str(cfg.get("mechanism", "LDW")).strip()
    target_type = normalize_target_type(cfg.get("target_type", "circle"))
    target = str(cfg.get("target", "T_P")).strip()

    baseline = parse_baseline(cfg.get("baseline", None), target_type, target)

    if mode == "wob":
        wob = {
            "sft_version": sft_version,
            "obstruction_id": cfg.get("obstruction_id", "WOB-TEMPLATE-0001"),
            "witness_tier": tier,
            "obstruction_code": cfg.get("obstruction_code", "WOB-01-no-subsystem"),
            "description": cfg.get("description", "Obstruction stub produced by engine runner."),
            "evidence": cfg.get("evidence", ["Add evidence bullet(s) here."]),
            "recommended_next_moves": cfg.get("recommended_next_moves", ["Add next-move bullet(s) here."]),
        }
        print(json.dumps(wob, indent=2))
        return

    # default: WSC template
    k = int(cfg.get("k", 1))
    witness_kind = str(cfg.get("witness_kind", "degree")).strip()
    margins = cfg.get("margins", {"LCM_pi": 1.0})
    assumptions = cfg.get("assumptions", [])
    perturb = cfg.get("perturbation_model", "PM-2-lift-sup")

    req_preds = required_predicates(mechanism, target_type, witness_kind)
    ledger = []
    for pid in req_preds:
        ledger.append(
            {
                "predicate": pid,
                "holds": True,
                "wob_code": PRED_TO_WOB.get(pid, ""),
                "evidence_refs": cfg.get(
                    "predicate_evidence_refs",
                    ["engine/contracts/VALIDATOR_PREDICATE_SPEC.md"],
                ),
            }
        )

    wsc = {
        "sft_version": sft_version,
        "certificate_id": cfg.get("certificate_id", "WSC-TEMPLATE-0001"),
        "witness_tier": tier,
        "mechanism": mechanism,
        "target_type": target_type,
        "target": target,
        "baseline": baseline,
        "period_P": P,
        "witness_value": {
            "kind": witness_kind,
            "integer": k,
            "notes": cfg.get("witness_notes", ""),
        },
        "perturbation_model": perturb,
        "margins": margins,
        "assumptions": assumptions,
        "validator_predicates": ledger,
        "artifacts": {
            "theorem_refs": cfg.get("theorem_refs", []),
            "files": cfg.get(
                "files",
                [
                    "engine/schemas/witness_stability_certificate_schema.json",
                    "engine/audit/audit_wsc.py",
                ],
            ),
        },
    }

    sm = {"k": k, "target_rank": int(cfg.get("target_rank", 1))}
    if "LCM_pi" in margins:
        sm["LCM_pi"] = float(margins["LCM_pi"])
    if "HMR_P" in margins:
        sm["HMR_P"] = float(margins["HMR_P"])
    if "b" in cfg:
        sm["b"] = float(cfg["b"])
    if "lambda" in cfg:
        sm["lambda"] = float(cfg["lambda"])
    wsc["stability_modulus"] = sm

    ccs = cfg.get("convergence_certificates", None)
    if ccs is not None:
        wsc["convergence_certificates"] = ccs

    print(json.dumps(wsc, indent=2))


if __name__ == "__main__":
    main()
