#!/usr/bin/env python3
"""Positive and negative release-evidence schema and invariant tests."""

from __future__ import annotations

from copy import deepcopy
from pathlib import Path
import json
import subprocess
import sys
import tempfile
import re
import unittest

ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "release" / "evidence-v1.schema.json"
MANIFEST = ROOT / "adapter" / "omp-spt.toml"
VALIDATOR = ROOT / "ci" / "release" / "validate-release-evidence.py"
CANONICAL_VERSION = re.search(r'(?m)^version = "([^"]+)"$', MANIFEST.read_text(encoding="utf-8")).group(1)
MINIMUM_OMP_VERSION = "16.3.15"
MINIMUM_SPT_CORE_VERSION = re.search(
    r'(?m)^min_spt_core_version = "([^"]+)"$', MANIFEST.read_text(encoding="utf-8")
).group(1)
TARGETS = (
    "x86_64-pc-windows-msvc",
    "x86_64-unknown-linux-gnu",
    "x86_64-unknown-linux-musl",
)


def record(target: str = "x86_64-unknown-linux-musl") -> dict:
    evidence = "native-acceptance.log#sha256=" + "e" * 64
    outcome = {"passed": True, "evidence": evidence}
    return {
        "schema_version": 1,
        "release": {
            "version": CANONICAL_VERSION,
            "tag": f"v{CANONICAL_VERSION}",
            "source_commit": "a" * 40,
            "source_tree": "b" * 40,
        },
        "target": target,
        "environment": {
            "omp_version": MINIMUM_OMP_VERSION,
            "spt_core_version": MINIMUM_SPT_CORE_VERSION,
        },
        "artifacts": [
            {"name": "adapter.spt", "sha256": "c" * 64},
            {
                "name": f"{target}/{'omp-spt.exe' if 'windows' in target else 'omp-spt'}",
                "sha256": "d" * 64,
            },
            {"name": "native-acceptance.log", "sha256": "e" * 64},
        ],
        "acquisition": deepcopy(outcome),
        "native_acceptance": {
            "fresh": {
                "passed": True,
                "endpoint_id": "omp-spt-accept-fresh",
                "session_id": "session-fresh",
                "evidence": evidence,
            },
            "resume": {
                "passed": True,
                "endpoint_id": "omp-spt-accept-resume",
                "session_id": "session-resume",
                "evidence": evidence,
            },
            "message_reply": deepcopy(outcome),
            "lifecycle": deepcopy(outcome),
            "shutdown": deepcopy(outcome),
            "ready_agent": deepcopy(outcome),
            "live_agent": deepcopy(outcome),
        },
        "tested_at": "2026-07-16T12:00:00Z",
    }


def validate(*records: dict) -> subprocess.CompletedProcess[str]:
    with tempfile.TemporaryDirectory() as work:
        paths = []
        for index, value in enumerate(records):
            path = Path(work) / f"record-{index}.json"
            path.write_text(json.dumps(value), encoding="utf-8", newline="\n")
            paths.append(path)
        return subprocess.run(
            [sys.executable, str(VALIDATOR), str(SCHEMA), str(MANIFEST), *map(str, paths)],
            text=True,
            capture_output=True,
            check=False,
        )


