"""AutoPOE2 - Bộ Kiểm Thử Phục Hồi Lỗi & Kiểm Toán Sức Bền (Fault Injection & Resilience Harness)
Domain: QA & Governance Sentinel (Sư đoàn 4)
Tuân thủ nghiêm ngặt:
- Rule 8: Kiểm toán toàn diện Master Invariant Matrix.
- Rule 9: Verification Gate độc lập.
- Rule 11: Phòng ngừa hồi quy bằng Mock Harness tự động.
- Rule 14: 100% dữ liệu cấu trúc thực nghiệm, không mock ảo.

Kiểm chứng 3 kịch bản trọng yếu:
1. Scenario 1 (Focus Loss): Cửa sổ game mất focus tiền cảnh -> Focus Interlock chặn 100% phím/chuột.
2. Scenario 2 (IPC Watchdog Degradation): Heartbeat gián đoạn > 3000ms -> Co-Pilot chuyển sang Passive Degrade, nhả WASD an toàn.
3. Scenario 3 (Faustus Gold Exhaustion Guard): Quỹ vàng <= 5,000 Gold -> Khóa giao dịch bảo vệ INV-EXCHANGE-GOLD-TAX.
"""

from __future__ import annotations

import time
from typing import Any, Dict, List, Optional
from unittest.mock import MagicMock, patch

import pytest

from src.agentic_engine.invariant_sentinel import (
    ALL_INVARIANT_CODES,
    ERR_CORE_DISCONNECTED,
    ERR_WATCHDOG_HEARTBEAT_TIMEOUT,
    InvariantSentinel,
)
from src.assistant_tool.agent_watcher import AgentWatcherSentinel
from src.assistant_tool.currency_exchange.models import (
    ExchangeActionType,
    ExchangeOrder,
    ExchangeOrderStatus,
    ExchangePortfolioConfig,
)
from src.assistant_tool.currency_exchange.portfolio_optimizer import (
    CurrencyPortfolioOptimizer,
    OptimizationResult,
)
from src.assistant_tool.currency_exchange.exchange_orchestrator import (
    CurrencyExchangeOrchestrator,
    ExchangeExecutionReport,
)
from src.assistant_tool.item_hover_watcher import ItemHoverWatcher


# ============================================================================
# SCENARIO 1: FOCUS LOSS & DESKTOP SAFETY INTERLOCK
# ============================================================================

class TestFocusLossInterlock:
    """Kiểm tra phản ứng khi cửa sổ game đột ngột mất focus tiền cảnh.
    
    Bất biến liên quan:
    - INV-INPUT-ABS-MOUSE
    - INV-WASD-NO-BLIND-LMB
    - INV-F8-MUTES-BRAIN
    """

    def test_focus_loss_blocks_item_hover_watcher_input(self):
        """Khi cửa sổ game mất focus, ItemHoverWatcher KHÔNG ĐƯỢC gửi Ctrl+C hoặc phím bất kỳ."""
        watcher = ItemHoverWatcher(dwell_time_ms=100.0, enable_focus_interlock=True)
        watcher.send_ctrl_c = MagicMock()
        watcher.read_clipboard_text = MagicMock(return_value="Item Class: Boots\nRarity: Rare")

        # 1. Giả lập Game đang Focused -> Hoạt động bình thường
        watcher._mock_is_focused = True
        assert watcher.is_game_window_focused() is True

        state = {
            "last_pos": (500, 500),
            "anchor_pos": (500, 500),
            "dwell_accum_ms": 120.0,
            "has_triggered": False,
        }
        watcher.process_tick(500, 500, state)
        assert watcher.send_ctrl_c.call_count == 1
        assert state["has_triggered"] is True

        # 2. FAULT INJECTION: Người dùng Alt-Tab, game mất focus
        watcher._mock_is_focused = False
        assert watcher.is_game_window_focused() is False

        watcher.send_ctrl_c.reset_mock()
        state["dwell_accum_ms"] = 120.0
        state["has_triggered"] = False

        # Quét lại tick -> Focus Interlock PHẢI chặn 100% lệnh phát phím
        watcher.process_tick(500, 500, state)
        assert watcher.send_ctrl_c.call_count == 0, (
            "VI PHẠM FOCUS INTERLOCK: send_ctrl_c bị gọi khi game đang mất focus!"
        )

    def test_hideout_navigator_focus_interlock_verification(self):
        """Khi HideoutNavigator bắt đầu điều hướng, phải xác nhận focus cửa sổ trước khi gửi WASD."""
        from src.assistant_tool.hideout_navigator import HideoutNavigator

        nav = HideoutNavigator()
        mock_send_wasd = MagicMock()
        mock_capture = MagicMock(return_value="test.png")
        mock_find_md = MagicMock(return_value=(1175, 645))

        with patch("src.common.win32_window.find_poe2_window", return_value=12345):
            with patch("src.common.win32_window.ensure_window_focus") as mock_ensure_focus:
                nav.navigate_from_salvage_to_map_device(
                    send_wasd_func=mock_send_wasd,
                    capture_func=mock_capture,
                    find_map_device_func=mock_find_md,
                )
                assert mock_ensure_focus.call_count >= 1, "Phải gọi ensure_window_focus trước khi di chuyển!"

    def test_focus_loss_safety_interlock_state_consistency(self):
        """Kiểm tra khôi phục focus: Sau khi lấy lại focus, hệ thống cho phép tiếp tục an toàn."""
        watcher = ItemHoverWatcher(enable_focus_interlock=True)
        watcher.send_ctrl_c = MagicMock()

        # Mất focus
        watcher._mock_is_focused = False
        state = {"last_pos": (200, 200), "anchor_pos": (200, 200), "dwell_accum_ms": 200.0, "has_triggered": False}
        watcher.process_tick(200, 200, state)
        assert watcher.send_ctrl_c.call_count == 0

        # Khôi phục focus
        watcher._mock_is_focused = True
        state["has_triggered"] = False
        state["dwell_accum_ms"] = 200.0
        watcher.process_tick(200, 200, state)
        assert watcher.send_ctrl_c.call_count == 1


