from __future__ import annotations

import argparse
import hashlib
import json
import os
import pathlib
import sys


ROOT = pathlib.Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))

from reliability_core.suite_isolation import discover_test_ids, load_policy_manifest, validate_policy_manifest


PREFIX = "tests.test_r10r3r2.R10R3R2Tests."
ALLOWED_NEW_TEST_IDS = {
    "tests.test_r10r2_process.R10R2ProcessRegressionTests.test_r10r2_rg_53_dynamic_discovery_count_comes_from_raw_rows",
}
PROCESS_CLEANUP_ASSERTIONS = [
    "exit_code",
    "timed_out",
    "orphan_process_count",
    "zombie_process_count",
    "output_drain_threads_stopped",
]
POLICY_OVERRIDES = {
    "tests.test_r10r3r2.R10R3R2Tests.test_worker_import_discovery_calibration_uses_fresh_processes": ("ISOLATED_TEST_PROCESS", "isolated_heavy", True),
    "tests.test_r4_rg.R4RegressionTests.test_r4_rg_07": ("ISOLATED_TEST_PROCESS", "isolated_batch", True),
    "tests.test_r4_rg.R4RegressionTests.test_r4_rg_11": ("ISOLATED_TEST_PROCESS", "adaptive_process", True),
    "tests.test_r4_rg.R4RegressionTests.test_r4_rg_14": ("ISOLATED_BATCH_CONTROLLER", "isolated_batch", True),
    "tests.test_r5_rg.R5RegressionTests.test_r5_rg_17": ("ISOLATED_BATCH_CONTROLLER", "isolated_batch", True),
    "tests.test_r6_rg.R6RegressionTests.test_r6_rg_09": ("ISOLATED_BATCH_CONTROLLER", "isolated_heavy", True),
    "tests.test_r7_rg.R7RegressionTests.test_r7_rg_23": ("ISOLATED_TEST_PROCESS", "isolated_batch", True),
    "tests.test_r8_rg.R8RegressionTests.test_r8_rg_25": ("ISOLATED_TEST_PROCESS", "isolated_batch", True),
    "tests.test_r8_rg.R8RegressionTests.test_r8_rg_27": ("ISOLATED_TEST_PROCESS", "isolated_batch", True),
    "tests.test_repetition.RepetitionTests.test_eval_03_twenty_scenarios_three_repetitions": ("ISOLATED_BATCH_CONTROLLER", "isolated_heavy", False),
    "tests.test_rg.RegressionAttackTests.test_rg_08": ("ISOLATED_BATCH_CONTROLLER", "adaptive_process", False),
    "tests.test_rg.RegressionAttackTests.test_rg_09": ("ISOLATED_TEST_PROCESS", "adaptive_process", True),
    "tests.test_rg.RegressionAttackTests.test_rg_16": ("ISOLATED_BATCH_CONTROLLER", "isolated_heavy", True),
    "tests.test_rg.RegressionAttackTests.test_rg_17": ("ISOLATED_TEST_PROCESS", "adaptive_process", True),
}
OBSERVED_TIMEOUT_OVERRIDES = {
    "tests.test_r2_rg.R2RegressionTests.test_r2_rg_01",
    "tests.test_r3_rg.R3RegressionTests.test_r3_rg_04",
    "tests.test_r3_rg.R3RegressionTests.test_r3_rg_01",
    "tests.test_correction_regressions.CorrectionDefectTests.test_d16_platform_check_is_nonblocking",
    "tests.test_correction_regressions.CorrectionDefectTests.test_d15_rollback_uses_canary_patch",
}


def sha256_file(path: pathlib.Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def entry(test_id: str) -> dict[str, object]:
    return {
        "creates_process": True,
        "creates_thread": True,
        "mutates_environment": True,
        "policy": "ISOLATED_TEST_PROCESS",
        "reason": "R10 R3 R2 deadline, checkpoint, canary, or process-cleanup contract is verified in a fresh interpreter",
        "required_cleanup_assertions": [
            "exit_code",
            "timed_out",
            "orphan_process_count",
            "zombie_process_count",
            "output_drain_threads_stopped",
        ],
        "requirement_ids": ["R10R3R2"],
        "test_id": test_id,
        "timeout_profile": "adaptive_process",
        "uses_global_cache": True,
        "uses_signal": False,
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--apply", action="store_true")
    args = parser.parse_args()
    path = ROOT / "test_execution_manifest.json"
    before = sha256_file(path)
    payload = load_policy_manifest(path)
    discovered = discover_test_ids(ROOT)
    current = {str(row["test_id"]): row for row in payload["tests"]}
    missing = sorted(set(discovered) - set(current))
    unknown = sorted(set(current) - set(discovered))
    unexpected_missing = [
        test_id
        for test_id in missing
        if not test_id.startswith(PREFIX) and test_id not in ALLOWED_NEW_TEST_IDS
    ]
    if unknown or unexpected_missing:
        raise SystemExit(json.dumps({"unknown": unknown, "unexpected_missing": unexpected_missing}, sort_keys=True))
    updated = {**current, **{test_id: entry(test_id) for test_id in missing}}
    for test_id, (policy, timeout_profile, creates_process) in POLICY_OVERRIDES.items():
        if test_id not in updated:
            raise SystemExit(f"policy override target is not discovered: {test_id}")
        updated[test_id] = {
            **updated[test_id],
            "creates_process": creates_process,
            "creates_thread": creates_process,
            "policy": policy,
            "reason": "fresh Stage 5 runtime observations require isolated execution and a measured timeout profile",
            "required_cleanup_assertions": PROCESS_CLEANUP_ASSERTIONS,
            "timeout_profile": timeout_profile,
            "uses_signal": creates_process,
        }
    for test_id in OBSERVED_TIMEOUT_OVERRIDES:
        if test_id not in updated:
            raise SystemExit(f"observed timeout override target is not discovered: {test_id}")
        updated[test_id] = {
            **updated[test_id],
            "reason": "fresh Linux random-order worker measurements require an observed adaptive execution profile",
            "timeout_profile": "adaptive_process",
        }
    output = {
        **payload,
        "discovered_test_count": len(discovered),
        "generated_from_discovery": True,
        "tests": [updated[test_id] for test_id in sorted(updated)],
        "version": "R10_R3_R2",
    }
    if args.apply:
        temporary = path.with_suffix(path.suffix + ".tmp")
        temporary.write_text(
            json.dumps(output, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
            encoding="utf-8",
            newline="\n",
        )
        os.replace(temporary, path)
    validation = validate_policy_manifest(output, discovered)
    print(json.dumps({
        "applied": args.apply,
        "before_sha256": before,
        "after_sha256": sha256_file(path) if args.apply else hashlib.sha256((json.dumps(output, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode("utf-8")).hexdigest(),
        "added_count": len(missing),
        "discovered_count": len(discovered),
        "validation": validation,
    }, ensure_ascii=False, sort_keys=True))
    return 0 if validation["passed"] else 1


if __name__ == "__main__":
    raise SystemExit(main())
