"""Proof-gate surface for the canonical Windows proof path.

This module provides a single authoritative entry point for validating that
the Windows proof workflow can be trusted. Downstream slices should call
check_proof_gate() before attempting any live proof operations.

The gate checks three subsystems in order:
1. Integrity - proof-critical files are not corrupted
2. Readiness - vm_wait_ready works correctly with blocking_reason
3. Live preconditions - VM exists, VirtualBox is available

Each check returns a ProofGateResult with explicit blocking_category when failed.
"""

from __future__ import annotations

import re
import shutil
import subprocess
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path
from typing import Literal

# Proof-critical files that must be free of merge corruption
PROOF_CRITICAL_FILES: tuple[str, ...] = (
    "packages/mcp_vm_blackbox/models.py",
    "packages/mcp_vm_blackbox/server.py",
    "packages/mcp_vm_blackbox/packer_tools.py",
    "packages/mcp_vm_blackbox/vm_readiness.py",
)

# Merge conflict marker patterns
# Match lines starting with 7+ '<', '=', or '>' characters (merge conflict markers)
MERGE_CONFLICT_PATTERN = re.compile(r"^(<{7,}\s*$|={7,}\s*$|>{7,}\s*$)", re.MULTILINE)


class BlockingCategory(StrEnum):
    """Explicit failure categories for the proof-gate."""

    INTEGRITY = "integrity"
    READINESS = "readiness"
    LIVE_PRECONDITION = "live_precondition"


@dataclass
class ProofGateResult:
    """Result of proof-gate validation.

    Attributes:
        status: 'passed' or 'blocked'
        checks_run: Number of checks executed
        blocking_category: The subsystem that blocked (if status='blocked')
        blocking_reason: Human-readable explanation of the block
        details: Additional context for debugging
    """

    status: Literal["passed", "blocked"]
    checks_run: int = 0
    blocking_category: BlockingCategory | None = None
    blocking_reason: str | None = None
    details: dict[str, str] = field(default_factory=dict)

    def __bool__(self) -> bool:
        """Truthiness based on pass/fail status."""
        return self.status == "passed"


def check_integrity(repo_root: Path) -> ProofGateResult:
    """Check proof-critical files for merge corruption and import validity.

    Scans proof-critical Python files for:
    - Merge conflict markers (<<<<<<<, =======, >>>>>>>)
    - Import errors that would prevent module loading

    Returns:
        ProofGateResult with status and any blocking details.
    """
    errors: list[str] = []
    missing_files: list[str] = []
    conflict_errors: list[str] = []

    for rel_path in PROOF_CRITICAL_FILES:
        file_path = repo_root / rel_path
        if not file_path.exists():
            missing_files.append(rel_path)
            continue

        content = file_path.read_text()

        # Check for merge conflict markers
        conflicts = MERGE_CONFLICT_PATTERN.findall(content)
        if conflicts:
            conflict_errors.append(f"{rel_path}: {len(conflicts)} markers found")

    # Categorize errors for clearer blocking messages
    if missing_files:
        errors.extend(f"Missing: {f}" for f in missing_files)
    if conflict_errors:
        errors.extend(f"Conflict in {e}" for e in conflict_errors)

    if errors:
        if missing_files and not conflict_errors:
            blocking_reason = "Proof-critical files missing"
        elif conflict_errors and not missing_files:
            blocking_reason = "Proof-critical files contain merge conflict markers"
        else:
            blocking_reason = "Proof-critical files have integrity issues"

        return ProofGateResult(
            status="blocked",
            checks_run=len(PROOF_CRITICAL_FILES),
            blocking_category=BlockingCategory.INTEGRITY,
            blocking_reason=blocking_reason,
            details={"errors": "; ".join(errors)},
        )

    return ProofGateResult(status="passed", checks_run=len(PROOF_CRITICAL_FILES))