# ============================================================================
# SCENARIO 2: IPC WATCHDOG DEGRADATION & CO-PILOT PASSIVE DEGRADE
# ============================================================================

class TestIpcWatchdogDegradation:
    """Kiểm tra phản ứng khi kênh giao tiếp IPC gián đoạn nhịp tim > 3000ms.
    
    Bất biến liên quan:
    - INV-F8-MUTES-BRAIN
    - INV-WASD-NO-BLIND-LMB
    - INV-CLIENT-CRASH-RECOVER
    """

    def test_watchdog_heartbeat_interruption_over_3000ms(self):
        """Gián đoạn nhịp tim > 3000ms kích hoạt cảnh báo ERR_WATCHDOG_HEARTBEAT_TIMEOUT."""
        now_ms = int(time.time() * 1000)

        # 1. Nhịp tim bình thường (mới nhận cách đây 250ms)
        errs_healthy = InvariantSentinel.check_watchdog_invariants(
            last_heartbeat_ms=now_ms - 250,
            core_is_online=True,
            timeout_ms=3000,
            now_ms=now_ms,
        )
        assert errs_healthy == []

        # 2. FAULT INJECTION: Gián đoạn nhịp tim kéo dài 3200ms (> 3000ms)
        errs_timeout = InvariantSentinel.check_watchdog_invariants(
            last_heartbeat_ms=now_ms - 3200,
            core_is_online=True,
            timeout_ms=3000,
            now_ms=now_ms,
        )
        assert ERR_WATCHDOG_HEARTBEAT_TIMEOUT in errs_timeout

    def test_watchdog_passive_degrade_triggers_safe_wasd_release(self):
        """Khi Co-Pilot phát hiện mất nhịp tim, phải kích hoạt nhả toàn bộ phím WASD.
        
        Xác minh bất biến INV-F8-MUTES-BRAIN và quy tắc an toàn chống chạy mù.
        """
        released_keys: List[int] = []

        def mock_keybd_event(vk, scan, flags, extra):
            # flags == 2 là KEYEVENTF_KEYUP
            if flags & 0x0002:
                released_keys.append(vk)

        with patch("ctypes.windll.user32.keybd_event", side_effect=mock_keybd_event):
            with patch("ctypes.windll.user32.mouse_event") as mock_mouse:
                from src.common.win32_input import emergency_f8_pause_and_release_all, VK_W, VK_A, VK_S, VK_D

                emergency_f8_pause_and_release_all()

                # Xác minh đã nhả toàn bộ 4 phím di chuyển WASD
                assert VK_W in released_keys, "Phải nhả phím W"
                assert VK_A in released_keys, "Phải nhả phím A"
                assert VK_S in released_keys, "Phải nhả phím S"
                assert VK_D in released_keys, "Phải nhả phím D"
                # Xác minh đã nhả chuột
                assert mock_mouse.call_count >= 2

    def test_watchdog_recovers_to_healthy_when_heartbeat_resumes(self):
        """Khi nhịp tim phục hồi sau gián đoạn, hệ thống phải tự động xóa cờ cảnh báo lỗi."""
        now_ms = int(time.time() * 1000)

        # Đang lỗi
        errs1 = InvariantSentinel.check_watchdog_invariants(
            last_heartbeat_ms=now_ms - 4500,
            core_is_online=True,
            timeout_ms=3000,
            now_ms=now_ms,
        )
        assert ERR_WATCHDOG_HEARTBEAT_TIMEOUT in errs1

        # Phục hồi nhịp tim mới
        now_ms_new = now_ms + 1000
        errs2 = InvariantSentinel.check_watchdog_invariants(
            last_heartbeat_ms=now_ms_new - 50,
            core_is_online=True,
            timeout_ms=3000,
            now_ms=now_ms_new,
        )
        assert errs2 == [], "Hệ thống phải trở lại trạng thái Healthy sau khi nhận heartbeat"

    def test_core_disconnect_isolation(self):
        """Nếu core báo offline hoàn toàn, Sentinel kích hoạt ERR_CORE_DISCONNECTED."""
        errs = InvariantSentinel.check_watchdog_invariants(
            last_heartbeat_ms=int(time.time() * 1000),
            core_is_online=False,
            timeout_ms=3000,
        )
        assert ERR_CORE_DISCONNECTED in errs


