"""CPU-testable contracts for the experimental Jetson support path."""

from __future__ import annotations

import importlib.metadata
import json
import sys
from pathlib import Path
from types import SimpleNamespace

import pytest
import yaml

try:
    import tomllib
except ModuleNotFoundError:  # pragma: no cover - exercised by the Python 3.10 CI lane
    import tomli as tomllib

from scripts import jetson_support
from scripts import run_conditional_gate
from scripts import setup_jetson


ROOT = Path(__file__).parents[1]


def test_host_evidence_is_allow_listed(monkeypatch, tmp_path):
    tegra = tmp_path / "nv_tegra_release"
    tegra.write_text("# R36 (release), REVISION: 4.3\nserial=secret\n")
    os_release = tmp_path / "os-release"
    os_release.write_text(
        'ID=ubuntu\nVERSION_ID="22.04"\nPRETTY_NAME="Ubuntu 22.04"\nSECRET=value\n'
    )
    monkeypatch.setattr(jetson_support.platform, "machine", lambda: "aarch64")
    monkeypatch.setattr(jetson_support.platform, "python_version", lambda: "3.10.12")
    monkeypatch.setattr(jetson_support, "_capture", lambda command, **kwargs: "6.2+b17")

    facts = jetson_support.collect_host_facts(
        tegra_release=tegra,
        os_release=os_release,
    )

    assert facts == {
        "architecture": "aarch64",
        "python_version": "3.10.12",
        "os": {
            "id": "ubuntu",
            "version_id": "22.04",
            "pretty_name": "Ubuntu 22.04",
        },
        "l4t_release": "# R36 (release), REVISION: 4.3",
        "jetpack_package": "6.2+b17",
    }
    assert "secret" not in json.dumps(facts).lower()


def test_runtime_evidence_reports_cuda_without_device_identity(monkeypatch):
    properties = SimpleNamespace(name="Orin", total_memory=64 * 1024**3)
    fake_cuda = SimpleNamespace(
        is_available=lambda: True,
        device_count=lambda: 1,
        get_device_properties=lambda _index: properties,
        get_device_capability=lambda _index: (8, 7),
    )
    fake_torch = SimpleNamespace(
        __version__="2.8.0a0+nv25.06",
        version=SimpleNamespace(cuda="12.6"),
        cuda=fake_cuda,
    )
    monkeypatch.setitem(sys.modules, "torch", fake_torch)

    def missing_package(_name):
        raise importlib.metadata.PackageNotFoundError

    monkeypatch.setattr(jetson_support.importlib.metadata, "version", missing_package)

    facts = jetson_support.collect_runtime_facts()

    assert facts["cuda_available"] is True
    assert facts["device_name"] == "Orin"
    assert facts["compute_capability"] == [8, 7]
    assert facts["total_memory_gb"] == 64.0
    assert set(facts) == {
        "torch_imported",
        "torch_version",
        "torch_cuda_version",
        "cuda_available",
        "cuda_device_count",
        "device_name",
        "compute_capability",
        "total_memory_gb",
        "bitsandbytes_version",
    }


def test_report_validation_blocks_non_jetson_or_non_cuda_and_warns_on_bnb():
    report = {
        "host": {"architecture": "x86_64", "l4t_release": None},
        "runtime": {
            "torch_imported": True,
            "torch_cuda_version": None,
            "cuda_available": False,
            "bitsandbytes_version": "0.47.0",
        },
    }

    errors, warnings = jetson_support.validate_report(report)

    assert len(errors) == 3
    assert any("ARM64" in error for error in errors)
    assert any("Jetson L4T" in error for error in errors)
    assert any("not a CUDA build" in error for error in errors)
    assert warnings and "unsupported" in warnings[0]


def test_gate_evidence_and_issue_body_cannot_copy_arbitrary_fields(tmp_path):
    evidence = tmp_path / "gate.json"
    evidence.write_text(json.dumps({
        "gate": "jetson-runtime",
        "status": "passed",
        "git_sha": "a" * 40,
        "counts": {"tests": 1, "token": "nested-secret"},
        "token": "must-not-escape",
        "hostname": "must-not-escape",
    }))

    summary = jetson_support._gate_summary(evidence)
    body = jetson_support.issue_body({"gate_evidence": summary})

    assert summary == {
        "gate": "jetson-runtime",
        "status": "passed",
        "git_sha": "a" * 40,
        "counts": {"tests": 1},
    }
    assert "must-not-escape" not in body
    assert "nested-secret" not in body
    assert "excludes environment variables" in body