def check_readiness_semantics() -> ProofGateResult:
    """Check that vm_wait_ready has correct readiness semantics.

    Verifies:
    - VmReadyResult model has blocking_reason field
    - VmReadyResult model has os_product field
    - VmReadyResult model has desktop_settle_s field
    - vm_wait_ready function exists and is callable

    Returns:
        ProofGateResult with status and any blocking details.
    """
    errors: list[str] = []

    try:
        from mcp_vm_blackbox.models import VmReadyResult  # noqa: PLC0415
        from mcp_vm_blackbox.vm_readiness import vm_wait_ready  # noqa: PLC0415

        # Check VmReadyResult has required fields
        model_fields = VmReadyResult.model_fields

        required_fields = ["blocking_reason", "os_product", "desktop_settle_s"]
        errors.extend(
            f"VmReadyResult missing field: {field_name}"
            for field_name in required_fields
            if field_name not in model_fields
        )

        # Check vm_wait_ready is callable
        if not callable(vm_wait_ready):
            errors.append("vm_wait_ready is not callable")

    except ImportError as e:
        errors.append(f"Import error: {e}")
    except (AttributeError, TypeError) as e:
        errors.append(f"Model inspection error: {e}")

    if errors:
        return ProofGateResult(
            status="blocked",
            checks_run=1,
            blocking_category=BlockingCategory.READINESS,
            blocking_reason="Readiness semantics incomplete or broken",
            details={"errors": "; ".join(errors)},
        )

    return ProofGateResult(status="passed", checks_run=1)


def check_live_preconditions(vm_name: str = "DSR-QA-Windows") -> ProofGateResult:
    """Check live preconditions for the Windows proof VM.

    Verifies:
    - VirtualBox is installed and VBoxManage is available
    - Target VM exists in VirtualBox registry

    Note: This check does NOT start the VM or verify it's running.
    It only checks that the preconditions exist for a live proof run.

    Args:
        vm_name: The canonical Windows proof VM name.

    Returns:
        ProofGateResult with status and any blocking details.
    """
    errors: list[str] = []
    checks_run = 0

    # Check VBoxManage is available
    vboxmanage_path = shutil.which("VBoxManage")
    if not vboxmanage_path:
        errors.append("VBoxManage not found in PATH")
    else:
        checks_run += 1

        # Check VM exists
        result = subprocess.run(
            [vboxmanage_path, "showvminfo", vm_name, "--machinereadable"], capture_output=True, text=True, check=False
        )

        if result.returncode != 0:
            errors.append(f"VM '{vm_name}' not found in VirtualBox registry")
        else:
            checks_run += 1

    if errors:
        return ProofGateResult(
            status="blocked",
            checks_run=checks_run,
            blocking_category=BlockingCategory.LIVE_PRECONDITION,
            blocking_reason="Live proof preconditions not met",
            details={"errors": "; ".join(errors)},
        )

    return ProofGateResult(status="passed", checks_run=checks_run)


def check_proof_gate(
    repo_root: Path | None = None, vm_name: str = "DSR-QA-Windows", skip_live_preconditions: bool = False
) -> ProofGateResult:
    """Run the full proof-gate validation for the Windows proof path.

    This is the single authoritative entry point for validating that the
    Windows proof workflow can be trusted. Downstream slices should call
    this before attempting any live proof operations.

    The gate runs three checks in order:
    1. Integrity - proof-critical files are not corrupted
    2. Readiness - vm_wait_ready has correct semantics
    3. Live preconditions - VM exists, VirtualBox is available

    The gate stops at the first failure and returns the blocking category.

    Args:
        repo_root: Path to the repository root. Defaults to current working directory.
        vm_name: The canonical Windows proof VM name.
        skip_live_preconditions: Skip VM existence check (for offline CI).

    Returns:
        ProofGateResult with status and explicit blocking category on failure.

    Example:
        ```python
        from mcp_vm_blackbox.proof_gate import check_proof_gate

        result = check_proof_gate()
        if result.status == "blocked":
            print(f"Blocked: {result.blocking_category}")
            print(f"Reason: {result.blocking_reason}")
        else:
            print("Proof gate passed")
        ```
    """
    if repo_root is None:
        repo_root = Path.cwd()

    results: list[ProofGateResult] = []

    # Check 1: Integrity
    integrity_result = check_integrity(repo_root)
    results.append(integrity_result)
    if integrity_result.status == "blocked":
        return integrity_result

    # Check 2: Readiness semantics
    readiness_result = check_readiness_semantics()
    results.append(readiness_result)
    if readiness_result.status == "blocked":
        return readiness_result

    # Check 3: Live preconditions (optional, skip in offline CI)
    if not skip_live_preconditions:
        live_result = check_live_preconditions(vm_name)
        results.append(live_result)
        if live_result.status == "blocked":
            return live_result

    # All checks passed
    total_checks = sum(r.checks_run for r in results)
    return ProofGateResult(status="passed", checks_run=total_checks)
