from __future__ import annotations

import hashlib
import importlib.util
import json
from pathlib import Path
from types import ModuleType

import pytest

REPOSITORY_ROOT = Path(__file__).parents[1]
INTEGRITY_PATH = (
    REPOSITORY_ROOT / "plugins" / "xgrids-k1" / "lab" / "iphone-capture" / "session_integrity.py"
)


def _load_module() -> ModuleType:
    spec = importlib.util.spec_from_file_location(
        "iphone_capture_session_integrity", INTEGRITY_PATH
    )
    assert spec is not None
    assert spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


INTEGRITY = _load_module()


def _sha256(value: bytes) -> str:
    return hashlib.sha256(value).hexdigest()


def _session(tmp_path: Path) -> tuple[Path, Path]:
    session = tmp_path / "20260716T000000Z_test_abcd"
    capture_dir = session / "captures"
    capture_dir.mkdir(parents=True)
    capture = capture_dir / "iphone-network.pcap"
    capture_bytes = b"synthetic-private-capture"
    capture.write_bytes(capture_bytes)
    manifest = {
        "session_id": session.name,
        "status": "completed",
        "artifacts": [
            {
                "kind": "iphone-network-pcap-compatible",
                "relative_path": "captures/iphone-network.pcap",
                "size_bytes": len(capture_bytes),
                "sha256": _sha256(capture_bytes),
            }
        ],
    }
    (session / "manifest.redacted.json").write_text(json.dumps(manifest), encoding="utf-8")
    (session / "capture.stderr.log").write_bytes(b"")
    events = session / "operator-events.private.jsonl"
    events.write_text('{"sequence":1}\n', encoding="utf-8")
    return session, capture


def test_seal_session_preserves_manifest_and_hashes_late_operator_events(tmp_path: Path) -> None:
    session, _ = _session(tmp_path)
    manifest = session / "manifest.redacted.json"
    before = manifest.read_bytes()

    destination = INTEGRITY.seal_session(session)
    inventory = json.loads(destination.read_text(encoding="utf-8"))

    assert manifest.read_bytes() == before
    assert inventory["capture_manifest_preserved"] is True
    assert inventory["verified_manifest_artifacts"][0]["relative_path"] == (
        "captures/iphone-network.pcap"
    )
    derived = {item["kind"]: item for item in inventory["derived_session_artifacts"]}
    assert derived["capture-manifest"]["sha256"] == _sha256(before)
    assert derived["capture-stderr"]["size_bytes"] == 0
    assert derived["operator-events"]["relative_path"] == "operator-events.private.jsonl"
    assert destination.stat().st_mode & 0o777 == 0o600


def test_seal_session_rejects_capture_tampering(tmp_path: Path) -> None:
    session, capture = _session(tmp_path)
    capture.write_bytes(b"tampered")

    with pytest.raises(INTEGRITY.SessionIntegrityError, match="size mismatch"):
        INTEGRITY.seal_session(session)


def test_seal_session_refuses_to_replace_inventory_without_explicit_flag(
    tmp_path: Path,
) -> None:
    session, _ = _session(tmp_path)
    INTEGRITY.seal_session(session)

    with pytest.raises(INTEGRITY.SessionIntegrityError, match="already exists"):
        INTEGRITY.seal_session(session)


def test_seal_session_rejects_manifest_path_escape(tmp_path: Path) -> None:
    session, _ = _session(tmp_path)
    manifest_path = session / "manifest.redacted.json"
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    manifest["artifacts"][0]["relative_path"] = "../outside.pcap"
    manifest_path.write_text(json.dumps(manifest), encoding="utf-8")

    with pytest.raises(INTEGRITY.SessionIntegrityError, match="stay inside"):
        INTEGRITY.seal_session(session)
