"""Immutable closure regressions for the V15 live launcher."""

from __future__ import annotations

import hashlib
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Final

import pytest

from scripts.execute_nutricoach_v150_sealed_live import sandbox_command
from scripts.nutricoach_v150_detached_bootstrap import (
    PRESEAL,
    BootstrapDenied,
    verify_package_inventory,
)
from scripts.seal_nutricoach_v150_candidate import (
    candidate_source_paths,
    main as seal_candidate,
)
from scripts.verify_nutricoach_v140_candidate_core import (
    CandidateContractError,
    load_json,
    require_object,
)
from scripts.verify_nutricoach_v150_candidate_inputs import (
    SUCCESSOR_OVERLAY_PATHS,
    verify_v17_overlay,
)

CANDIDATE_ROOT_ENV: Final = "NUTRICOACH_V150_CANDIDATE_ROOT"
DEFAULT_CANDIDATE_ROOT: Final = (
    Path("/home/cube/projects/richard/traning coach")
    / ".omo/evidence/nutricoach-v150-combined/task-v15r71-candidate-r3"
)
EVIDENCE = Path(os.environ.get(CANDIDATE_ROOT_ENV, str(DEFAULT_CANDIDATE_ROOT)))
HERMES_WHEEL = EVIDENCE / "artifacts/build-1/hermes_agent-0.17.0-py3-none-any.whl"
PROFILE_WHEEL = (
    EVIDENCE / "artifacts/build-1/physique_checkin_cli-0.1.0-py3-none-any.whl"
)


def _detached_pythonpath(source: Path) -> str:
    return os.pathsep.join((str(HERMES_WHEEL), str(PROFILE_WHEEL), str(source)))


_STEPPER_OVERLAY_PATHS = {
    "dualcoach/profile/checkin_cli/wizard.py",
    "dualcoach/profile/checkin_cli/wizard_models.py",
    "gateway/platforms/physique_checkin.py",
    "gateway/platforms/physique_checkin_bindings.py",
    "gateway/platforms/telegram.py",
    "gateway/platforms/telegram_checkin_stepper_transport.py",
    "gateway/platforms/telegram_physique_checkin_stepper.py",
    "tests/gateway/test_nutrition_coaching.py",
}


def test_candidate_root_defaults_to_r3_and_accepts_fixture_injection() -> None:
    injected = os.environ.get(CANDIDATE_ROOT_ENV)
    expected = DEFAULT_CANDIDATE_ROOT if injected is None else Path(injected)
    assert DEFAULT_CANDIDATE_ROOT.name == "task-v15r71-candidate-r3"
    assert EVIDENCE == expected


def test_fresh_launcher_targets_r71_closure() -> None:
    assert PRESEAL.name == "live-transaction-preseal-v15-runtime-authority-r71b-maintenance"
    assert any(
        "live-transaction-preseal-v15-runtime-authority-r71b-maintenance" in argument
        and argument.endswith("nutricoach_v150_detached_bootstrap.py")
        for argument in sandbox_command("approval")
    )


def test_successor_overlay_contains_every_stepper_wheel_source() -> None:
    assert _STEPPER_OVERLAY_PATHS <= SUCCESSOR_OVERLAY_PATHS


def test_successor_overlay_contains_every_maintenance_runtime_import() -> None:
    assert {
        "gateway/platforms/nutrition_weekly_dispatch_result.py",
        "gateway/platforms/nutrition_weekly_maintenance_contract.py",
        "gateway/platforms/nutrition_weekly_maintenance_io.py",
        "gateway/platforms/nutrition_weekly_maintenance_store.py",
        "gateway/platforms/nutrition_weekly_operations.py",
        "gateway/platforms/nutrition_weekly_operations_publication_contract.py",
        "gateway/platforms/telegram_weekly_host_dispatch.py",
    } <= SUCCESSOR_OVERLAY_PATHS


@pytest.mark.parametrize("omitted", sorted(_STEPPER_OVERLAY_PATHS))
def test_successor_overlay_rejects_each_omitted_stepper_source(
    tmp_path: Path,
    omitted: str,
) -> None:
    inputs = tmp_path / "inputs"
    inputs.mkdir()
    digest = "0" * 64
    overlay_paths = SUCCESSOR_OVERLAY_PATHS | _STEPPER_OVERLAY_PATHS
    overlay = overlay_paths - {omitted}
    _ = (inputs / "successor-overlay.sha256").write_text(
        "".join(f"{digest}  {path}\n" for path in sorted(overlay)),
        encoding="utf-8",
    )
    exact = inputs / "v17-exact-source-set.sha256"
    patch = inputs / "v17-patch-tree.sha256"
    sandbox = inputs / "v17-sandbox.diff"
    for path in (exact, patch, sandbox):
        _ = path.write_bytes(b"")
    _ = (inputs / "v17-evidence-files.sha256").write_text(
        "".join(
            f"{hashlib.sha256(path.read_bytes()).hexdigest()}  /sealed/{name}\n"
            for path, name in (
                (exact, "exact-current-source-set.final.sha256"),
                (patch, "patch-tree.sha256"),
                (sandbox, "sandbox.diff"),
            )
        ),
        encoding="utf-8",
    )
    source_entries = [
        {"path": f"snapshot/source/{path}", "sha256": digest}
        for path in sorted(overlay_paths)
    ]

    with pytest.raises(CandidateContractError, match="successor overlay path set"):
        verify_v17_overlay(tmp_path, source_entries)