def test_dependency_export_rejects_torch_and_bitsandbytes(tmp_path):
    requirements = tmp_path / "requirements.txt"
    requirements.write_text("transformers==4.56.0\npytest==8.4.1\n")
    setup_jetson._require_exclusions(requirements)

    for forbidden in (
        "torch==2.8.0",
        "torch @ https://example.invalid/torch.whl",
        "bitsandbytes[diagnostics]==0.47.0",
    ):
        requirements.write_text(forbidden + "\n")
        with pytest.raises(RuntimeError, match="forbidden packages"):
            setup_jetson._require_exclusions(requirements)


def test_jetson_bootstrap_preserves_vendor_runtime_and_uses_locked_no_deps(
    monkeypatch,
    tmp_path,
):
    project = tmp_path / "project"
    (project / "scripts").mkdir(parents=True)
    (project / "scripts" / "jetson_support.py").write_text("# fixture\n")
    (project / "uv.lock").write_text("# fixture\n")
    venv = tmp_path / "jetson-venv"
    commands: list[list[str]] = []

    def fake_run(command, *, cwd):
        command = list(command)
        commands.append(command)
        if command[1:4] == ["-m", "venv", "--system-site-packages"]:
            (venv / "bin").mkdir(parents=True)
            (venv / "pyvenv.cfg").write_text("include-system-site-packages = true\n")
        if "--output-file" in command:
            output = Path(command[command.index("--output-file") + 1])
            output.write_text("transformers==4.56.0\n")

    monkeypatch.setattr(setup_jetson, "_run", fake_run)

    setup_jetson.prepare(
        project=project,
        venv=venv,
        python="vendor-python",
        uv_python="tool-python",
        reuse=False,
    )

    assert commands[0][0] == "vendor-python"
    assert "--check" in commands[0]
    export = next(command for command in commands if "export" in command)
    assert export.count("--no-emit-package") == 2
    assert "torch" in export and "bitsandbytes" in export
    installs = [command for command in commands if "install" in command]
    assert len(installs) == 2
    assert all("--no-deps" in command for command in installs)
    assert any("check" in command for command in commands)


def test_bootstrap_rejects_unsafe_or_non_vendor_reusable_targets(tmp_path):
    project = tmp_path / "project"
    project.mkdir()
    with pytest.raises(ValueError, match="unsafe"):
        setup_jetson._require_safe_target(project, project)

    existing = tmp_path / "existing"
    existing.mkdir()
    (existing / "pyvenv.cfg").write_text("include-system-site-packages = false\n")
    with pytest.raises(ValueError, match="does not expose JetPack"):
        setup_jetson._require_new_or_reusable_venv(existing, reuse=True)


def test_bitsandbytes_is_opt_in_for_quantization_only():
    metadata = tomllib.loads((ROOT / "pyproject.toml").read_text())
    base = metadata["project"]["dependencies"]
    extras = metadata["project"]["optional-dependencies"]

    assert not any(value.startswith("bitsandbytes") for value in base)
    assert extras["quantization"] == ["bitsandbytes>=0.46.1"]


def test_jetson_issue_form_requires_reproducible_sanitized_hardware_evidence():
    form = yaml.safe_load(
        (ROOT / ".github" / "ISSUE_TEMPLATE" / "jetson-runtime.yml").read_text(),
    )
    fields = {value.get("id"): value for value in form["body"] if value.get("id")}

    assert set(fields) == {
        "device",
        "jetpack",
        "commit",
        "reproduction",
        "expected",
        "actual",
        "evidence",
        "confirmations",
    }
    assert all(value.get("validations", {}).get("required") for value in fields.values())
    confirmations = fields["confirmations"]["attributes"]["options"]
    assert all(option["required"] for option in confirmations)
    assert any("secrets" in option["label"] for option in confirmations)


def test_jetson_conditional_prerequisites_are_physical_and_cuda(monkeypatch, tmp_path):
    fake_torch = SimpleNamespace(cuda=SimpleNamespace(is_available=lambda: True))
    monkeypatch.setitem(sys.modules, "torch", fake_torch)
    monkeypatch.setattr(run_conditional_gate.platform, "machine", lambda: "aarch64")
    tegra = tmp_path / "nv_tegra_release"
    tegra.write_text("# R36\n")
    monkeypatch.setattr(run_conditional_gate, "JETSON_RELEASE", tegra)
    assert run_conditional_gate.missing_prerequisites("jetson-runtime") == []

    monkeypatch.setattr(run_conditional_gate.platform, "machine", lambda: "x86_64")
    fake_torch.cuda.is_available = lambda: False
    tegra.unlink()
    assert run_conditional_gate.missing_prerequisites("jetson-runtime") == [
        "a CUDA-capable PyTorch runtime",
        "an ARM64 host",
        "a Jetson L4T runtime",
    ]
