from __future__ import annotations

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

import pytest

HERE = Path(__file__).parent
CONTROLLER = HERE / "reset_controller.py"
CONTRACT = HERE / "schema-contract.json"
FIXTURE = HERE / "fixtures/current-empty-profile"
CANDIDATE = "2e0894eac92bc396cc4723bf1f18ebc653b95018dd41574df435941c235da925"
WHEEL = "af4a9d0a1ffffb6eb7551c1d6dc2b32853ca6d024332a4f8f5702bbf992f141b"
PLAN = "7ace03c6dad33d2fc3ef223621cbca68a150fde8429932138e252fb8498ac582"
APPROVAL = "TASK26_ARCHIVE_FIRST_PROFILE_RESET_APPROVED"


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


def profile(tmp_path: Path) -> tuple[Path, Path]:
    target = tmp_path / "target"
    other = tmp_path / "other"
    shutil.copytree(FIXTURE, target)
    other.mkdir(mode=0o700)
    marker = other / "marker"
    marker.write_bytes(b"other-profile")
    marker.chmod(0o600)
    archives = target / "data/profile-reset-archives"
    prior = archives / "prior"
    prior.mkdir(parents=True, mode=0o700)
    evidence = prior / "evidence.json"
    evidence.write_bytes(b"{}\n")
    evidence.chmod(0o600)
    return target, other


def receipt(tmp_path: Path) -> Path:
    value = {
        "schema": "task26-profile-reset-permission-v1",
        "approval": APPROVAL,
        "candidate_digest": CANDIDATE,
        "wheel_sha256": WHEEL,
        "plan_sha256": PLAN,
        "controller_sha256": sha(CONTROLLER),
        "contract_sha256": sha(CONTRACT),
        "execute_allowed": True,
    }
    path = tmp_path / "permission.json"
    path.write_text(json.dumps(value, sort_keys=True) + "\n")
    path.chmod(0o600)
    return path


def run(target: Path, other: Path, permission: Path, mode: str, *extra: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [sys.executable, str(CONTROLLER), mode, "--profile", str(target), "--other-profile", str(other),
         "--archive-root", str(target / "data/profile-reset-archives"), "--contract", str(CONTRACT),
         "--permission", str(permission), "--candidate", CANDIDATE, "--wheel-sha256", WHEEL,
         "--plan-sha256", PLAN, "--approval", APPROVAL, "--run-id", "fixture-run", *extra],
        text=True, capture_output=True, check=False,
    )


def test_execute_archives_all_authority_and_reaches_empty_baseline(tmp_path: Path) -> None:
    target, other = profile(tmp_path)
    permission = receipt(tmp_path)
    bootstrap = target / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json"
    bootstrap_raw = bootstrap.read_bytes()
    other_before = sha(other / "marker")
    result = run(target, other, permission, "execute")
    assert result.returncode == 0, result.stderr
    archive = target / "data/profile-reset-archives/fixture-run"
    manifest = json.loads((archive / "manifest.json").read_text())
    assert manifest["bootstrap_session_count"] == 3
    assert manifest["prior_archives"][0]["path"] == "data/profile-reset-archives/prior"
    assert (archive / "payload/data/onboarding/telegram-customer-bootstrap-v1/ledger.json").read_bytes() == bootstrap_raw
    assert not bootstrap.exists()
    assert sha(other / "marker") == other_before
    verified = run(target, other, permission, "verify", "--archive", str(archive))
    assert verified.returncode == 0, verified.stderr


def test_dry_run_is_byte_non_mutating_and_deterministic(tmp_path: Path) -> None:
    target, other = profile(tmp_path)
    permission = receipt(tmp_path)
    before = sha(target / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json")
    one = run(target, other, permission, "dry-run")
    two = run(target, other, permission, "dry-run")
    assert one.returncode == two.returncode == 0
    assert one.stdout == two.stdout
    assert sha(target / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json") == before
    assert not (target / "data/profile-reset-archives/fixture-run").exists()


@pytest.mark.parametrize("attack", ["symlink", "hardlink", "mode", "unknown", "schema", "pin"])
def test_fail_closed_without_mutation(tmp_path: Path, attack: str) -> None:
    target, other = profile(tmp_path)
    permission = receipt(tmp_path)
    ledger = target / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json"
    before = ledger.read_bytes()
    if attack == "symlink":
        ledger.unlink(); ledger.symlink_to(target / "customers/registry.json")
    elif attack == "hardlink":
        os.link(ledger, target / "data/onboarding/telegram-customer-bootstrap-v1/alias.json")
    elif attack == "mode":
        ledger.chmod(0o644)
    elif attack == "unknown":
        p = target / "data/unknown-authority"; p.write_bytes(b"x"); p.chmod(0o600)
    elif attack == "schema":
        ledger.write_text("{}\n"); ledger.chmod(0o600)
    elif attack == "pin":
        value = json.loads(permission.read_text()); value["wheel_sha256"] = "0" * 64
        permission.write_text(json.dumps(value)); permission.chmod(0o600)
    result = run(target, other, permission, "execute")
    assert result.returncode != 0
    assert not (target / "data/profile-reset-archives/fixture-run").exists()
    if attack not in {"symlink", "schema"}:
        assert ledger.read_bytes() == before


def test_injected_precommit_mismatch_rolls_back(tmp_path: Path) -> None:
    target, other = profile(tmp_path)
    permission = receipt(tmp_path)
    before = (target / "customers/registry.json").read_bytes()
    result = run(target, other, permission, "execute", "--test-fail-before-commit")
    assert result.returncode != 0
    assert (target / "customers/registry.json").read_bytes() == before
    assert not (target / "data/profile-reset-archives/fixture-run").exists()