# ============================================================================
# SCENARIO 3: FAUSTUS GOLD EXHAUSTION GUARD (INV-EXCHANGE-GOLD-TAX)
# ============================================================================

class TestFaustusGoldExhaustionGuard:
    """Kiểm tra cơ chế ngắt giao dịch bảo vệ quỹ vàng sinh tồn tối thiểu 5,000 Gold.
    
    Bất biến liên quan:
    - INV-EXCHANGE-GOLD-TAX: Cấm thực hiện bất kỳ giao dịch nào khiến quỹ vàng sau thuế < 5000 Gold.
    """

    def test_gold_at_exact_5000_threshold_rejects_exchange(self):
        """Ví vàng đúng bằng 5,000 Gold: Phải từ chối giao dịch để bảo toàn định mức an toàn."""
        cfg = ExchangePortfolioConfig(min_gold_reserve=5000)
        optimizer = CurrencyPortfolioOptimizer(config=cfg)

        wallet = {"Exalted Orb": 20, "Chaos Orb": 10}
        market_prices = {"Exalted Orb": 12.0, "Divine Orb": 150.0}

        # current_gold == 5000
        result = optimizer.optimize_portfolio(wallet, market_prices, current_gold=5000)
        assert result.is_affordable is False
        assert len(result.orders) == 0
        assert any("INV-EXCHANGE-GOLD-TAX" in note or "Khóa an toàn Vàng" in note for note in result.summary_notes)

    def test_gold_below_5000_threshold_rejects_exchange(self):
        """Ví vàng tụt xuống 4,999 Gold: Thuật toán phải lập tức ngắt toàn bộ đơn hàng."""
        cfg = ExchangePortfolioConfig(min_gold_reserve=5000)
        optimizer = CurrencyPortfolioOptimizer(config=cfg)

        wallet = {"Exalted Orb": 50, "Chaos Orb": 200}
        market_prices = {"Exalted Orb": 10.0, "Divine Orb": 140.0}

        # current_gold == 4999 (tiệm cận ngưỡng)
        result = optimizer.optimize_portfolio(wallet, market_prices, current_gold=4999)
        assert result.is_affordable is False
        assert len(result.orders) == 0
        assert any("Khóa an toàn Vàng" in note for note in result.summary_notes)

    def test_gold_above_5000_but_order_tax_depletes_reserve_is_rejected(self):
        """Ví vàng có 5,050 Gold nhưng thuế phí 100 Gold làm quỹ còn 4,950 Gold (<5000):
        Phải từ chối hoặc điều chỉnh để không bao giờ vi phạm INV-EXCHANGE-GOLD-TAX.
        """
        cfg = ExchangePortfolioConfig(min_gold_reserve=5000)
        optimizer = CurrencyPortfolioOptimizer(config=cfg)

        wallet = {"Exalted Orb": 15, "Chaos Orb": 10}
        market_prices = {"Exalted Orb": 10.0, "Divine Orb": 150.0}

        result = optimizer.optimize_portfolio(wallet, market_prices, current_gold=5050)
        # Nếu được đề xuất, tổng thuế phí sàn (estimated_gold_tax) cộng với 5000 Gold không được vượt quá current_gold
        for order in result.orders:
            remaining_gold_after_order = 5050 - order.estimated_gold_tax
            assert remaining_gold_after_order >= 5000, (
                f"Lệnh {order} vi phạm INV-EXCHANGE-GOLD-TAX: Vàng sau lệnh={remaining_gold_after_order} < 5000!"
            )

    def test_exchange_orchestrator_aborts_cleanly_on_gold_exhaustion(self):
        """ExchangeOrchestrator phải ngắt quy trình và không gửi phím chuột khi vàng <= 5000."""
        cfg = ExchangePortfolioConfig(min_gold_reserve=5000)
        orchestrator = CurrencyExchangeOrchestrator(config=cfg)

        wallet = {"Exalted Orb": 20, "Chaos Orb": 50}
        market_prices = {"Exalted Orb": 10.0, "Divine Orb": 150.0}

        report = orchestrator.execute_session(
            wallet=wallet,
            market_prices_in_chaos=market_prices,
            current_gold=4500,  # Vàng dưới ngưỡng
        )
        assert report.orders_planned == 0
        assert report.orders_executed == 0
        assert report.status == "REJECTED"
        assert "ngưỡng an toàn" in (report.error or "") or "Quỹ vàng" in (report.error or "")


