"""AutoPOE2 - Unit & Integration Tests cho Zero-Day Invariant Gate & Recovery Plan.
===================================================================================
[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME] (Rule 5 Compliance)

Domain: QA & Governance Sentinel (Sư đoàn 4)
Kiểm thử toàn diện 4 chốt chặn kiểm toán và quy trình phục hồi sau bảo trì:
- Gate 1: Client Process Health & Log Integrity (Client.txt load clean, 0 crash/fatal).
- Gate 2: Memory Drift Audit (INV-MEM-DYNAMIC-XYZ, PlayerFinder quét 16MB tìm XYZ sống, từ chối static offset drift).
- Gate 3: UI Perception Invariants (INV-MAPDEVICE-VERIFY-UI, TRAVERSE OCR, Stash UI verification).
- Gate 4: Hot Path DOD Reflexes (INV-ZERO-HEAP-HOTPATH, 1086 C++ tests PASS).
- Kế hoạch phục hồi tự động khi game client mở lại (INV-CLIENT-CRASH-RECOVER).
"""

from __future__ import annotations

import os
import shutil
import tempfile
import time
from unittest.mock import MagicMock, patch

import pytest

from src.assistant_tool.zero_day_gate import (
    GateCheckResult,
    GateStatus,
    ZeroDayAuditReport,
    ZeroDayInvariantGate,
)


@pytest.fixture
def temp_gate_env():
    """Tạo môi trường kiểm thử cô lập cho Cổng Zero-Day."""
    temp_dir = tempfile.mkdtemp(prefix="zero_day_gate_test_")
    log_file = os.path.join(temp_dir, "Client.txt")
    incidents_dir = os.path.join(temp_dir, "incidents")

    with open(log_file, "w", encoding="utf-8") as f:
        f.write("2026/09/18 05:00:00 12345678 [INFO Client 22632] Connected to server.\n")
        f.write("2026/09/18 05:00:05 12345679 [INFO Client 22632] Entering area Shoreline Hideout.\n")

    yield {
        "temp_dir": temp_dir,
        "log_file": log_file,
        "incidents_dir": incidents_dir,
    }

    shutil.rmtree(temp_dir, ignore_errors=True)


