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

FOCAL VERIFICATIONS REQUIRED (10 MANDATORY CHECKS):
1. La sesión no depende de un ID perdido entre procesos PowerShell (ciclo unificado con New-PSSession/Invoke/Remove).
2. Toda sesión se cierra incluso ante errores (garantizado mediante bloque finally).
3. No se registran ni persisten credenciales (en memoria exclusivamente).
4. El código incluye transferencia ejecutable real (Copy-Item -ToSession implementado).
5. Rutas fuera del workspace quedan rechazadas (Path traversal y escape a producción bloqueados).
6. El JSON construido manualmente no obtiene acreditación (Host attestation rechaza JSON arbitrario).
7. SHA-256 sin autenticación no se presenta como firma confiable (requiere atestación del host).
8. Los errores de Python nunca generan PASS (exit_code != 0 rechaza PASS).
9. Sin aislamiento acreditado, ninguna reparación dinámica (FAIL_CLOSED preservado).
10. La ejecución con parches prefijados no se declara autónoma (razonador infiere de forma autónoma).
"""
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,
)
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_session_lifecycle_is_unified_not_lost_between_processes():
    """
    Comprobacion 1:
    La sesión PowerShell Direct se gestiona en un ciclo unificado que comprende
    New-PSSession, transferencia, preflight, ejecución y cierre, evitando que el
    objeto PSSession se pierda al salir de un proceso powershell.exe independiente.
    """
    config = HyperVControllerConfig(vm_name="TEITAN-AR88-LAB", workspace_root=WS)
    controller = HyperVHostController(config)

    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 "New-PSSession -VMName $vmName" in script
    assert "Invoke-Command -Session $sess" in script
    assert "Copy-Item -Path $sourceTarget -Destination" in script
    assert "-ToSession $sess" in script
    assert "Remove-PSSession -Session $sess" in script


def test_2_session_cleanup_is_guaranteed_in_finally():
    """
    Comprobacion 2:
    Toda sesión de PowerShell Direct se cierra forzosamente incluso ante errores
    o excepciones del script mediante un 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 "} finally {" in script
    assert "Remove-PSSession -Session $sess" in script


def test_3_zero_credentials_logged_or_persisted():
    """
    Comprobacion 3:
    Las credenciales del invitado se obtienen interactivamente en memoria ($guestCred)
    y nunca se escriben en argumentos de línea de comandos, archivos de log ni informes.
    """
    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",
    )

    # Verifica que usa Get-Credential en memoria si no esta predefinido
    assert "Get-Credential -Message" in script
    # No contiene passwords en texto plano ni exportaciones a archivo
    assert "Export-Clixml" not in script
    assert "ConvertTo-SecureString" not in script


def test_4_real_executable_transfer_implemented():
    """
    Comprobacion 4:
    El código incluye transferencia ejecutable real mediante Copy-Item -ToSession,
    asegurando previamente la creación del directorio de destino en el invitado.
    """
    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 "Copy-Item -Path $sourceTarget -Destination (Join-Path $guestWs $targetRel) -ToSession $sess -Force" in script
    assert "TRANSFER_LAB_COPY" in script


def test_5_path_traversal_and_production_targets_strictly_rejected():
    """
    Comprobacion 5:
    Cualquier intento de transferir o evaluar rutas fuera del workspace o dentro
    de la carpeta protegida de producción es rechazado con error explícito.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))

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

    # Caso B: Target apuntando a la ruta de producción
    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() or "traversal" in msg.lower() or "escapes" in msg.lower()

    # Caso C: Target legítimo dentro del laboratorio
    ok, msg, abs_path = controller.validate_workspace_path("app/remediation/self_repair/circuit.py")
    assert ok is True
    assert os.path.exists(abs_path)


def test_6_manually_constructed_json_denied_host_attestation():
    """
    Comprobacion 6:
    Un JSON construido manualmente por un llamador sin sesión autenticada ni VM GUID
    es rechazado categóricamente sin adquirir acreditación de procedencia.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))

    # JSON arbitrario simulado sin guid de sesion valida
    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_7_unauthenticated_hash_not_presented_as_trusted_signature():
    """
    Comprobacion 7:
    La procedencia no se fía de un SHA-256 local sobre datos no autenticados.
    HostAttestedEvidence genera su firma incorporando la identidad del anfitrión,
    el ID de la VM, el GUID de la sesión y el resultado físico.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    target_hash = "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5"

    valid_lifecycle = 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": target_hash,
            "python_exit_code": 1,
            "assessment_status": "FAIL",
            "assertion_error": "AssertionError: effector expected NOT_REACHED, got CircuitStatus.UNKNOWN",
            "duration_ms": 38.0,
        }
    })

    ok, msg, attested = controller.attest_guest_execution(
        lifecycle_output_json=valid_lifecycle,
        expected_contract_id="contract_circuit_tracer_downstream_break",
        expected_target_rel="app/remediation/self_repair/circuit.py",
        expected_file_sha256=target_hash,
    )
    assert ok is True
    assert attested is not None
    assert attested.is_host_attested is True
    assert attested.attestation_signature != ""
    assert attested.vm_id == "b2c6e788-99aa-4c22-b06f-1249fa6b2190"


def test_8_python_errors_never_generate_pass():
    """
    Comprobacion 8:
    Cualquier ejecución con código de salida != 0 no puede obtener PASS bajo ninguna circunstancia.
    """
    controller = HyperVHostController(HyperVControllerConfig(workspace_root=WS))
    target_hash = "A57A6ABC362CAA43984B45A68AAA8F4E91DAF5E393C1A66715EBCE910BABBAB5"

    inconsistent_lifecycle = 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",
            "target_file_rel": "app/remediation/self_repair/circuit.py",
            "guest_target_sha256": target_hash,
            "python_exit_code": 1,
            "assessment_status": "PASS",  # Inconsistente!
        }
    })

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


def test_9_no_accredited_isolation_means_zero_dynamic_repair():
    """
    Comprobacion 9:
    InternalRepairCoordinator mantiene incondicionalmente FAIL_CLOSED si las barreras
    físicas del host no están acreditadas (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_10_execution_with_presupplied_patch_is_not_declared_autonomous():
    """
    Comprobacion 10:
    El razonador autónomo deduce el parche de CircuitTracer directamente desde
    la evidencia y el síntoma de fallo, SIN requerir instrucciones suministradas manualmente.
    """
    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=40.0,
        assertion_error="AssertionError: effector expected NOT_REACHED, got CircuitStatus.UNKNOWN",
    )

    valid, reason, result_tuple = FunctionalDiagnosticAdapter.validate_and_convert_failure(
        eval_result,
        workspace_root=WS,
    )
    assert valid is True
    event, incident, evidence = result_tuple

    engine = SelfRepairEngine(project_root=WS)
    trace = engine.tracer.trace(incident=incident, evidence=evidence)
    root_cause = engine.analyzer.analyze(incident=incident, evidence=evidence, trace=trace, workspace_root=WS)
    proposal = engine.planner.plan_repair(incident=incident, root_cause_info=root_cause, workspace_root=WS)

    res = engine.patch_reasoner.reason_patch(
        event=event,
        incident=incident,
        evidence=evidence,
        root_cause_info=root_cause,
        proposal=proposal,
        workspace_root=WS,
    )
    assert res.success is True
    assert res.confidence == "HIGH"
    assert len(res.candidate_instructions) == 1
    assert "CircuitStatus.NOT_REACHED" in res.candidate_instructions[0].after