# [unit->REQ-DIST-RELEASE-EVIDENCE]
class ReleaseEvidenceTests(unittest.TestCase):
    def test_complete_supported_target_set_is_accepted_at_version_floors(self) -> None:
        result = validate(*(record(target) for target in TARGETS))
        self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
        self.assertIn("RELEASE-EVIDENCE OK", result.stdout)

    def test_cross_target_adapter_digest_mismatch_is_rejected(self) -> None:
        records = [record(target) for target in TARGETS]
        records[1]["artifacts"][0]["sha256"] = "f" * 64

        result = validate(*records)

        self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
        self.assertIn("adapter.spt digest differs", result.stdout)

    def test_schema_and_cross_record_failures_are_rejected(self) -> None:
        invalid_cases = []

        wrong_version = record()
        wrong_version["release"]["version"] = "9.9.9"
        wrong_version["release"]["tag"] = "v9.9.9"
        invalid_cases.append(("canonical version mismatch", (wrong_version,), "canonical manifest version"))

        missing_target = tuple(record(target) for target in TARGETS if target != "x86_64-unknown-linux-gnu")
        invalid_cases.append(("missing supported target", missing_target, "missing supported target"))

        unsupported_target = tuple(record(target) for target in (*TARGETS, "aarch64-unknown-linux-gnu"))
        invalid_cases.append(("unsupported target", unsupported_target, "unsupported target"))

        wrong_tag = record()
        wrong_tag["release"]["tag"] = "v0.0.0"
        invalid_cases.append(("noncanonical tag", (wrong_tag,), f"must equal v{CANONICAL_VERSION}"))

        failed_outcome = record()
        failed_outcome["native_acceptance"]["shutdown"]["passed"] = False
        invalid_cases.append(("failed outcome", (failed_outcome,), "True was expected"))

        missing_outcome = record()
        del missing_outcome["native_acceptance"]["message_reply"]
        invalid_cases.append(("missing outcome", (missing_outcome,), "required property"))

        bad_digest = record()
        bad_digest["artifacts"][0]["sha256"] = "not-a-digest"
        invalid_cases.append(("bad digest", (bad_digest,), "does not match"))

        unbound_evidence = record()
        unbound_evidence["native_acceptance"]["message_reply"]["evidence"] = "native.log"
        invalid_cases.append(("unbound evidence", (unbound_evidence,), "#sha256"))

        whitespace_omp_version = record()
        whitespace_omp_version["environment"]["omp_version"] = "   "
        invalid_cases.append(
            ("whitespace OMP version", (whitespace_omp_version,), "does not match")
        )

        whitespace_spt_version = record()
        whitespace_spt_version["environment"]["spt_core_version"] = "\t"
        invalid_cases.append(
            ("whitespace spt-core version", (whitespace_spt_version,), "does not match")
        )

        prefixed_omp_version = record()
        prefixed_omp_version["environment"]["omp_version"] = "v16.3.15"
        invalid_cases.append(
            ("prefixed OMP version", (prefixed_omp_version,), "does not match")
        )

        below_omp_floor = record()
        below_omp_floor["environment"]["omp_version"] = "16.3.14"
        invalid_cases.append(
            ("OMP version below floor", (below_omp_floor,), "below supported floor 16.3.15")
        )

        below_spt_floor = record()
        below_spt_floor["environment"]["spt_core_version"] = "0.30.99"
        invalid_cases.append(
            (
                "spt-core version below floor",
                (below_spt_floor,),
                f"below manifest floor {MINIMUM_SPT_CORE_VERSION}",
            )
        )

        for phase in ("fresh", "resume"):
            for field in ("endpoint_id", "session_id"):
                whitespace_id = record()
                whitespace_id["native_acceptance"][phase][field] = " \t "
                invalid_cases.append(
                    (f"whitespace {phase} {field}", (whitespace_id,), "does not match")
                )

        missing_log_artifact = record()
        missing_log_artifact["artifacts"].pop()
        invalid_cases.append(
            (
                "missing referenced log artifact",
                (missing_log_artifact,),
                "is not present in artifacts",
            )
        )

        mismatched_log_digest = record()
        mismatched_log_digest["artifacts"][2]["sha256"] = "f" * 64
        invalid_cases.append(
            (
                "mismatched referenced log digest",
                (mismatched_log_digest,),
                "does not match artifacts inventory",
            )
        )

        url_evidence = record()
        url_evidence["acquisition"]["evidence"] = (
            "https://example.invalid/native-acceptance.log#sha256=" + "e" * 64
        )
        invalid_cases.append(("URL evidence reference", (url_evidence,), "does not match"))

        path_evidence = record()
        path_evidence["acquisition"]["evidence"] = (
            "logs/native-acceptance.log#sha256=" + "e" * 64
        )
        invalid_cases.append(("path evidence reference", (path_evidence,), "does not match"))

        bad_timestamp = record()
        bad_timestamp["tested_at"] = "yesterday"
        invalid_cases.append(("bad timestamp shape", (bad_timestamp,), "does not match"))
        impossible_timestamp = record()
        impossible_timestamp["tested_at"] = "2026-02-30T12:00:00Z"
        invalid_cases.append(
            ("impossible timestamp", (impossible_timestamp,), "valid RFC 3339 date-time")
        )

        duplicate_artifact = record()
        duplicate_artifact["artifacts"][1]["name"] = "adapter.spt"
        invalid_cases.append(("duplicate artifact", (duplicate_artifact,), "must be unique"))

        missing_helper = record()
        missing_helper["artifacts"][1]["name"] = "native-acceptance.log"
        invalid_cases.append(("missing helper", (missing_helper,), "selected helper"))

        missing_adapter = record()
        missing_adapter["artifacts"][0]["name"] = "acceptance.log"
        invalid_cases.append(("missing adapter", (missing_adapter,), "acquired adapter.spt"))

        duplicate_target_a = record()
        duplicate_target_b = record()
        invalid_cases.append(("duplicate target", (duplicate_target_a, duplicate_target_b), "duplicate target"))

        inconsistent_a = record("x86_64-pc-windows-msvc")
        inconsistent_b = record("x86_64-unknown-linux-musl")
        inconsistent_b["release"]["source_commit"] = "f" * 40
        invalid_cases.append(("mixed source identity", (inconsistent_a, inconsistent_b), "source identity differs"))

        for description, records, message in invalid_cases:
            with self.subTest(case=description):
                result = validate(*records)
                self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
                self.assertIn(message, result.stdout)


if __name__ == "__main__":
    unittest.main(verbosity=2)