class TestZeroDayGate1ClientHealth:
    """Kiểm thử Chốt chặn Gate 1: Client Process Health & Log Integrity."""

    def test_gate1_passes_on_clean_log(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Client process sống và log Client.txt hoàn toàn sạch 0 fatal crash -> Gate 1 PASSED.
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            res = gate.check_gate1_client_health(client_pid=22632)
            assert res.status == GateStatus.PASSED
            assert res.gate_id == "GATE-1"
            assert "0 fatal crash" in res.message

    def test_gate1_blocks_on_fatal_crash_signature(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Khi Client.txt chứa ngoại lệ nghiêm trọng (Fatal crash, DX12 removed) -> Gate 1 BLOCKED.
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        # Ghi log crash nghiêm trọng
        with open(temp_gate_env["log_file"], "a", encoding="utf-8") as f:
            f.write("2026/09/18 05:01:00 12345680 [CRIT Client 22632] Fatal Error: DX12 device removed!\n")

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            res = gate.check_gate1_client_health(client_pid=22632)
            assert res.status == GateStatus.BLOCKED
            assert "Phát hiện" in res.message
            assert len(res.details.get("fatal_matches", [])) > 0


class TestZeroDayGate2MemoryDrift:
    """Kiểm thử Chốt chặn Gate 2: Memory Drift Audit (INV-MEM-DYNAMIC-XYZ)."""

    def test_gate2_passes_on_valid_dynamic_xyz(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Tọa độ XYZ hợp lệ được quét từ PlayerFinder 16MB -> Gate 2 PASSED.
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate2_memory_drift(
            pos_x=150.5,
            pos_y=280.2,
            pos_z=0.0,
            static_offset_detected=False,
            player_finder_16mb_active=True,
        )
        assert res.status == GateStatus.PASSED
        assert res.invariant_code == "INV-MEM-DYNAMIC-XYZ"

    def test_gate2_blocks_on_static_offset_drift(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Khi phát hiện cố tình dùng static offset cũ (0x290/0x540) bị lệch -> Gate 2 BLOCKED.
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate2_memory_drift(
            pos_x=150.5,
            pos_y=280.2,
            pos_z=0.0,
            static_offset_detected=True,  # Lệch offset sau bản vá
            player_finder_16mb_active=True,
        )
        assert res.status == GateStatus.BLOCKED
        assert "Static Offset Drift" in res.message

    def test_gate2_blocks_on_out_of_bounds_and_frozen(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Bắt lỗi tọa độ vượt biên hoặc kẹt gốc (0,0,0) khi đang di chuyển -> Gate 2 BLOCKED.
        """
        gate = ZeroDayInvariantGate()

        # Out of bounds
        res_oob = gate.check_gate2_memory_drift(pos_x=99999.0, pos_y=100.0, pos_z=0.0)
        assert res_oob.status == GateStatus.BLOCKED

        # Frozen at (0,0,0)
        res_frozen = gate.check_gate2_memory_drift(
            pos_x=0.0, pos_y=0.0, pos_z=0.0, is_moving=True
        )
        assert res_frozen.status == GateStatus.BLOCKED


class TestZeroDayGate3UIPerception:
    """Kiểm thử Chốt chặn Gate 3: UI Perception Invariants (INV-MAPDEVICE-VERIFY-UI)."""

    def test_gate3_passes_on_verified_optical_ui(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Giao diện Map Device có tín hiệu TRAVERSE OCR / khay Waystone -> Gate 3 PASSED.
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate3_ui_perception(
            has_traverse_button=True,
            has_waystone_slot=True,
            has_verified_ui_signal=True,
        )
        assert res.status == GateStatus.PASSED
        assert res.invariant_code == "INV-MAPDEVICE-VERIFY-UI"

    def test_gate3_blocks_fail_closed_when_ui_missing(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Không có tín hiệu quang học UI nào -> Gate 3 BLOCKED (Fail-closed kích hoạt).
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate3_ui_perception(
            has_traverse_button=False,
            has_waystone_slot=False,
            has_verified_ui_signal=False,
            stash_opened=False,
        )
        assert res.status == GateStatus.BLOCKED
        assert "Fail-closed" in res.message


class TestZeroDayGate4HotPathDOD:
    """Kiểm thử Chốt chặn Gate 4: Hot Path DOD Reflexes (INV-ZERO-HEAP-HOTPATH & 1086 C++ tests)."""

    def test_gate4_passes_with_1086_cpp_tests_and_zero_heap(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Đủ 1086/1086 C++ tests PASS và tuân thủ Zero Heap DOD -> Gate 4 PASSED.
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate4_hot_path_dod(
            cpp_passed_count=1086,
            zero_heap_compliant=True,
            actuator_latency_us=18.5,
        )
        assert res.status == GateStatus.PASSED
        assert "1086/1086 C++ tests PASS" in res.message

    def test_gate4_blocks_if_cpp_tests_regressed(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Nếu C++ tests không đạt đủ 1086 checks -> Gate 4 BLOCKED.
        """
        gate = ZeroDayInvariantGate()
        res = gate.check_gate4_hot_path_dod(cpp_passed_count=1080)
        assert res.status == GateStatus.BLOCKED
        assert "chỉ đạt 1080/1086" in res.message


class TestZeroDayFullAuditAndRecoveryPlan:
    """Kiểm thử Tổng hợp Cổng Zero-Day và Kế hoạch Phục hồi Sau Bảo Trì."""

    def test_full_zero_day_audit_all_gates_cleared(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Xác nhận toàn bộ 4 Gates đều PASSED -> Đủ điều kiện tự hành (is_cleared_for_autonomous = True).
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            report = gate.execute_full_zero_day_audit(client_pid=22632)
            assert report.is_cleared_for_autonomous is True
            assert report.total_gates_checked == 4
            assert report.gates_passed == 4
            assert report.gates_blocked == 0
            assert "PASSED" in report.summary

    def test_full_zero_day_audit_blocks_when_any_gate_fails(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Chỉ cần 1 Gate thất bại -> Toàn bộ Cổng bị BLOCKED (Fail-closed).
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            # Cố tình gây lỗi Gate 2 (Static offset drift)
            report = gate.execute_full_zero_day_audit(
                client_pid=22632,
                static_offset_detected=True,
            )
            assert report.is_cleared_for_autonomous is False
            assert report.gates_blocked >= 1
            assert "BLOCKED" in report.summary

    def test_post_maintenance_recovery_plan_5_steps_execution(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Kiểm chứng quy trình 5 bước phục hồi tự động khi game client mở lại (INV-CLIENT-CRASH-RECOVER).
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        mock_launcher = MagicMock(return_value=True)
        mock_zone = MagicMock(return_value="Shoreline Hideout")

        plan = gate.execute_post_maintenance_recovery_plan(
            launcher_func=mock_launcher,
            zone_verifier_func=mock_zone,
            is_simulated=True,
        )

        assert plan["status"] == "SUCCESS"
        assert len(plan["steps_completed"]) == 5
        expected_steps = [
            "STEP1_WATCHDOG_DETECT",
            "STEP2_INCIDENT_DOSSIER_PACKAGED",
            "STEP3_CLIENT_LAUNCHED",
            "STEP4_HIDEOUT_SAFE_VERIFIED",
            "STEP5_ZERO_DAY_GATES_CLEARED",
        ]
        assert plan["steps_completed"] == expected_steps
        assert os.path.isdir(plan["incident_dir"])
        assert plan["audit_report"].is_cleared_for_autonomous is True

    def test_gate1_blocks_on_d3d12_pso_crash(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Quét chính xác các lỗi Fatal D3D12, PSO creation failure sau bản vá -> Gate 1 BLOCKED.
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )
        with open(temp_gate_env["log_file"], "a", encoding="utf-8") as f:
            f.write("2026/09/18 07:15:00 [CRIT Client 22632] PSO creation failed: CreateGraphicsPipelineState error\n")

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            res = gate.check_gate1_client_health(client_pid=22632)
            assert res.status == GateStatus.BLOCKED
            assert len(res.details.get("fatal_matches", [])) > 0

    def test_gate2_blocks_on_unattached_player_address_or_non_ci_vitals(self):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Bắt lỗi con trỏ nhân vật 0x0 (Rule 5) và vi phạm INV-CI-01 (HP khác 1/1) -> Gate 2 BLOCKED.
        """
        gate = ZeroDayInvariantGate()

        # Case 1: Player pointer is 0x0 (Unattached memory - Rule 5)
        res_null_ptr = gate.check_gate2_memory_drift(
            pos_x=100.0, pos_y=100.0, pos_z=0.0, player_addr="0x0"
        )
        assert res_null_ptr.status == GateStatus.BLOCKED
        assert res_null_ptr.invariant_code == "INV-RECONNECT-NO-HARDCODED-VITALS"

        # Case 2: HP != 1/1 on CI build (INV-CI-01)
        res_non_ci = gate.check_gate2_memory_drift(
            pos_x=100.0, pos_y=100.0, pos_z=0.0, hp=1500, max_hp=1500, is_ci_build=True
        )
        assert res_non_ci.status == GateStatus.BLOCKED
        assert res_non_ci.invariant_code == "INV-CI-01"

    def test_autonomy_readiness_score_quantification(self, temp_gate_env):
        """[MÔ PHỎNG THUẬT TOÁN HEADLESS - KHÔNG PHẢI LIVE GAME]
        Kiểm toán công thức ARS: 100% khi 4 Gates PASS; 0% (Fail-closed) khi 1 Gate BLOCKED.
        """
        gate = ZeroDayInvariantGate(
            client_log_path=temp_gate_env["log_file"],
            incidents_dir=temp_gate_env["incidents_dir"],
        )

        with patch("src.assistant_tool.client_launcher.Poe2ClientLauncher.is_client_running", return_value=True):
            # All 4 Gates PASS -> ARS = 100.0%
            clean_report = gate.execute_full_zero_day_audit(client_pid=22632)
            assert clean_report.autonomy_readiness_score == 100.0

            # 1 Gate BLOCKED -> ARS = 0.0% (Fail-closed)
            blocked_report = gate.execute_full_zero_day_audit(
                client_pid=22632,
                static_offset_detected=True,
            )
            assert blocked_report.autonomy_readiness_score == 0.0
            assert blocked_report.is_cleared_for_autonomous is False

