import json
from pathlib import Path

import numpy as np

from SR_analysis.tools import audit_round1
from SR_analysis.tools.audit_round1 import audit_run
from SR_analysis.utils.provenance import hash_file, hash_json, sha256_text


def _write_json(path: Path, value: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(value), encoding="utf-8")


def _fixture(root: Path, *, bad_expression_hash: bool = False) -> None:
    data = root / "data/runs/data-one/illusion/scene"
    results = root / "results/runs/result-one"
    data.mkdir(parents=True)
    results.mkdir(parents=True)
    np.savez(
        data / "controlled.npz",
        schema_version=np.array("sr-stage1-npz-v2"),
        sensors_post=np.zeros((3, 6)),
        forces_post=np.zeros((3, 6)),
        actions_norm=np.zeros((3, 3)),
        dt_c=np.array(0.2),
    )
    telemetry = results / "telemetry.npz"
    np.savez(telemetry, schema_version=np.array("sr-telemetry-v1"), values=np.zeros(3))
    expression = "2*x"
    formula = {
        "schema_version": "1.0",
        "artifact_id": "formula-1",
        "run_id": "result-one",
        "feature_names": ["x"],
        "fitted_expression": expression,
        "deployment_expression": expression,
        "fitted_expression_hash": sha256_text(expression),
        "deployment_expression_hash": "0" * 64 if bad_expression_hash else sha256_text(expression),
        "alignment_metadata": {"alignment": "causal_post_state_to_next_action"},
        "dt_c_metadata": {"value": 0.2},
        "g_metadata": {"operator": "identity"},
        "git_sha": "abc",
        "environment": {"conda_env": "sr_env"},
        "provenance": {
            "fit": {
                "mode": "per-scene",
                "seed": 0,
                "niterations": 1,
                "scene_sample_counts": {"scene": 3},
                "diagnostic_metrics": {"r2": 1.0},
            },
            "pysr": {"version": "test"},
            "data": {"dataset_hash": hash_file(data / "controlled.npz")},
        },
    }
    formula_path = results / "formula.json"
    _write_json(formula_path, formula)
    validation = {
        "schema_version": "1.0",
        "artifact_id": "validation-1",
        "formula_hash": hash_file(formula_path),
        "metric_version": "legacy_dtw_v1_abs_n_unclipped",
        "scene": "scene",
        "mode": "pysr",
        "n_steps": 3,
        "metrics": {"full": {}, "tail": {}, "per_channel": {}, "action_range": {}},
        "telemetry": {"path": "telemetry.npz", "telemetry_hash": hash_file(telemetry)},
        "provenance": {"git_sha": "abc", "environment": "pycuda_3_10 GPU2", "command": "validate", "created_at": "now"},
    }
    validation["record_hash"] = hash_json(validation)
    _write_json(results / "validation.json", validation)


def test_hash_mismatch_is_reported(tmp_path):
    root = tmp_path / "SR_analysis"
    _fixture(root, bad_expression_hash=True)

    report, _ = audit_run("result-one", "data-one", sr_root=root)

    assert report["status"] == "fail"
    assert any(error["code"] == "expression_hash_mismatch" for error in report["errors"])
    assert json.loads((root / "results/runs/result-one/audit_report.json").read_text())["strict_failure"] is True


def test_strict_cli_returns_nonzero_for_broken_chain(tmp_path, monkeypatch):
    root = tmp_path / "SR_analysis"
    _fixture(root, bad_expression_hash=True)
    monkeypatch.setattr(audit_round1, "SR_ROOT", root)

    assert audit_round1.main(["--run-id", "result-one", "--data-run-id", "data-one", "--strict"]) == 1
    assert audit_round1.main(["--run-id", "result-one", "--data-run-id", "data-one"]) == 0


def test_failed_validation_is_registered_as_rejected(tmp_path):
    root = tmp_path / "SR_analysis"
    _fixture(root)
    validation_path = root / "results/runs/result-one/validation.json"
    validation = json.loads(validation_path.read_text())
    validation["status"] = "failed"
    validation["record_hash"] = hash_json({k: v for k, v in validation.items() if k != "record_hash"})
    _write_json(validation_path, validation)

    report, registry = audit_run("result-one", "data-one", sr_root=root)

    assert report["status"] == "pass"
    statuses = {item["artifact_id"]: item["status"] for item in registry["artifacts"]}
    assert statuses["formula-1"] == "rejected"
    assert statuses["validation-1"] == "rejected"
    assert registry["status"] == "pass"
    assert registry["promotion_eligible"] is False
    assert registry["scientifically_eligible"] is False


def test_incomplete_validation_rejects_entire_chain(tmp_path):
    root = tmp_path / "SR_analysis"
    _fixture(root)
    validation_path = root / "results/runs/result-one/validation.json"
    validation = json.loads(validation_path.read_text())
    validation["status"] = "incomplete"
    validation["record_hash"] = hash_json({k: v for k, v in validation.items() if k != "record_hash"})
    _write_json(validation_path, validation)

    report, registry = audit_run("result-one", "data-one", sr_root=root)

    assert report["status"] == "pass"
    assert registry["scientifically_eligible"] is False
    assert all(item["status"] == "rejected" for item in registry["artifacts"])


def test_run_prefix_does_not_grant_scientific_promotion(tmp_path):
    root = tmp_path / "SR_analysis"
    _fixture(root)
    source = root / "results/runs/result-one"
    prefixed = root / "results/runs/round1-legacy-v2-20260716-extra"
    source.rename(prefixed)

    report, registry = audit_run(
        "round1-legacy-v2-20260716-extra", "data-one", sr_root=root,
    )

    assert report["status"] == "pass"
    assert registry["status"] == "pass"
    assert registry["promotion_eligible"] is False
    assert registry["scientifically_eligible"] is True


def test_split_formula_and_validation_runs_are_audited_as_one_chain(tmp_path):
    root = tmp_path / "SR_analysis"
    _fixture(root)
    source = root / "results/runs/result-one/formula.json"
    formula_run = root / "results/runs/formula-one"
    formula_run.mkdir()
    source.rename(formula_run / source.name)

    report, registry = audit_run(
        "result-one", "data-one", formula_run_id="formula-one", sr_root=root,
    )

    assert report["status"] == "pass"
    assert report["formula_run_id"] == "formula-one"
    assert {item["artifact_id"] for item in registry["artifacts"]} == {"formula-1", "validation-1"}


def test_candidate_registry_is_generated_without_canonical_mutation(tmp_path):
    root = tmp_path / "SR_analysis"
    root.mkdir()
    canonical = root / "scene_registry.json"
    canonical.write_text('{"frozen": true}\n', encoding="utf-8")
    before = canonical.read_bytes()
    _fixture(root)

    report, registry = audit_run("result-one", "data-one", sr_root=root)

    assert report["status"] == "pass"
    assert canonical.read_bytes() == before
    assert {item["artifact_id"] for item in registry["artifacts"]} == {"formula-1", "validation-1"}
    assert all(set(item) == {"artifact_id", "path", "sha256", "run_id", "status"} for item in registry["artifacts"])
    assert all(item["status"] == "candidate" for item in registry["artifacts"])
    assert registry["status"] == "pass"
    assert registry["promotion_eligible"] is False
    assert registry["scientifically_eligible"] is True
    written = json.loads((root / "results/runs/result-one/candidate_registry.json").read_text())
    assert written == registry