def test_sealer_rejects_occupied_successor_without_mutation(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    successor = tmp_path / "occupied"
    successor.mkdir(mode=0o750)
    sentinel = successor / "sentinel"
    _ = sentinel.write_bytes(b"do not mutate\n")
    sentinel.chmod(0o640)
    before = (successor.stat().st_mode, sentinel.stat().st_mode, sentinel.read_bytes())
    monkeypatch.setattr(
        sys,
        "argv",
        [
            "sealer",
            "--base",
            str(tmp_path / "base.json"),
            "--v17-evidence",
            str(tmp_path / "v17"),
            "--successor",
            str(successor),
            "--build-1-hermes",
            str(tmp_path / "h1.whl"),
            "--build-1-profile",
            str(tmp_path / "p1.whl"),
            "--build-2-hermes",
            str(tmp_path / "h2.whl"),
            "--build-2-profile",
            str(tmp_path / "p2.whl"),
            "--receipts-root",
            str(tmp_path / "receipts"),
            "--successor-overlay",
            str(tmp_path / "overlay.sha256"),
            "--r70-error-evidence",
            str(tmp_path / "task-10-cron-no-send-recovery-analysis.json"),
            "--observer-evidence",
            str(tmp_path / "task-10-debug-observer-r70.json"),
            "--fixed-collector-evidence",
            str(tmp_path / "task-10-r70-health-recovery.json"),
        ],
    )

    with pytest.raises(RuntimeError, match="successor_exists"):
        _ = seal_candidate()

    assert (successor.stat().st_mode, sentinel.stat().st_mode, sentinel.read_bytes()) == before


def test_live_launcher_writable_binds_external_authority_paths() -> None:
    command = sandbox_command("approval")
    roots = (
        "/home/cube/.hermes/runtime-authority/dualcoach-v1.3.0-owner-risk-first-customer",
        "/home/cube/.hermes/runtime-authority/dualcoach-v1.3.2-daily-checkin-rebound-first-customer-credentials",
    )
    for root in roots:
        index = command.index(root)
        assert command[index - 1] == "--bind"
        assert command[index + 1] == root


def test_package_inventory_rejects_unlisted_file(tmp_path: Path) -> None:
    payload = tmp_path / "payload.json"
    _ = payload.write_text("payload\n", encoding="utf-8")
    digest = hashlib.sha256(payload.read_bytes()).hexdigest()
    manifest = tmp_path / "package-manifest.json"
    _ = manifest.write_text(
        json.dumps({"entries": {"payload.json": digest}}),
        encoding="utf-8",
    )
    payload.chmod(0o444)
    verify_package_inventory(manifest, tmp_path)
    _ = (tmp_path / "unlisted.json").write_text("extra\n", encoding="utf-8")

    with pytest.raises(BootstrapDenied, match="package_inventory"):
        verify_package_inventory(manifest, tmp_path)


def test_candidate_closure_includes_verifier_imports() -> None:
    base = require_object(
        load_json(EVIDENCE / "inputs/base-manifest.json"),
        "base manifest",
    )
    sources = candidate_source_paths(base, EVIDENCE)
    assert Path("scripts/nutricoach_v150_live_upgrade_common.py") in sources
    assert Path("scripts/nutricoach_v150_live_authority.py") in sources
    assert Path("scripts/nutricoach_v150_live_transaction.py") in sources
    assert Path("tests/nutricoach_v150_transaction_support.py") in sources


def test_verifier_imports_from_detached_closure(tmp_path: Path) -> None:
    source_root = Path(__file__).resolve().parents[1]
    base = require_object(
        load_json(EVIDENCE / "inputs/base-manifest.json"),
        "base manifest",
    )
    for relative in candidate_source_paths(base, EVIDENCE):
        destination = tmp_path / relative
        destination.parent.mkdir(parents=True, exist_ok=True)
        _ = shutil.copy2(source_root / relative, destination)
    verifier = tmp_path / "scripts/verify_nutricoach_v150_preseal_v15.py"
    environment = dict(os.environ)
    environment["PYTHONDONTWRITEBYTECODE"] = "1"
    environment["PYTHONPATH"] = _detached_pythonpath(tmp_path)
    result = subprocess.run(
        (sys.executable, "-B", str(verifier)),
        check=False,
        capture_output=True,
        text=True,
        env=environment,
        cwd=tmp_path,
    )
    assert result.returncode in {0, 2}
    assert not result.stderr
    assert result.stdout.startswith(("DENIED:", "{"))


def test_worker_imports_from_detached_closure(tmp_path: Path) -> None:
    source_root = Path(__file__).resolve().parents[1]
    base = require_object(
        load_json(EVIDENCE / "inputs/base-manifest.json"),
        "base manifest",
    )
    for relative in candidate_source_paths(base, EVIDENCE):
        destination = tmp_path / relative
        destination.parent.mkdir(parents=True, exist_ok=True)
        _ = shutil.copy2(source_root / relative, destination)
    environment = dict(os.environ)
    environment["PYTHONDONTWRITEBYTECODE"] = "1"
    environment["PYTHONPATH"] = _detached_pythonpath(tmp_path)
    result = subprocess.run(
        (
            sys.executable,
            "-B",
            "-c",
            "import scripts.nutricoach_v150_controller_worker",
        ),
        check=False,
        capture_output=True,
        text=True,
        env=environment,
        cwd=tmp_path,
    )
    assert result.returncode == 0, result.stderr
