# -*- coding: utf-8 -*-
"""
Tests for AR88-05D Final: Physical Transport, Reusable Session Lifecycle & Strict Host Attestation.

FOCAL VERIFICATIONS REQUIRED:
1. iso_ok=False jamás permite acreditación (prueba negativa obligatoria: con iso_ok=False, devuelve BLOCKED).
2. JSON fabricado no concede autoridad (sin sesión autenticada ni GUID de VM, rechazado).
3. El ciclo de ejecución interactivo se prepara para ejecutar sin dependencia de -NonInteractive.
4. El archivo objetivo y sus dependencias mínimas se transfieren de forma completa (manifest verificado).
5. La recuperación ejecutable de evidencias existe (Copy-Item -FromSession implementado).
6. Una falla de Python nunca produce PASS (código != 0 rechaza PASS).
7. Rutas fuera del workspace quedan rechazadas (Path traversal y escape a producción bloqueados).
8. Sin aislamiento acreditado en anfitrión, ninguna reparación dinámica (FAIL_CLOSED preservado).
9. Ningún cambio alcanza producción (0 escrituras en C:\\xampp\\htdocs\\Digital Optimus).
10. El defecto de CircuitTracer permanece intacto en laboratorio (A57A6ABC...).
"""
import hashlib
import json
import os
import sys
import tempfile
import pytest

WS = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if WS not in sys.path:
    sys.path.insert(0, WS)

from app.remediation.self_repair.hyperv_host_controller import (
    HyperVHostController,
    HyperVControllerConfig,
    HyperVControllerStatus,
    HostAttestedEvidence,
    MINIMAL_CONTRACT_MODULES,
)
from app.remediation.self_repair.functional_assessor import (
    FunctionalAssessmentStatus,
    FunctionalEvaluationResult,
    InternalFunctionalAssessor,
)
from app.remediation.self_repair.functional_diagnostic_adapter import (
    FunctionalDiagnosticAdapter,
    _compute_sha256,
)
from app.remediation.self_repair.internal_repair_coordinator import (
    InternalRepairCoordinator,
    InternalRepairStatus,
)
from app.remediation.self_repair.engine import SelfRepairEngine


def test_1_iso_ok_false_never_grants_attestation():
    """
    Comprobacion 1 (Prueba negativa obligatoria):
    Con iso_ok=False (o barreras de aislamiento no satisfechas), incluso si el
    JSON presenta GUID, hash y status PASS correctos, attest_guest_execution()
    devuelve incondicionalmente BLOCKED y ninguna evidencia acreditada.
    """
    config = HyperVControllerConfig(vm_name="TEITAN-AR88-LAB", workspace_root=WS)
    controller = HyperVHostController(config)

    # Forzar que la auditoría de aislamiento devuelva iso_ok = False
    controller.audit_vm_isolation_barriers = lambda: (
        False,
        "Network adapter detected on VM (unisolated)",
        {"network_adapters_zero": False, "all_barriers_satisfied": False},
    )

    perfect_json = json.dumps({
        "lifecycle_success": True,
        "stage_reached": "COMPLETE",
        "session_guid": "7a35607b-8395-467a-9db1-c56df9359e10",
        "vm_guid": "b2c6e788-99aa-4c22-b06f-1249fa6b2190",
        "guest_evidence": {
            "contract_id": "contract_circuit_tracer_downstream_break",
            "component_name": "CircuitTracer",
            "target_file_rel": "app/remediation/self_repair/circuit.py",
            "guest_target_sha256": "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
            "python_exit_code": 0,
            "assessment_status": "PASS",
            "duration_ms": 35.0,
        }
    })

    ok, reason, attested = controller.attest_guest_execution(
        lifecycle_output_json=perfect_json,
        expected_contract_id="contract_circuit_tracer_downstream_break",
        expected_target_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256="A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
    )
    # DEBE SER RECHAZADO: FAIL-CLOSED ANTE FALTA DE AISLAMIENTO
    assert ok is False
    assert "BLOCKED_ISOLATION_UNMET" in reason
    assert attested is None


