import json
import os
from pathlib import Path

ROOT = Path(".")
INPUT_JSON = ROOT / "results" / "row20_h0_attribution" / "row20_thin_slice_attribution.json"
OUTPUT_JSON = ROOT / "results" / "row20_h0_attribution" / "row20_SN_geometry_probe.json"
OUTPUT_REPORT = ROOT / "results" / "row20_h0_attribution" / "row20_SN_geometry_probe_REPORT.md"


def load_json(path):
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def find_block(data, tag):
    for block in data.get("blocks", []):
        if block.get("tag") == tag:
            return block
    raise ValueError(f"Block not found: {tag}")


def main():
    data = load_json(INPUT_JSON)
    sn = find_block(data, "SN")
    nodes = sn["nodes"]

    # Sort by redshift embedded in label
    def get_z(n):
        label = n["slice"]
        return float(label.split("z=")[1])

    nodes = sorted(nodes, key=get_z)

    residuals = []
    for n in nodes:
        residuals.append({
            "slice": n["slice"],
            "z": get_z(n),
            "data": n["data"],
            "theory": n["theory"],
            "residual": n["residual_after_gate_handling"],
            "pull_sigma": n["diag_pull_sigma"],
            "chi2": n["white_chi2_contribution"],
        })

    # Simple curvature diagnostic: midpoint residual relative to linear interpolation
    # between low-z and high-z residuals.
    low = residuals[0]
    mid = residuals[1]
    high = residuals[2]

    z0, r0 = low["z"], low["residual"]
    z1, r1 = mid["z"], mid["residual"]
    z2, r2 = high["z"], high["residual"]

    interp_mid = r0 + (r2 - r0) * ((z1 - z0) / (z2 - z0))
    midpoint_curvature = r1 - interp_mid

    # Same for pull space
    p0, p1, p2 = low["pull_sigma"], mid["pull_sigma"], high["pull_sigma"]
    interp_pull_mid = p0 + (p2 - p0) * ((z1 - z0) / (z2 - z0))
    midpoint_pull_curvature = p1 - interp_pull_mid

    interpretation = []

    interpretation.append(
        "SN geometry probe tests whether the z=0.35 node is a simple offset or a midpoint curvature/pivot residual."
    )

    if abs(midpoint_pull_curvature) > 2:
        interpretation.append(
            "The z=0.35 residual is not explained by a simple linear offset between low-z and high-z SN nodes."
        )
    else:
        interpretation.append(
            "The z=0.35 residual is broadly compatible with a smooth offset trend."
        )

    if mid["chi2"] > low["chi2"] and mid["chi2"] > high["chi2"]:
        interpretation.append(
            "The midpoint node is the dominant SN stress contributor."
        )

    result = {
        "signature": "ROW20_SN_GEOMETRY_PROBE_v1_NATIVE_DIAGNOSTIC",
        "context": "DIAGNOSTIC_NON_GATING",
        "rules": data.get("rules", {}),
        "SN_block": {
            "chi2": sn["chi2"],
            "dof": sn["dof"],
            "chi2_per_dof": sn["chi2_per_dof"],
            "nuisance": sn.get("nuisance", {}),
            "nodes": residuals,
        },
        "midpoint_geometry": {
            "midpoint_slice": mid["slice"],
            "linear_interpolated_mid_residual": interp_mid,
            "actual_mid_residual": r1,
            "midpoint_curvature_residual": midpoint_curvature,
            "linear_interpolated_mid_pull": interp_pull_mid,
            "actual_mid_pull": p1,
            "midpoint_pull_curvature": midpoint_pull_curvature,
        },
        "interpretation": interpretation,
        "verdict": (
            "SN_midpoint_geometry_probe_complete; diagnostic only; "
            "no physics, thresholds, datasets, parameters, or gate verdicts changed."
        ),
    }

    OUTPUT_JSON.parent.mkdir(parents=True, exist_ok=True)

    with open(OUTPUT_JSON, "w", encoding="utf-8") as f:
        json.dump(result, f, indent=2)

    with open(OUTPUT_REPORT, "w", encoding="utf-8") as f:
        f.write("# UTFANSWF v21 — Row 20 SN Geometry Probe\n\n")
        f.write("**Context:** DIAGNOSTIC_NON_GATING. No physics, thresholds, datasets, parameters, or gate verdicts changed.\n\n")

        f.write("## SN nodes\n\n")
        f.write("| Slice | z | data | theory | residual | pull sigma | chi2 |\n")
        f.write("|---|---:|---:|---:|---:|---:|---:|\n")
        for n in residuals:
            f.write(
                f"| {n['slice']} | {n['z']:.3f} | {n['data']:.6f} | "
                f"{n['theory']:.6f} | {n['residual']:.6f} | "
                f"{n['pull_sigma']:.6f} | {n['chi2']:.6f} |\n"
            )

        f.write("\n## Midpoint geometry\n\n")
        f.write("| Quantity | Value |\n")
        f.write("|---|---:|\n")
        f.write(f"| midpoint slice | {mid['slice']} |\n")
        f.write(f"| linear interpolated midpoint residual | {interp_mid:.6f} |\n")
        f.write(f"| actual midpoint residual | {r1:.6f} |\n")
        f.write(f"| midpoint curvature residual | {midpoint_curvature:.6f} |\n")
        f.write(f"| linear interpolated midpoint pull | {interp_pull_mid:.6f} |\n")
        f.write(f"| actual midpoint pull | {p1:.6f} |\n")
        f.write(f"| midpoint pull curvature | {midpoint_pull_curvature:.6f} |\n")

        f.write("\n## Interpretation\n\n")
        for item in interpretation:
            f.write(f"- {item}\n")

        f.write("\n## Verdict\n\n")
        f.write(result["verdict"] + "\n")

    print("Wrote:")
    print(os.path.abspath(OUTPUT_JSON))
    print(os.path.abspath(OUTPUT_REPORT))
    print()
    print("SN geometry probe:")
    print(f"SN chi2/dof={sn['chi2_per_dof']:.6f}")
    print(f"midpoint residual={r1:.6f}")
    print(f"midpoint pull={p1:.6f} sigma")
    print(f"midpoint pull curvature={midpoint_pull_curvature:.6f}")


if __name__ == "__main__":
    main()