# ============================================================================
# SCENARIO 4: MASTER INVARIANT MATRIX RESILIENCE AUDIT (18 INVARIANTS)
# ============================================================================

class TestEighteenInvariantsResilienceAudit:
    """Kiểm tra sức bền và tính bất khả xâm phạm của 18 bất biến kiến trúc khi gặp lỗi ngoại cảnh."""

    def test_ci_hp_one_with_es_drop_does_not_drink_life_flask(self):
        """[INV-CI-01]: Nhân vật Chaos Inoculation (HP 1/1) khi tụt ES tuyệt đối không uống Life Flask."""
        player_ci = {
            "current_hp": 1,
            "max_hp": 1,
            "current_es": 800,
            "max_es": 2000,  # ES tụt còn 40%
            "current_mana": 100,
            "max_mana": 200,
        }
        # InvariantSentinel không được báo lỗi sinh tồn giả mạo với build CI
        anomalies = InvariantSentinel.check_vitals_invariants(player_ci)
        assert anomalies == [], "Build CI 1/1 HP là hợp lệ và không được coi là lỗi"

    def test_bogus_marker_778_rejected_immediately(self):
        """[INV-VIT-01]: Bắt struct quái giả mạo 778/778 ngay lập tức."""
        bogus_player = {
            "current_hp": 778,
            "max_hp": 778,
            "current_es": 0,
            "max_es": 0,
        }
        anomalies = InvariantSentinel.check_vitals_invariants(bogus_player)
        from src.agentic_engine.invariant_sentinel import ERR_BOGUS_VITAL_STRUCT_778
        assert ERR_BOGUS_VITAL_STRUCT_778 in anomalies

    def test_ui_scene_detector_prevents_world_map_interaction_in_hideout(self):
        """[INV-KEY-NO-U-FOR-MAP-DEVICE]: Nhận diện UI Scene không mở bản đồ thế giới tại Hideout."""
        from src.assistant_tool.autonomous_lifecycle import AutonomousLifecycleManager

        assert AutonomousLifecycleManager.is_ui_scene("atlas") is True
        assert AutonomousLifecycleManager.is_ui_scene("world map") is True
        assert AutonomousLifecycleManager.is_ui_scene("act 1") is True
        # Khu vực chơi thật
        assert AutonomousLifecycleManager.is_ui_scene("Shoreline Hideout") is False
        assert AutonomousLifecycleManager.is_ui_scene("Sandswept Marsh") is False

    def test_all_18_invariants_taxonomy_coverage(self):
        """Xác minh hệ thống InvariantSentinel bao quát toàn bộ danh mục mã lỗi chuẩn hóa."""
        assert len(ALL_INVARIANT_CODES) == 14
        # Kiểm tra tính hiện diện của các mã lỗi nòng cốt
        assert ERR_WATCHDOG_HEARTBEAT_TIMEOUT in ALL_INVARIANT_CODES
        assert ERR_CORE_DISCONNECTED in ALL_INVARIANT_CODES