def test_2_fabricated_json_denied_authority():
    """
    Comprobacion 2:
    Un JSON fabricado por el llamador sin GUID de sesión ni GUID de VM
    es rechazado categóricamente sin adquirir acreditación.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))

    # Mockear isolation_barriers como satisfechas para aislar la prueba de validación de JSON
    controller.audit_vm_isolation_barriers = lambda: (
        True,
        "OK",
        {"all_barriers_satisfied": True},
    )

    fake_json = json.dumps({
        "lifecycle_success": True,
        "stage_reached": "COMPLETE",
        "session_guid": "",  # Vacio!
        "vm_guid": "",       # Vacio!
        "guest_evidence": {
            "contract_id": "contract_circuit_tracer_downstream_break",
            "target_file_rel": "app/remediation/self_repair/circuit.py",
            "guest_target_sha256": "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
            "python_exit_code": 1,
            "assessment_status": "FAIL",
        }
    })

    ok, reason, attested = controller.attest_guest_execution(
        lifecycle_output_json=fake_json,
        expected_contract_id="contract_circuit_tracer_downstream_break",
        expected_target_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256="A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
    )
    assert ok is False
    assert "missing authenticated session" in reason
    assert attested is None


def test_3_interactive_lifecycle_script_structure():
    """
    Comprobacion 3:
    El script del ciclo interactivo captura credenciales interactivas en memoria ($guestCred),
    no usa contraseñas en texto plano y garantiza la limpieza de la sesión en el bloque finally.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    script = controller.build_unified_ps_direct_lifecycle_script(
        contract_id="contract_circuit_tracer_downstream_break",
        target_file_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256="A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
    )

    assert "Get-Credential -Message" in script
    assert "New-PSSession -VMName $vmName -Credential $guestCred" in script
    assert "} finally {" in script
    assert "Remove-PSSession -Session $sess" in script


def test_4_complete_minimal_dependencies_transfer_manifest():
    """
    Comprobacion 4:
    Se verifica que el controlador identifique y valide el conjunto completo mínimo
    de módulos internos requeridos por el contrato (app/__init__.py, schemas.py, circuit.py),
    calculando sus hashes antes de cualquier transferencia.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    ok, msg, manifest = controller.get_minimal_transfer_manifest()

    assert ok is True
    assert len(manifest) == 4
    rel_paths = [m["rel_path"] for m in manifest]
    assert "app/__init__.py" in rel_paths
    assert "app/remediation/self_repair/__init__.py" in rel_paths
    assert "app/remediation/self_repair/schemas.py" in rel_paths
    assert "app/remediation/self_repair/circuit.py" in rel_paths

    for item in manifest:
        assert os.path.isfile(item["abs_path"])
        assert len(item["sha256"]) == 64


def test_5_executable_evidence_retrieval_implemented():
    """
    Comprobacion 5:
    El ciclo implementa la recuperación ejecutable de evidencias hacia el host
    mediante Copy-Item -FromSession, guardándolas en evidence\\guest_runs.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    script = controller.build_unified_ps_direct_lifecycle_script(
        contract_id="contract_circuit_tracer_downstream_break",
        target_file_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256="A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
    )

    assert "RECOVER_EVIDENCE" in script
    assert "Copy-Item -Path $guestResultFile -Destination $hostRetrievedFile -FromSession $sess -Force" in script
    assert "evidence\\guest_runs" in script


