"""AutoPOE2 - Unit Tests for MapClearEvaluator V2 (Doc 69 §3.4 / Stage 5 In-Map).
================================================================================
Kiểm thử toàn diện bộ thẩm định hoàn tất bản đồ chuẩn thương mại V2:
- Bất biến INV-MAP-LEGITIMATE-CLEAR-V2:
  Bản đồ CHỈ ĐƯỢC CÔNG NHẬN HOÀN TẤT khi thỏa mãn ĐỒNG THỜI cả 3 trụ cột:
    1. Trụ cột 1: Thời gian ở trong map >= min_duration_sec (>= 90s).
    2. Trụ cột 2: Quãng đường tuần tra thực tế >= min_traverse_units (>= 1200u).
    3. Trụ cột 3: Mục tiêu cốt lõi (Mở sương Minimap >= 80% HOẶC Boss Slain).
- Triệt tiêu 100% bug "đánh 1 con quái xong kết luận map clear" hoặc "vào ngõ cụt yên ắng 25s thoát map sớm".
"""

import pytest
from src.assistant_tool.navigation.map_clear_evaluator import MapClearEvaluator


class TestMapClearEvaluatorV2Invariants:
    """Kiểm thử các bất biến của MapClearEvaluator V2 (INV-MAP-LEGITIMATE-CLEAR-V2)."""

    def test_rejects_clear_before_min_duration_regardless_of_monsters(self):
        """Trụ cột 1: Dù chỉ còn 0 quái vật, nếu elapsed < 90s thì TUYỆT ĐỐI KHÔNG hoàn tất map."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Cập nhật có 1 con quái hoặc 0 con quái sau 15 giây
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=1, coverage_ratio=0.10)
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=15.0)
        assert is_cleared is False
        assert reason == ""

        # Sau 60 giây vẫn chưa đạt 90s
        evaluator.update_telemetry(pos_x=200.0, pos_y=200.0, monsters_near=0, coverage_ratio=0.30)
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=60.0)
        assert is_cleared is False
        assert reason == ""

        # Sau 89 giây vẫn bị từ chối
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=89.9)
        assert is_cleared is False
        assert reason == ""

    def test_single_monster_killed_at_entrance_strictly_rejects_clear(self):
        """Triệt tiêu lỗi gốc: Đánh 1 con quái ở cửa map, sau đó đứng yên hoặc đi nhẹ -> BẮT BUỘC trả về False."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # 1. Gặp 1 con quái ở lối vào map (pos 100, 100)
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=1, coverage_ratio=0.05, now_sec=5.0)
        assert evaluator.packs_encountered == 1
        assert evaluator.quiet_start_time is None

        # 2. Tiêu diệt con quái đó (monsters_near = 0), sương mở 8%
        evaluator.update_telemetry(pos_x=120.0, pos_y=100.0, monsters_near=0, coverage_ratio=0.08, now_sec=10.0)

        # 3. Chờ đến 95s (đã quá 90s), nhưng quãng đường chỉ ~20u và sương chỉ 8%
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=95.0, now_sec=95.0)
        assert is_cleared is False
        assert reason == ""

        # 4. Thậm chí chờ đến 240s (hết thời gian tối đa) -> evaluate_clear vẫn từ chối hoàn tất map giả tạo
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=240.0, now_sec=240.0)
        assert is_cleared is False
        assert reason == ""

    def test_quiet_area_in_dead_end_cannot_fake_clear_without_boss_or_coverage(self):
        """Lỗ hổng quiet_start_time: Đi vào vùng vắng quái (dead-end) 25s nhưng chưa đủ 80% sương và chưa diệt Boss -> KHÔNG ĐƯỢC CLEAR."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Di chuyển tích lũy qua lại trong ngõ cụt để đạt 1300u
        for i in range(7):
            evaluator.update_telemetry(pos_x=100.0 + (i * 200.0), pos_y=100.0, monsters_near=2)
        assert evaluator.total_distance_traversed >= 1200.0

        # Đi vào góc cụt không có quái (monsters_near=0) lúc t=100s, sương mới đạt 25%
        evaluator.update_telemetry(pos_x=1400.0, pos_y=100.0, monsters_near=0, coverage_ratio=0.25, now_sec=100.0)

        # Tại t=130s (đã yên ắng 30s > 25s), elapsed=130s (>90s), dist=1300u (>1200u)
        # NHƯNG sương mới 25% (< 80%) và Boss chưa chết -> BẮT BUỘC trả về False!
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=130.0, now_sec=130.0)
        assert is_cleared is False
        assert reason == ""

    def test_boss_slain_alone_without_traverse_distance_rejects_clear(self):
        """Trụ cột 2: Boss bị hạ gục sớm nhưng chưa tuần tra đủ 1200u -> BẮT BUỘC từ chối clear sớm."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Di chuyển ngắn 300u và hạ gục Boss lúc t=95s
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=5)
        evaluator.update_telemetry(pos_x=400.0, pos_y=100.0, monsters_near=1, boss_slain=True)
        assert evaluator.total_distance_traversed < 1200.0

        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=95.0)
        assert is_cleared is False
        assert reason == ""

    def test_coverage_80_percent_alone_without_traverse_distance_rejects_clear(self):
        """Trụ cột 2: Tỷ lệ mở sương đạt 85% nhưng quãng đường tuần tra < 1200u -> Từ chối hoàn tất."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Mới di chuyển 500u (3 bước hợp lệ <= 250u)
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=5)
        evaluator.update_telemetry(pos_x=300.0, pos_y=100.0, monsters_near=4)  # +200u
        evaluator.update_telemetry(pos_x=500.0, pos_y=100.0, monsters_near=3)  # +200u
        evaluator.update_telemetry(pos_x=600.0, pos_y=100.0, monsters_near=2, coverage_ratio=0.85)  # +100u
        assert evaluator.total_distance_traversed == 500.0

        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=100.0)
        assert is_cleared is False
        assert reason == ""

    def test_core_lifecycle_state_cannot_bypass_three_pillars(self):
        """Trụ cột 3: Core SHM State 9 (MAP_COMPLETED_PORTAL_OUT) không được phép bypass 3 trụ cột."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Core báo State 9 khi mới chạy 400u và sương mở 20%
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=5)
        evaluator.update_telemetry(pos_x=300.0, pos_y=100.0, monsters_near=2)  # +200u
        evaluator.update_telemetry(pos_x=500.0, pos_y=100.0, monsters_near=0, coverage_ratio=0.20)  # +200u
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=100.0, core_lifecycle_state=9)
        assert is_cleared is False
        assert reason == ""

    def test_all_three_pillars_met_with_boss_slain_triggers_clear(self):
        """Hội tụ 3 trụ cột: Elapsed >= 90s + Distance >= 1200u + Boss Slain -> Công nhận hoàn tất map."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Di chuyển tích lũy qua các cụm tọa độ để đạt >= 1200u
        positions = [
            (100.0, 100.0), (350.0, 100.0), (600.0, 100.0),
            (850.0, 100.0), (1100.0, 100.0), (1350.0, 100.0),
        ]
        for px, py in positions:
            evaluator.update_telemetry(pos_x=px, pos_y=py, monsters_near=3)

        assert evaluator.total_distance_traversed >= 1200.0

        # Hạ gục Boss tại t=95s
        evaluator.update_telemetry(pos_x=1400.0, pos_y=100.0, monsters_near=0, boss_slain=True, coverage_ratio=0.72)
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=95.0, now_sec=95.0)

        assert is_cleared is True
        assert "Map Boss đã bị tiêu diệt" in reason
        assert ">= 1200u" in reason

    def test_all_three_pillars_met_with_coverage_80_percent_triggers_clear(self):
        """Hội tụ 3 trụ cột: Elapsed >= 90s + Distance >= 1200u + Minimap Coverage >= 80% -> Hoàn tất map."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Di chuyển tích lũy qua các điểm đạt >= 1200u
        positions = [
            (100.0, 100.0), (350.0, 100.0), (600.0, 100.0),
            (850.0, 100.0), (1100.0, 100.0), (1350.0, 100.0),
        ]
        for px, py in positions:
            evaluator.update_telemetry(pos_x=px, pos_y=py, monsters_near=2)

        # Mở sương đạt 83.5% (>= 80%) sau 110s
        evaluator.update_telemetry(pos_x=1400.0, pos_y=100.0, monsters_near=1, coverage_ratio=0.835)
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=110.0, now_sec=110.0)

        assert is_cleared is True
        assert "Minimap đã mở sương đạt 83.5%" in reason
        assert ">= 80%" in reason

    def test_all_three_pillars_met_with_quiet_area_confirmation(self):
        """Hội tụ 3 trụ cột kèm xác nhận khu vực hoàn toàn yên ắng (quiet area >= 25s)."""
        evaluator = MapClearEvaluator(min_duration_sec=90.0, target_coverage=0.80, min_traverse_units=1200.0)

        # Di chuyển đủ 1350u
        for step in range(7):
            evaluator.update_telemetry(pos_x=100.0 + step * 210.0, pos_y=100.0, monsters_near=3)
        assert evaluator.total_distance_traversed >= 1200.0

        # Mở sương 85% và bắt đầu yên ắng lúc t=100s
        evaluator.update_telemetry(pos_x=1500.0, pos_y=100.0, monsters_near=0, coverage_ratio=0.85, now_sec=100.0)

        # Đến t=128s (yên ắng 28s >= 25s) và elapsed=128s
        is_cleared, reason = evaluator.evaluate_clear(elapsed_sec=128.0, now_sec=128.0)
        assert is_cleared is True
        assert "hoàn toàn yên ắng" in reason
        assert "85.0%" in reason

    def test_single_monster_encounter_resets_quiet_start_time(self):
        """Xác minh quái vật đơn lẻ (monsters_near=1) phá vỡ trạng thái quiet và reset quiet_start_time về None."""
        evaluator = MapClearEvaluator()

        # Bắt đầu yên ắng lúc t=50s
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=0, now_sec=50.0)
        assert evaluator.quiet_start_time == 50.0

        # Gặp 1 con quái lẻ lúc t=60s -> quiet_start_time BẮT BUỘC bị reset về None
        evaluator.update_telemetry(pos_x=150.0, pos_y=100.0, monsters_near=1, now_sec=60.0)
        assert evaluator.quiet_start_time is None
        assert evaluator.packs_encountered == 1

    def test_reset_clears_all_internal_state(self):
        """Xác minh reset() xóa sạch toàn bộ bộ đếm trắc viễn cho map mới."""
        evaluator = MapClearEvaluator()
        evaluator.update_telemetry(pos_x=100.0, pos_y=100.0, monsters_near=5)
        evaluator.update_telemetry(pos_x=250.0, pos_y=250.0, monsters_near=0, boss_slain=True, coverage_ratio=0.90, now_sec=50.0)

        assert evaluator.total_distance_traversed > 0.0
        assert evaluator.boss_slain is True
        assert evaluator.last_coverage_ratio == 0.90

        evaluator.reset()
        assert evaluator.total_distance_traversed == 0.0
        assert evaluator.last_pos is None
        assert evaluator.last_coverage_ratio == 0.0
        assert evaluator.boss_slain is False
        assert evaluator.packs_encountered == 0
        assert evaluator.quiet_start_time is None