def test_6_python_failure_never_produces_pass():
    """
    Comprobacion 6:
    Un código de salida de Python != 0 jamás puede conceder un status PASS.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    controller.audit_vm_isolation_barriers = lambda: (True, "OK", {"all_barriers_satisfied": True})

    inconsistent_json = json.dumps({
        "lifecycle_success": True,
        "stage_reached": "COMPLETE",
        "session_guid": "7a35607b-8395-467a-9db1-c56df9359e10",
        "vm_guid": "b2c6e788-99aa-4c22-b06f-1249fa6b2190",
        "guest_evidence": {
            "contract_id": "contract_circuit_tracer_downstream_break",
            "component_name": "CircuitTracer",
            "target_file_rel": "app/remediation/self_repair/circuit.py",
            "guest_target_sha256": "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
            "python_exit_code": 1,
            "assessment_status": "PASS",  # Inconsistente con exit_code=1!
        }
    })

    ok, reason, attested = controller.attest_guest_execution(
        lifecycle_output_json=inconsistent_json,
        expected_contract_id="contract_circuit_tracer_downstream_break",
        expected_target_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256="A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5",
    )
    assert ok is False
    assert "Inconsistent" in reason
    assert attested is None


def test_7_path_traversal_and_production_targets_rejected():
    """
    Comprobacion 7:
    Cualquier intento de traversal o escape hacia la carpeta de producción
    es rechazado antes de cualquier operación.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))

    # Intento traversal
    ok, msg, _ = controller.validate_workspace_path("../outside.py")
    assert ok is False
    assert "traversal" in msg.lower() or "escapes" in msg.lower()

    # Escape a produccion
    prod_target = r"C:\xampp\htdocs\Digital Optimus\app\remediation\self_repair\circuit.py"
    ok, msg, _ = controller.validate_workspace_path(prod_target)
    assert ok is False
    assert "production" in msg.lower()


def test_8_no_accredited_isolation_means_zero_dynamic_repair():
    """
    Comprobacion 8:
    InternalRepairCoordinator mantiene incondicionalmente FAIL_CLOSED en el anfitrión
    devolviendo BLOCKED_UNACCREDITED_ISOLATION.
    """
    target_rel = "app/remediation/self_repair/circuit.py"
    target_hash = _compute_sha256(os.path.join(WS, target_rel))

    eval_result = FunctionalEvaluationResult(
        contract_id="contract_circuit_tracer_downstream_break",
        component_name="CircuitTracer",
        target_file_rel=target_rel,
        file_sha256=target_hash,
        status=FunctionalAssessmentStatus.FAIL,
        duration_ms=30.0,
        assertion_error="AssertionError: effector expected NOT_REACHED, got CircuitStatus.UNKNOWN",
    )

    coord = InternalRepairCoordinator(project_root=WS)
    summary = coord.coordinate_internal_evaluation(eval_result, read_only=False, persist_artifacts=False)
    assert summary["status"] == InternalRepairStatus.BLOCKED_UNACCREDITED_ISOLATION
    assert summary["sandbox_repair_executed"] is False


def test_9_production_directory_remains_completely_untouched():
    """
    Comprobacion 9:
    CERO modificaciones en el directorio de producción.
    """
    prod_files = {
        r"C:\xampp\htdocs\Digital Optimus\app\remediation\self_repair\circuit.py": "AA3999237E9AF1ACC08B533460A73826984E320B893FFFD9BAD3A5D781BAFE7E",
        r"C:\xampp\htdocs\Digital Optimus\app\remediation\self_repair\engine.py": "B63087E87AA4BD3D49C81B4748C426EB939E035349D56DDDDBBAA85036E20C7E",
        r"C:\xampp\htdocs\Digital Optimus\app\remediation\self_repair\self_diagnostic.py": "CE84ED5437A1BE21B7F7C1E7D014374006C6AAA45FD359A300B99C294F3658F9",
        r"C:\xampp\htdocs\Digital Optimus\app\remediation\self_repair\schemas.py": "29D39B61AC9AA031F72E43A7F11153A15FB53F09E71715E6B4A89A58292DD564",
    }
    for path, expected_hash in prod_files.items():
        with open(path, "rb") as f:
            h = hashlib.sha256(f.read()).hexdigest().upper()
        assert h == expected_hash, f"Production file modified: {path}"


def test_10_circuittracer_defect_remains_intact_in_lab():
    """
    Comprobacion 10:
    El archivo circuit.py en el laboratorio conserva el defecto original
    (SHA-256: A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5).
    """
    lab_circuit = os.path.join(WS, "app/remediation/self_repair/circuit.py")
    actual_hash = _compute_sha256(lab_circuit).upper()
    assert actual_hash == "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5"
