"""
Unit Test cho Optical Health Globe Sensor (OpticalHPSensor)
Kiểm tra tính toán ROI theo tỉ lệ phân giải màn hình, logic phân tích màu đỏ,
kỹ thuật Multi-slit sampling loại trừ text số máu, giải phóng an toàn GDI handles,
tốc độ thực thi < 2ms và tích hợp với CoreController / ControlCenterApp.
"""

import os
import sys
import time
from unittest.mock import MagicMock, patch

from PIL import Image, ImageDraw

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from src.assistant_tool.core_controller import CoreController
from src.assistant_tool.optical_hp_sensor import OpticalHPSensor


def test_optical_sensor_initialization():
    """Kiểm tra giá trị khởi tạo của OpticalHPSensor."""
    sensor_default = OpticalHPSensor()
    assert sensor_default.expected_max_hp == 0

    sensor = OpticalHPSensor(expected_max_hp=189)
    assert sensor.expected_max_hp == 189
    assert sensor.top_margin_ratio == 0.10
    assert sensor.bottom_margin_ratio == 0.90
    assert sensor.slit_offset == 15
    assert sensor.last_ratio == 0.0


def test_is_red_pixel():
    """Kiểm tra điều kiện nhận diện pixel màu đỏ của chất lỏng máu: R > 110, R > 1.5*G, R > 1.5*B."""
    # 1. Đỏ đậm chuẩn
    assert OpticalHPSensor.is_red_pixel(220, 30, 30) is True
    # 2. Đỏ vừa đủ ngưỡng
    assert OpticalHPSensor.is_red_pixel(120, 70, 70) is True
    # 3. Quá tối (R <= 110)
    assert OpticalHPSensor.is_red_pixel(100, 20, 20) is False
    # 4. Chữ trắng (White text)
    assert OpticalHPSensor.is_red_pixel(255, 255, 255) is False
    # 5. Viền vàng/kim loại (Yellow/Gold)
    assert OpticalHPSensor.is_red_pixel(220, 200, 50) is False
    # 6. Màu xanh mana (Blue/Cyan)
    assert OpticalHPSensor.is_red_pixel(30, 100, 220) is False
    # 7. Màu nền tối (Dark background)
    assert OpticalHPSensor.is_red_pixel(25, 25, 25) is False


def test_get_globe_roi_scaling():
    """Kiểm tra tính toán tọa độ và kích thước ROI theo tỉ lệ màn hình (1080p, 1440p, 4K)."""
    sensor = OpticalHPSensor()

    # 1. Màn hình 1080p chuẩn (1920x1080)
    with patch("win32gui.GetClientRect", return_value=(0, 0, 1920, 1080)):
        rx, ry, rw, rh = sensor.get_globe_roi(12345)
        assert rw == 150
        assert rh == 150
        assert rx == 15
        assert ry == 1080 - 170  # 910

    # 2. Màn hình 1440p (2560x1440) -> scale ~ 1.3333
    with patch("win32gui.GetClientRect", return_value=(0, 0, 2560, 1440)):
        rx, ry, rw, rh = sensor.get_globe_roi(12345)
        scale_1440 = 1440 / 1080.0
        assert rw == int(150 * scale_1440)  # 200
        assert rh == int(150 * scale_1440)  # 200
        assert rx == int(15 * scale_1440)   # 20
        assert ry == 1440 - int(170 * scale_1440)  # 1214

    # 3. Màn hình 4K (3840x2160) -> scale = 2.0
    with patch("win32gui.GetClientRect", return_value=(0, 0, 3840, 2160)):
        rx, ry, rw, rh = sensor.get_globe_roi(12345)
        assert rw == 300
        assert rh == 300
        assert rx == 30
        assert ry == 2160 - 340  # 1820

    # 4. Fallback khi lỗi GetClientRect
    with patch("win32gui.GetClientRect", side_effect=Exception("Failed")):
        rx, ry, rw, rh = sensor.get_globe_roi(12345)
        assert rw == 150
        assert rh == 150
        assert rx == 15
        assert ry == 910


def test_multi_slit_sampling_and_text_rejection():
    """
    Tạo ảnh mô phỏng quả cầu máu ở các mức 100%, 50%, 0% và trường hợp chữ số máu trắng '1234/1234'
    đè lên tâm quả cầu để chứng minh Multi-slit sampling không bị nhiễu.
    """
    sensor = OpticalHPSensor(expected_max_hp=2000)
    w, h = 150, 150
    y_start = int(h * 0.10)  # 15
    y_end = int(h * 0.90)    # 135
    total_span = y_end - y_start + 1

    # 1. 100% Full HP
    img_100 = Image.new("RGB", (w, h), (15, 15, 20))
    draw_100 = ImageDraw.Draw(img_100)
    draw_100.rectangle([0, y_start, w, y_end], fill=(210, 30, 30))
    cur, mhp, ratio = sensor.analyze_pil_image(img_100, expected_max_hp=2000)
    assert ratio == 1.0
    assert cur == 2000
    assert mhp == 2000

    # 2. 0% HP (Rỗng)
    img_0 = Image.new("RGB", (w, h), (15, 15, 20))
    cur, mhp, ratio = sensor.analyze_pil_image(img_0, expected_max_hp=2000)
    assert ratio == 0.0
    assert cur == 0
    assert mhp == 2000

    # 3. 50% HP Không có chữ số
    img_50_clean = Image.new("RGB", (w, h), (15, 15, 20))
    draw_50 = ImageDraw.Draw(img_50_clean)
    y_mid = y_end - int(round(total_span * 0.50)) + 1
    draw_50.rectangle([0, y_mid, w, y_end], fill=(210, 30, 30))
    cur_clean, _, ratio_clean = sensor.analyze_pil_image(img_50_clean, expected_max_hp=2000)
    assert abs(ratio_clean - 0.50) <= 0.02
    assert abs(cur_clean - 1000) <= 40

    # 4. 50% HP CÓ CHỮ SỐ TRẮNG ('1234/1234') ĐÈ LÊN TRỤC GIỮA
    img_50_text = Image.new("RGB", (w, h), (15, 15, 20))
    draw_text = ImageDraw.Draw(img_50_text)
    draw_text.rectangle([0, y_mid, w, y_end], fill=(210, 30, 30))
    # Vẽ ô chữ trắng đè lên trục giữa X=75 (che slit X=75 từ Y=90 đến Y=110)
    draw_text.rectangle([68, 90, 82, 110], fill=(255, 255, 255))

    cur_text, _, ratio_text = sensor.analyze_pil_image(img_50_text, expected_max_hp=2000)
    # Tỉ lệ sau khi loại trừ text phải tương đương nhau nhờ multi-slit
    assert abs(ratio_text - ratio_clean) <= 0.02
    assert abs(cur_text - cur_clean) <= 40


def test_performance_under_2ms():
    """Kiểm tra thời gian thực thi của thuật toán phân tích pixel: bắt buộc < 2.0 ms."""
    sensor = OpticalHPSensor(expected_max_hp=1000)
    # Chuẩn bị buffer BGRA 150x150
    w, h = 150, 150
    raw_bytes = bytes([120, 40, 210, 0] * (w * h))

    # Chạy khởi động JIT/cache
    sensor.analyze_pixels(w, h, raw_bytes)

    # Đo thời gian 20 lần chạy
    times = []
    for _ in range(20):
        t0 = time.perf_counter()
        sensor.analyze_pixels(w, h, raw_bytes)
        times.append((time.perf_counter() - t0) * 1000.0)

    avg_time = sum(times) / len(times)
    min_time = min(times)
    print(f"  -> Optical Sensor Benchmark: avg={avg_time:.3f}ms, min={min_time:.3f}ms")
    assert avg_time < 2.0, f"Analysis took {avg_time:.3f}ms, which exceeds the 2.0ms limit!"


def test_gdi_handles_cleanup_safety():
    """
    Kiểm tra bảo đảm an toàn tài nguyên GDI:
    Tất cả các handle DeleteObject, DeleteDC, ReleaseDC bắt buộc phải được gọi
    ngay cả khi quá trình BitBlt xảy ra Exception.
    """
    sensor = OpticalHPSensor()

    mock_win32gui = MagicMock()
    mock_win32ui = MagicMock()

    mock_hwnd_dc = 0x1234
    mock_mfc_dc = MagicMock()
    mock_save_dc = MagicMock()
    mock_bitmap = MagicMock()
    mock_bitmap.GetHandle.return_value = 0x5678

    mock_win32gui.GetDC.return_value = mock_hwnd_dc
    mock_win32ui.CreateDCFromHandle.return_value = mock_mfc_dc
    mock_mfc_dc.CreateCompatibleDC.return_value = mock_save_dc
    mock_win32ui.CreateBitmap.return_value = mock_bitmap

    # Làm cho BitBlt ném ngoại lệ để kiểm tra khối finally
    mock_save_dc.BitBlt.side_effect = RuntimeError("GDI BitBlt Failure Simulation")

    with patch("src.assistant_tool.optical_hp_sensor.win32gui", mock_win32gui), \
         patch("src.assistant_tool.optical_hp_sensor.win32ui", mock_win32ui):
        res = sensor.capture_roi_bytes(hwnd=9999, rx=15, ry=910, rw=150, rh=150)
        assert res is None

        # Xác minh toàn bộ GDI handles được giải phóng an toàn
        mock_win32gui.DeleteObject.assert_called_once_with(0x5678)
        mock_save_dc.DeleteDC.assert_called_once()
        mock_mfc_dc.DeleteDC.assert_called_once()
        mock_win32gui.ReleaseDC.assert_called_once_with(9999, mock_hwnd_dc)


def test_core_controller_and_gui_max_hp_flags():
    """
    Kiểm tra truyền cờ --max-hp <N> trong CoreController với cả max_hp và expected_max_hp.
    """
    cc = CoreController()

    # 1. Truyền expected_max_hp
    cmd1 = cc.build_cmd({"expected_max_hp": 1500})
    assert "--max-hp" in cmd1
    idx1 = cmd1.index("--max-hp")
    assert cmd1[idx1 + 1] == "1500"

    # 2. Truyền max_hp
    cmd2 = cc.build_cmd({"max_hp": 2200})
    assert "--max-hp" in cmd2
    idx2 = cmd2.index("--max-hp")
    assert cmd2[idx2 + 1] == "2200"

    # 3. Khi không có max_hp (bằng 0 và tắt optical live scan)
    cmd3 = cc.build_cmd({"max_hp": 0, "expected_max_hp": 0, "optical_inject_max_hp": False})
    assert "--max-hp" not in cmd3


def test_control_center_optical_sensor_gui_integration():
    """Kiểm tra tích hợp OpticalHPSensor và badge vào ControlCenterApp."""
    from src.assistant_tool.control_center import ControlCenterApp

    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            app.update()

            # 1. Kiểm tra thuộc tính optical_sensor
            assert hasattr(app, "optical_sensor")
            assert app.optical_sensor is not None

            # 2. Kiểm tra ô nhập liệu expected_max_hp
            assert hasattr(app, "entry_expected_max_hp")
            assert hasattr(app, "entry_max_hp")
            assert app.entry_expected_max_hp == app.entry_max_hp

            app.entry_expected_max_hp.delete(0, "end")
            app.entry_expected_max_hp.insert(0, "1850")

            cfg = app._collect_config()
            assert cfg.get("expected_max_hp") == 1850
            assert cfg.get("max_hp") == 1850

            # 3. Kiểm tra badge HP Sensor
            assert hasattr(app, "lbl_hp_sensor_badge")
            badge_text = app.lbl_hp_sensor_badge.cget("text")
            assert "HP Sensor:" in badge_text

            # 4. Khi nhận Telemetry Snapshot -> chuyển sang MEMORY RPM
            from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry
            snap = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=150, max_hp=180, current_mana=100, max_mana=100)
            )
            app._on_telemetry_snapshot(snap)
            app.update()
            badge = app.lbl_hp_sensor_badge.cget("text")
            assert "MEMORY RPM" in badge or "LỆCH" in badge
        finally:
            app.on_closing()


def test_desktop_attachment_and_printwindow_fallback():
    """
    Kiểm tra gắn Desktop tương tác và cơ chế fallback PrintWindow với PW_RENDERFULLCONTENT
    khi BitBlt trả về ảnh đen hoặc khi ứng dụng chạy ngầm.
    """
    from src.assistant_tool.optical_hp_sensor import attach_to_default_desktop

    # 1. Kiểm tra hàm attach_to_default_desktop không ném ngoại lệ
    desk = attach_to_default_desktop()
    assert desk is not None or desk is None  # Safe execution

    sensor = OpticalHPSensor(expected_max_hp=491)
    assert sensor.PW_RENDERFULLCONTENT == 2
    assert sensor.PW_CLIENTONLY == 1

    # 2. Kiểm tra is_all_black
    black_buf = bytes([0] * (150 * 150 * 4))
    assert OpticalHPSensor.is_all_black(black_buf) is True
    assert OpticalHPSensor.is_all_black(None) is True
    non_black_buf = bytearray([0] * (150 * 150 * 4))
    non_black_buf[64] = 200
    non_black_buf[100] = 200
    assert OpticalHPSensor.is_all_black(bytes(non_black_buf)) is False

    # 3. Kiểm tra read_hp fallback PrintWindow khi BitBlt trả về None hoặc black
    mock_pw_bytes = bytes([120, 30, 220, 0] * (150 * 150))
    with patch.object(sensor, "find_poe2_window", return_value=12345), \
         patch.object(sensor, "get_globe_roi", return_value=(15, 910, 150, 150)), \
         patch.object(sensor, "capture_roi_bytes", return_value=black_buf), \
         patch.object(sensor, "capture_roi_printwindow", return_value=mock_pw_bytes) as mock_pw:
        cur, mhp, ratio = sensor.read_hp(hwnd=12345, expected_max_hp=491)
        mock_pw.assert_called_once_with(12345, 15, 910, 150, 150)
        assert mhp == 491
        assert cur > 0


def test_control_center_shm_unverified_mismatch_warning():
    """
    Kiểm tra xử lý các trường hợp số rác từ Shared Memory (2/250, 125/0, 2/256):
    - Mặc định Auto-Detect: Zero User Input, max_hp được game cập nhật tự động.
      Chỉ khi max_hp <= 0 mới đánh dấu UNVERIFIED. Khi max_hp > 0 thì VERIFIED (AUTO-DETECTED).
    - Khi người dùng chủ động gõ số ghi đè (Manual Override): Nếu max_hp != expected_max_hp -> UNVERIFIED.
    - CoreController không truyền --max-hp ở chế độ Auto, chỉ truyền khi có Manual Override > 0.
    """
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry

    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            app.update()

            # 1. Chế độ Mặc định Auto-Detect (Zero User Input): entry_max_hp là "Auto"
            cfg_default = app._collect_config()
            assert cfg_default.get("expected_max_hp") == 0
            assert cfg_default.get("max_hp") == 0

            # 1.1 Khi SHM gửi dữ liệu hợp lệ trong chế độ Auto (ví dụ 450/491) -> AUTO-DETECTED, VERIFIED
            snap_auto_valid = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=450, max_hp=491, current_mana=100, max_mana=100)
            )
            app._on_telemetry_snapshot(snap_auto_valid)
            app.update()

            hp_text_auto = app.lbl_hp.cget("text")
            badge_text_auto = app.lbl_hp_sensor_badge.cget("text")
            assert "450 / 491" in hp_text_auto
            assert "UNVERIFIED" not in hp_text_auto
            assert "AUTO-DETECTED" in badge_text_auto or "LỆCH" in badge_text_auto
            assert "MEMORY RPM" in badge_text_auto or "LỆCH" in badge_text_auto

            # 1.2 Khi SHM gửi dữ liệu rác max_hp <= 0 trong chế độ Auto (ví dụ 125/0) -> UNVERIFIED
            snap_garbage_zero = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=125, max_hp=0, current_mana=50, max_mana=100)
            )
            app._on_telemetry_snapshot(snap_garbage_zero)
            app.update()

            hp_text_zero = app.lbl_hp.cget("text")
            badge_text_zero = app.lbl_hp_sensor_badge.cget("text")
            assert "125 / 0" in hp_text_zero
            assert "UNVERIFIED" in hp_text_zero
            assert "UNVERIFIED" in badge_text_zero

            # 2. Chế độ Ghi đè Thủ công (Manual Override): Đặt expected_max_hp là 491
            app.entry_max_hp.delete(0, "end")
            app.entry_max_hp.insert(0, "491")
            app._on_expected_hp_changed()

            cfg_manual = app._collect_config()
            assert cfg_manual.get("expected_max_hp") == 491
            assert cfg_manual.get("max_hp") == 491

            # 2.1 Trường hợp SHM gửi số rác max_hp = 250 (mismatched 250 != 491) -> UNVERIFIED
            snap_garbage_250 = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=2, max_hp=250, current_mana=50, max_mana=100)
            )
            app._on_telemetry_snapshot(snap_garbage_250)
            app.update()

            hp_text_250 = app.lbl_hp.cget("text")
            badge_text_250 = app.lbl_hp_sensor_badge.cget("text")
            assert "2 / 250" in hp_text_250
            assert "UNVERIFIED" in hp_text_250 or "LỆCH" in hp_text_250
            assert "UNVERIFIED" in badge_text_250 or "LỆCH" in badge_text_250

            # 2.2 Trường hợp SHM gửi max_hp chuẩn khớp với cấu hình thủ công (491 == 491) -> VERIFIED
            snap_verified = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=450, max_hp=491, current_mana=100, max_mana=100)
            )
            app._on_telemetry_snapshot(snap_verified)
            app.update()

            hp_text_ok = app.lbl_hp.cget("text")
            badge_text_ok = app.lbl_hp_sensor_badge.cget("text")
            assert "450 / 491" in hp_text_ok
            assert "UNVERIFIED" not in hp_text_ok
            assert "VERIFIED" in badge_text_ok or "LỆCH" in badge_text_ok

            # 3. Kiểm tra CoreController: Khi tắt optical_inject_max_hp -> không truyền cờ --max-hp
            cc = CoreController()
            cmd_auto = cc.build_cmd({"optical_inject_max_hp": False})
            assert "--max-hp" not in cmd_auto

            cmd_auto_explicit = cc.build_cmd({"expected_max_hp": "Auto", "optical_inject_max_hp": False})
            assert "--max-hp" not in cmd_auto_explicit

            cmd_override = cc.build_cmd({"expected_max_hp": 491})
            assert "--max-hp" in cmd_override
            idx = cmd_override.index("--max-hp")
            assert cmd_override[idx + 1] == "491"
        finally:
            app.on_closing()


def test_optical_vitals_text_parsing_and_estimation():
    """
    Kiểm tra nhận diện text Life: 495/503, Shield: 105/200, Mana: 344/344
    và hàm ước tính sinh tồn theo cấp độ nhân vật trong POE2.
    """
    sensor = OpticalHPSensor()

    # 1. Phân tích chuỗi text Life, Shield, Mana
    raw_text = "Life: 495/503\nShield: 105/200\nMana: 344/344"
    hp, es, mana = OpticalHPSensor.parse_vitals_text(raw_text)

    assert hp == (495, 503)
    assert es == (105, 200)
    assert mana == (344, 344)

    # 2. detect_vitals_from_screen với mock_text
    vitals = sensor.detect_vitals_from_screen(mock_text=raw_text)
    assert vitals.is_detected is True
    assert vitals.current_hp == 495
    assert vitals.max_hp == 503
    assert vitals.current_shield == 105
    assert vitals.max_shield == 200
    assert vitals.current_mana == 344
    assert vitals.max_mana == 344
    assert abs(vitals.hp_ratio - (495 / 503.0)) < 0.001

    # 3. Ước tính cấp độ (Level 18 trong POE2 chiến dịch)
    est_hp, est_mana = OpticalHPSensor.estimate_vitals_from_level(18)
    assert 400 <= est_hp <= 600
    assert 250 <= est_mana <= 450

    # 4. Kiểm tra pixel màu xanh Mana
    assert OpticalHPSensor.is_blue_pixel(20, 40, 200) is True
    assert OpticalHPSensor.is_blue_pixel(200, 30, 30) is False

    # 5. Kiểm tra pixel chữ số sáng (white text)
    assert OpticalHPSensor.is_text_pixel(220, 220, 220) is True
    assert OpticalHPSensor.is_text_pixel(200, 20, 20) is False


def test_check_memory_discrepancy_778_vs_503():
    """
    TÁI HIỆN VÀ KHẮC PHỤC LỖI THỰC TẾ TRÊN MÀN HÌNH CỦA USER:
    C++ Core bắt nhầm ô nhớ rác 778/778 trong khi màn hình là 495/503.
    Hàm check_memory_discrepancy phải phát hiện chính xác sự lệch lạc này!
    """
    sensor = OpticalHPSensor()

    # 1. Trường hợp lỗi thực tế của user: Memory 778/778 vs Màn hình 495/503
    has_err, msg = sensor.check_memory_discrepancy(
        memory_cur=778,
        memory_max=778,
        optical_cur=495,
        optical_max=503,
        optical_ratio=495 / 503.0
    )
    assert has_err is True
    assert "778" in msg and "503" in msg

    # 2. Trường hợp lệch tỉ lệ: Memory 778/778 (100%), màn hình chất lỏng thực tế 50%
    has_err2, msg2 = sensor.check_memory_discrepancy(
        memory_cur=778,
        memory_max=778,
        optical_cur=250,
        optical_max=0,  # Chưa đọc được số text, chỉ có fluid ratio
        optical_ratio=0.50
    )
    assert has_err2 is True
    assert "Lệch tỉ lệ" in msg2

    # 3. Trường hợp chuẩn đã hiệu chuẩn (RAM 503 khớp Màn hình 503)
    has_err3, _ = sensor.check_memory_discrepancy(
        memory_cur=495,
        memory_max=503,
        optical_cur=495,
        optical_max=503,
        optical_ratio=495 / 503.0
    )
    assert has_err3 is False


def test_core_controller_auto_detect_max_hp_injection():
    """
    Kiểm tra CoreController: khi ở chế độ Auto, tự động truyền --max-hp 503
    khi nhận được detected_max_hp từ cấu hình hoặc Optical Sensor.
    """
    cc = CoreController()

    # 1. Khi ở chế độ Auto nhưng có detected_max_hp = 503
    cmd_auto_detected = cc.build_cmd({
        "expected_max_hp": "Auto",
        "detected_max_hp": 503,
        "detected_max_mana": 344
    })
    assert "--max-hp" in cmd_auto_detected
    idx = cmd_auto_detected.index("--max-hp")
    assert cmd_auto_detected[idx + 1] == "503"

    assert "--max-mana" in cmd_auto_detected
    idx_m = cmd_auto_detected.index("--max-mana")
    assert cmd_auto_detected[idx_m + 1] == "344"

    # 2. Khi ở chế độ Auto và OpticalHPSensor phát hiện màn hình
    from src.assistant_tool.optical_hp_sensor import OpticalVitals
    mock_vitals = OpticalVitals(current_hp=495, max_hp=503, current_mana=344, max_mana=344, is_detected=True)
    with patch("src.assistant_tool.core_controller.OpticalHPSensor") as mock_sensor_cls:
        mock_inst = MagicMock()
        mock_inst.detect_vitals_from_screen.return_value = mock_vitals
        mock_sensor_cls.return_value = mock_inst

        cmd_live_detect = cc.build_cmd({"expected_max_hp": "Auto", "optical_inject_max_hp": True})
        assert "--max-hp" in cmd_live_detect
        idx2 = cmd_live_detect.index("--max-hp")
        assert cmd_live_detect[idx2 + 1] == "503"


def test_control_center_quick_calibrate_and_discrepancy_alert():
    """
    Kiểm tra ControlCenterApp:
    1. Có nút bấm '⚡ Quét Vitals'.
    2. Click quét Vitals cập nhật ô entry_max_hp thành 503 và gọi send_recalibrate_vitals.
    3. Khi RAM gửi số rác 778/778 trong khi optical vitals là 503 -> hiển thị cảnh báo 'LỆCH BỘ NHỚ'.
    """
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry
    from src.assistant_tool.optical_hp_sensor import OpticalVitals

    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            app.update()

            # 1. Kiểm tra có nút bấm Quét Vitals
            assert hasattr(app, "btn_auto_detect_vitals")
            assert "Quét Vitals" in app.btn_auto_detect_vitals.cget("text")

            # 2. Giả lập quét phát hiện 503 HP và 344 Mana
            mock_vitals = OpticalVitals(current_hp=495, max_hp=503, current_mana=344, max_mana=344, is_detected=True, detection_method="ocr")
            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=mock_vitals):
                # Gắn mock bridge để kiểm tra gửi lệnh hiệu chuẩn
                mock_bridge = MagicMock()
                app.core_bridge = mock_bridge

                app._on_auto_detect_vitals_clicked()
                assert app.entry_max_hp.get() == "503"
                mock_bridge.send_recalibrate_vitals.assert_called_once_with(503, 344, 0)

            # 3. Giả lập tình huống C++ Core gửi số rác 778/778 trong khi Optical sensor đã nhận diện 503
            app.optical_sensor.last_detected_vitals = mock_vitals
            snap_garbage_778 = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=778, max_hp=778, current_mana=100, max_mana=100)
            )
            app._on_telemetry_snapshot(snap_garbage_778)
            app.update()

            hp_text = app.lbl_hp.cget("text")
            badge_text = app.lbl_hp_sensor_badge.cget("text")
            assert "LỆCH" in hp_text or "UNVERIFIED" in hp_text
            assert "LỆCH" in badge_text or "UNVERIFIED" in badge_text
            assert "778" in badge_text or "778" in hp_text
        finally:
            app.on_closing()


def test_overlay_hud_click_calibrate_and_discrepancy_warning():
    """
    Kiểm tra OverlayHUD:
    1. Khi click vào thanh máu / nhãn HP gọi _on_vitals_quick_set().
    2. Cảnh báo hiển thị rõ ràng trên HUD khi RAM (778) lệch màn hình thực tế (503).
    """
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry
    from src.assistant_tool.gold_tracker import GoldTracker
    from src.assistant_tool.log_watcher import LogWatcher
    from src.assistant_tool.market_advisor import MarketAdvisor
    from src.assistant_tool.optical_hp_sensor import OpticalVitals
    from src.assistant_tool.overlay_gui import OverlayHUD

    gt = GoldTracker()
    lw = LogWatcher("dummy.txt")
    ma = MarketAdvisor(gold_tracker=gt)
    mock_bridge = MagicMock()

    hud = OverlayHUD(gold_tracker=gt, log_watcher=lw, market_advisor=ma, core_bridge=mock_bridge)
    try:
        hud.update()

        # 1. Kiểm tra phương thức _on_vitals_quick_set
        assert hasattr(hud, "_on_vitals_quick_set")

        mock_vitals = OpticalVitals(current_hp=495, max_hp=503, current_mana=344, max_mana=344, hp_ratio=495 / 503.0, is_detected=True)
        with patch.object(hud.optical_sensor, "detect_vitals_from_screen", return_value=mock_vitals):
            hud._on_vitals_quick_set()
            mock_bridge.send_recalibrate_vitals.assert_called_once_with(503, 344, 0)
            assert "503" in hud.core_status_badge.cget("text")

        # 2. Cảnh báo lệch bộ nhớ khi RAM báo 778/778
        hud.optical_sensor.last_detected_vitals = mock_vitals
        snap_778 = CoreTelemetrySnapshot(
            is_online=True,
            player=PlayerTelemetry(current_hp=778, max_hp=778, current_mana=344, max_mana=344, hp_percentage=100.0)
        )
        hud._on_telemetry_update(snap_778)
        hud.update()

        hp_label_text = hud.hp_label.cget("text")
        status_text = hud.core_status_badge.cget("text")
        assert "LỆCH" in hp_label_text
        assert "503" in hp_label_text
        assert "LỆCH" in status_text
    finally:
        hud.destroy()


def test_shm_bridge_send_recalibrate_vitals():
    """Kiểm tra CoreShmBridge.send_recalibrate_vitals đẩy opCode 98 vào SHM."""
    from src.assistant_tool.core_shm_bridge import CoreShmBridge

    bridge = CoreShmBridge()
    # Mock mmap
    mock_shm = bytearray(4 * 1024 * 1024)
    import struct
    # Header: magic (0x504F45324155544F), proto (0x00050005), isRunning (1), telemSeq (1), cmdSeq (0), cmdRead (0)
    struct.pack_into("<QIIQQQ", mock_shm, 0, 0x504F45324155544F, 0x00050005, 1, 1, 0, 0)
    bridge._shm = mock_shm  # type: ignore[assignment]

    ok = bridge.send_recalibrate_vitals(503, 344)
    assert ok is True

    # Kiểm tra cmdSeq tăng lên 1
    cmd_seq = struct.unpack_from("<Q", mock_shm, 24)[0]
    assert cmd_seq == 1

    # Kiểm tra gói CommandPacket tại offset 0x300000:
    # commandId (995), opCode (98), targetX (503.0), targetY (344.0)
    cmd_id, op_code, target_x, target_y = struct.unpack_from("<QIff", mock_shm, 0x300000)
    assert cmd_id == 995
    assert op_code == 98
    assert target_x == 503.0
    assert target_y == 344.0


def test_optical_sensor_winocr_semantic_regex_and_crop():
    """
    Kiểm tra _detect_via_winocr:
    1. Vùng crop chuẩn mới: Left (0, 0.65*h, 0.26*w, 0.88*h), Right (0.74*w, 0.66*h, w, 0.88*h).
    2. Nhận diện ngữ nghĩa khi Life 1/1, Shield 2,843/2,843, Ward 826/826, Rage 45/50.
    3. CI Invariant: current_hp = 1, max_hp = 1.
    4. Fallback khi không có nhãn chữ.
    """
    from PIL import Image
    test_img = Image.new("RGB", (2560, 1440), (0, 0, 0))
    expected_w_l = int(0.25 * 2560)  # 640

    async def fake_recognize(crop, lang):
        res = MagicMock()
        if crop.size[0] in (expected_w_l, int(0.26 * 2560)):
            res.text = "Life: 1/1\nShield: 2,843/2,843\nWard: 826/826"
        else:
            res.text = "Spirit: 100/100\nMana: 350 / 350\nRage: 45/50"
        return res

    with patch("winocr.recognize_pil", side_effect=fake_recognize):
        vitals = OpticalHPSensor._detect_via_winocr(test_img)
        assert vitals is not None
        assert vitals.is_detected is True
        assert vitals.current_hp == 1
        assert vitals.max_hp == 1
        assert vitals.current_shield == 2843
        assert vitals.max_shield == 2843
        assert vitals.current_ward == 826
        assert vitals.max_ward == 826
        assert vitals.current_mana == 350
        assert vitals.max_mana == 350
        assert vitals.current_spirit == 100
        assert vitals.max_spirit == 100
        assert vitals.current_rage == 45
        assert vitals.max_rage == 50

    # Fallback khi không có từ khóa nhãn
    async def fake_recognize_fallback(crop, lang):
        res = MagicMock()
        if crop.size[0] == expected_w_l:
            res.text = "510 / 510\n80 / 80"
        else:
            res.text = "340 / 340\n50 / 50"
        return res

    with patch("winocr.recognize_pil", side_effect=fake_recognize_fallback):
        vitals_fb = OpticalHPSensor._detect_via_winocr(test_img)
        assert vitals_fb is not None
        assert vitals_fb.is_detected is True
        assert vitals_fb.current_hp == 510
        assert vitals_fb.max_hp == 510
        assert vitals_fb.current_shield == 80
        assert vitals_fb.max_shield == 80
        assert vitals_fb.current_mana == 340
        assert vitals_fb.max_mana == 340
        assert vitals_fb.current_spirit == 50
        assert vitals_fb.max_spirit == 50


def test_core_controller_auto_detect_optical_es_mana_flags():
    """
    Kiểm tra CoreController:
    Khi max_hp == 0 (Auto), optical_inject_max_hp mặc định True:
    Tự động truyền --max-hp, --max-mana, --max-es từ kết quả quét OpticalHPSensor.
    """
    cc = CoreController()
    from src.assistant_tool.optical_hp_sensor import OpticalVitals
    mock_vitals = OpticalVitals(
        current_hp=495,
        max_hp=503,
        current_shield=120,
        max_shield=200,
        current_mana=344,
        max_mana=344,
        is_detected=True
    )
    with patch("src.assistant_tool.core_controller.OpticalHPSensor") as mock_sensor_cls:
        mock_inst = MagicMock()
        mock_inst.detect_vitals_from_screen.return_value = mock_vitals
        mock_sensor_cls.return_value = mock_inst

        # Chạy mà KHÔNG cần truyền optical_inject_max_hp (vì mặc định là True)
        cmd = cc.build_cmd({"expected_max_hp": "Auto"})
        assert "--max-hp" in cmd
        idx_hp = cmd.index("--max-hp")
        assert cmd[idx_hp + 1] == "503"

        assert "--max-mana" in cmd
        idx_mp = cmd.index("--max-mana")
        assert cmd[idx_mp + 1] == "344"

        assert "--max-es" in cmd
        idx_es = cmd.index("--max-es")
        assert cmd[idx_es + 1] == "200"


def test_control_center_collect_config_and_target_es_calibration():
    """
    Kiểm tra ControlCenterApp:
    1. _collect_config(): không còn số cứng 189, có detected_max_es, optical_inject_max_hp là True.
    2. _on_auto_detect_vitals_clicked(): truyền target_es sang send_recalibrate_vitals.
    3. overlay_gui._on_vitals_quick_set(): truyền target_es sang send_recalibrate_vitals.
    """
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.gold_tracker import GoldTracker
    from src.assistant_tool.log_watcher import LogWatcher
    from src.assistant_tool.market_advisor import MarketAdvisor
    from src.assistant_tool.optical_hp_sensor import OpticalVitals
    from src.assistant_tool.overlay_gui import OverlayHUD

    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            app.update()
            # 1. Kiểm tra _collect_config khi chưa phát hiện
            empty_vitals = OpticalVitals()
            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=empty_vitals):
                app.optical_sensor.last_detected_vitals = empty_vitals
                cfg = app._collect_config()
                assert cfg["max_mana"] == 0  # Bỏ số cứng 189!
                assert "detected_max_es" in cfg
                assert cfg["optical_inject_max_hp"] is True

            # 2. Kiểm tra _on_auto_detect_vitals_clicked với max_shield > 0
            mock_vitals = OpticalVitals(
                current_hp=495,
                max_hp=503,
                current_shield=100,
                max_shield=150,
                current_mana=344,
                max_mana=344,
                is_detected=True,
                detection_method="winocr_screen"
            )
            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=mock_vitals):
                mock_bridge = MagicMock()
                app.core_bridge = mock_bridge
                app._on_auto_detect_vitals_clicked()
                mock_bridge.send_recalibrate_vitals.assert_called_once_with(503, 344, 150)

            # 3. Kiểm tra overlay_gui với max_shield > 0
            gt = GoldTracker()
            lw = LogWatcher("dummy.txt")
            ma = MarketAdvisor(gold_tracker=gt)
            mock_bridge_overlay = MagicMock()
            hud = OverlayHUD(gold_tracker=gt, log_watcher=lw, market_advisor=ma, core_bridge=mock_bridge_overlay)
            try:
                with patch.object(hud.optical_sensor, "detect_vitals_from_screen", return_value=mock_vitals):
                    hud._on_vitals_quick_set()
                    mock_bridge_overlay.send_recalibrate_vitals.assert_called_once_with(503, 344, 150)
                    assert "150 ES" in hud.core_status_badge.cget("text")
            finally:
                hud.destroy()
        finally:
            app.on_closing()



def test_level_41_vitals_winocr_detection_and_ranged_core_flags():
    """
    Kiểm tra nhận diện Vitals nhân vật Level 41 từ thực tế:
    - Ảnh giả lập với text 'Life 781/781 Shield 204/317' bên trái và 'Mana Spirit 373/373 91/418' bên phải.
    - Khẳng định _detect_via_winocr() và detect_vitals_from_screen() nhận diện chính xác:
      max_hp = 781, max_shield = 317, max_mana = 373, max_spirit = 418.
    - Kiểm tra core_controller truyền cờ --primary-skill rmb --ranged --max-hp 781 --max-mana 373 --max-es 317.
    """
    from PIL import Image
    test_img = Image.new("RGB", (2560, 1440), (0, 0, 0))

    async def fake_recognize_lv41(crop, lang):
        res = MagicMock()
        # crop_l: int(0.25 * 2560) = 640 or int(0.26 * 2560) = 665
        if crop.size[0] in (int(0.25 * 2560), int(0.26 * 2560)):
            res.text = "Life 781/781 Shield 204/317"
        else:
            res.text = "Mana Spirit 373/373 91/418"
        return res

    with patch("winocr.recognize_pil", side_effect=fake_recognize_lv41):
        # 1. Khẳng định _detect_via_winocr()
        vitals_winocr = OpticalHPSensor._detect_via_winocr(test_img)
        assert vitals_winocr is not None
        assert vitals_winocr.is_detected is True
        assert vitals_winocr.max_hp == 781
        assert vitals_winocr.current_hp == 781
        assert vitals_winocr.max_shield == 317
        assert vitals_winocr.current_shield == 204
        assert vitals_winocr.max_mana == 373
        assert vitals_winocr.current_mana == 373
        assert vitals_winocr.max_spirit == 418
        assert vitals_winocr.current_spirit == 91

        # 2. Khẳng định detect_vitals_from_screen()
        sensor = OpticalHPSensor()
        vitals_screen = sensor.detect_vitals_from_screen(img=test_img)
        assert vitals_screen is not None
        assert vitals_screen.max_hp == 781
        assert vitals_screen.max_shield == 317
        assert vitals_screen.max_mana == 373
        assert vitals_screen.max_spirit == 418

        # 3. Kiểm tra core_controller truyền cờ:
        # --primary-skill rmb --ranged --max-hp 781 --max-mana 373 --max-es 317
        cc = CoreController()
        with patch.object(sensor, "detect_vitals_from_screen", return_value=vitals_screen), \
             patch("src.assistant_tool.core_controller.OpticalHPSensor", return_value=sensor):
            cmd = cc.build_cmd({"expected_max_hp": "Auto"})
            cmd_str = " ".join(cmd)
            assert "--primary-skill rmb --ranged --max-hp 781 --max-mana 373 --max-es 317" in cmd_str

            # Kiểm tra riêng lẻ các cờ
            assert "--primary-skill" in cmd
            assert cmd[cmd.index("--primary-skill") + 1] == "rmb"
            assert "--ranged" in cmd
            assert cmd[cmd.index("--max-hp") + 1] == "781"
            assert cmd[cmd.index("--max-mana") + 1] == "373"
            assert cmd[cmd.index("--max-es") + 1] == "317"

            # Kiểm tra alias build_command
            cmd_alias = cc.build_command({"expected_max_hp": "Auto"})
            assert "--primary-skill rmb --ranged --max-hp 781 --max-mana 373 --max-es 317" in " ".join(cmd_alias)


def test_distorted_chat_ocr_vitals_recognition():
    """
    Kiểm tra chuỗi OCR thực tế khi có chat box và ký tự méo:
    'i 3#FreezySni c plantsactu .11, 793/793 shjeIdAt3ji/3j7'
    Khẳng định nhận diện chính xác: max_hp = 793, current_hp = 793, max_shield = 317.
    """
    from PIL import Image
    test_img = Image.new("RGB", (2560, 1440), (0, 0, 0))

    real_ocr_snippet = "i 3#FreezySni c plantsactu .11, 793/793 shjeIdAt3ji/3j7"

    async def fake_recognize(crop, lang):
        res = MagicMock()
        # crop_l: int(0.25 * 2560) = 640 or int(0.26 * 2560) = 665
        if crop.size[0] in (int(0.25 * 2560), int(0.26 * 2560)):
            res.text = real_ocr_snippet
        else:
            res.text = "Mana Spirit 373/373 91/418"
        return res

    with patch("winocr.recognize_pil", side_effect=fake_recognize):
        vitals = OpticalHPSensor._detect_via_winocr(test_img)
        assert vitals is not None
        assert vitals.is_detected is True
        assert vitals.max_hp == 793
        assert vitals.current_hp == 793
        assert vitals.max_shield == 317
        assert vitals.current_shield == 311


def test_control_center_vitals_validation_and_valid_recalibrate():
    """
    Kiểm tra tính hợp lý của Vitals và gửi lệnh recalibrate khi max_hp thay đổi hợp lệ (781 -> 793):
    1. is_valid_vitals từ chối < 50, > 30000, current > max, và lặp bất thường 7793/778/779.
    2. Khi phát hiện max_hp thay đổi hợp lệ (781 -> 793), gửi send_recalibrate_vitals(793, mana, es).
    """
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry
    from src.assistant_tool.optical_hp_sensor import OpticalVitals

    # 1. Kiểm tra is_valid_vitals
    assert ControlCenterApp.is_valid_vitals(793, 793) is True
    assert ControlCenterApp.is_valid_vitals(781, 781) is True
    assert ControlCenterApp.is_valid_vitals(49, 49) is False  # < 50
    assert ControlCenterApp.is_valid_vitals(35000, 1000) is False  # > 30000
    assert ControlCenterApp.is_valid_vitals(793, 800) is False  # current > max
    assert ControlCenterApp.is_valid_vitals(7793, 7793) is False  # Lặp bất thường 7793
    assert ControlCenterApp.is_valid_vitals(778, 778) is False  # UI marker 778
    assert ControlCenterApp.is_valid_vitals(779, 779) is False  # UI marker 779

    # 2. Kiểm tra _periodic_task gửi send_recalibrate_vitals(793, mana, es) khi max_hp đổi 781 -> 793
    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            mock_bridge = MagicMock()
            mock_bridge.is_connected.return_value = True
            mock_bridge.last_snapshot = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=781, max_hp=781, current_mana=373, max_mana=373, current_es=204, max_es=317)
            )
            app.core_bridge = mock_bridge

            # Giả lập optical sensor nhận diện được 793/793 HP hợp lệ
            new_vitals = OpticalVitals(
                current_hp=793,
                max_hp=793,
                current_shield=311,
                max_shield=317,
                current_mana=373,
                max_mana=373,
                is_detected=True,
                detection_method="winocr_screen"
            )
            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=new_vitals):
                app._last_optical_crosscheck = 0.0
                app._periodic_task()
                mock_bridge.send_recalibrate_vitals.assert_called_once_with(793, 373, 317)
                assert app.entry_max_hp.get() == "793"

            # 3. Khi optical sensor bắt nhầm số rác 7793 -> BỊ CHẶN, KHÔNG GỬI
            mock_bridge.reset_mock()
            garbage_vitals = OpticalVitals(
                current_hp=7793,
                max_hp=7793,
                current_shield=311,
                max_shield=317,
                current_mana=373,
                max_mana=373,
                is_detected=True
            )
            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=garbage_vitals):
                app._last_optical_crosscheck = 0.0
                app._periodic_task()
                mock_bridge.send_recalibrate_vitals.assert_not_called()
        finally:
            app.on_closing()


def test_optical_sensor_background_worker():
    """Kiểm tra luồng quét ngầm bất đồng bộ (Background OCR / Slit Sensor worker thread 2Hz)."""
    from src.assistant_tool.optical_hp_sensor import OpticalHPSensor, OpticalVitals

    sensor = OpticalHPSensor()
    assert sensor._bg_running is False

    mock_vitals = OpticalVitals(current_hp=495, max_hp=503, is_detected=True)
    with patch.object(sensor, "detect_vitals_from_screen", return_value=mock_vitals):
        sensor.start_background_worker(interval=0.05)
        assert sensor._bg_running is True
        assert sensor._bg_thread is not None and sensor._bg_thread.is_alive()

        # Đợi worker thực thi ít nhất 1 chu kỳ
        time.sleep(0.12)
        assert sensor.last_detected_vitals.is_detected is True
        assert sensor.last_detected_vitals.max_hp == 503

        sensor.stop_background_worker(timeout=0.5)
        assert sensor._bg_running is False
        assert sensor._bg_thread is None


def test_ci_optical_vitals_parsing_and_thousand_separators():
    """
    Kiểm tra nhận diện Vitals của nhân vật build Chaos Inoculation (CI) và số có dấu phẩy hàng nghìn:
    - 'Shield Ward 2,402/2,402 826/826' -> HP=1/1, Shield=2402/2402, Ward=826/826.
    - 'Life Shield Ward 2,402/2,402 826/826' -> HP=1/1, Shield=2402/2402, Ward=826/826.
    - 'Life: 1,250/1,250 | Mana: 820/820' -> HP=1250/1250, Mana=820/820.
    """
    from src.assistant_tool.optical_hp_sensor import OpticalHPSensor

    sensor = OpticalHPSensor()

    # 1. CI Build: Shield Ward 2,402/2,402 826/826
    v1 = sensor.detect_vitals_from_screen(mock_text="Shield Ward 2,402/2,402 826/826")
    assert v1.is_detected is True
    assert v1.current_hp == 1
    assert v1.max_hp == 1
    assert v1.current_shield == 2402
    assert v1.max_shield == 2402
    assert v1.current_ward == 826
    assert v1.max_ward == 826

    # 2. CI Build với tiền tố Life: Life Shield Ward 2,402/2,402 826/826
    v2 = sensor.detect_vitals_from_screen(mock_text="Life Shield Ward 2,402/2,402 826/826")
    assert v2.is_detected is True
    assert v2.current_hp == 1
    assert v2.max_hp == 1
    assert v2.current_shield == 2402
    assert v2.max_shield == 2402
    assert v2.current_ward == 826
    assert v2.max_ward == 826

    # 3. Phân cách hàng nghìn chuẩn: 1,250 Life và 820 Mana
    v3 = sensor.detect_vitals_from_screen(mock_text="Life: 1,250/1,250 | Mana: 820/820")
    assert v3.is_detected is True
    assert v3.current_hp == 1250
    assert v3.max_hp == 1250
    assert v3.current_mana == 820
    assert v3.max_mana == 820

    # 4. CI Build với đầy đủ Shield 2,843/2,843, Ward 826/826, Spirit và Rage
    v4 = sensor.detect_vitals_from_screen(
        mock_text="Life: 1/1\nShield: 2,843/2,843\nWard: 826/826\nMana: 350/350\nSpirit: 100/100\nRage: 45/50"
    )
    assert v4.is_detected is True
    assert v4.current_hp == 1
    assert v4.max_hp == 1
    assert v4.current_shield == 2843
    assert v4.max_shield == 2843
    assert v4.current_ward == 826
    assert v4.max_ward == 826
    assert v4.current_mana == 350
    assert v4.max_mana == 350
    assert v4.current_spirit == 100
    assert v4.max_spirit == 100
    assert v4.current_rage == 45
    assert v4.max_rage == 50

    # 5. Defense cluster with Life 1/1, Shield 4,284/4,284, Ward 241/241 without explicit Mana label:
    # Must NOT set mana=241! Ward must be 241, mana must be 0 or None.
    v5 = sensor.detect_vitals_from_screen(
        mock_text="Life: 1/1\nShield: 4,284/4,284\nWard: 241/241"
    )
    assert v5.is_detected is True
    assert v5.current_hp == 1
    assert v5.max_hp == 1
    assert v5.current_shield == 4284
    assert v5.max_shield == 4284
    assert v5.current_ward == 241
    assert v5.max_ward == 241
    assert v5.current_mana == 0 or v5.current_mana is None

    # 6. Defense cluster with separate Mana on right globe:
    v6 = sensor.detect_vitals_from_screen(
        mock_text="Life: 1/1\nShield: 4,284/4,284\nWard: 241/241\nMana: 897/897"
    )
    assert v6.is_detected is True
    assert v6.current_shield == 4284
    assert v6.current_ward == 241
    assert v6.current_mana == 897
    assert v6.max_mana == 897


def test_ci_recalibration_and_gui_display():
    """
    Kiểm tra tự động hiệu chuẩn CI (HP=1, max_shield > 0) và cập nhật giao diện GUI:
    - Gửi opcode 98: send_recalibrate_vitals(1, optical_mana, optical_max_shield).
    - Ô entry_max_hp hiển thị 'CI'.
    - Badge và Label hiển thị '[CI] Shield={optical_max_shield}, Ward={optical_max_ward}'.
    """
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry
    from src.assistant_tool.optical_hp_sensor import OpticalVitals

    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            mock_bridge = MagicMock()
            mock_bridge.is_connected.return_value = True
            mock_bridge.last_snapshot = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=0, max_hp=0, current_mana=0, max_mana=0, current_es=0, max_es=0)
            )
            app.core_bridge = mock_bridge
            app.core_controller.is_running = MagicMock(return_value=True)

            ci_vitals = OpticalVitals(
                current_hp=1,
                max_hp=1,
                current_shield=2402,
                max_shield=2402,
                current_ward=826,
                max_ward=826,
                current_mana=820,
                max_mana=820,
                is_detected=True,
                detection_method="winocr_screen"
            )

            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=ci_vitals):
                app._last_optical_crosscheck = 0.0
                app._periodic_task()

                # Xác minh gửi IPC recalibrate với max_hp=1, mana=820, es=2402
                mock_bridge.send_recalibrate_vitals.assert_called_once_with(1, 820, 2402)
                assert app.entry_max_hp.get() == "CI"
                assert "[CI] Shield=2402, Ward=826" in app.lbl_hp.cget("text")
                assert "[CI] Shield=2402, Ward=826" in app.lbl_hp_sensor_badge.cget("text")
        finally:
            app.on_closing()


def test_authentic_16_9_vitals_extraction_after_restoring_2_0():
    """
    Kiểm tra nhận diện Vitals từ ảnh chụp in-game thật captures/after_restoring_2_0.png (16:9 standard 2560x1440):
    - Khẳng định nhận diện chính xác 100%: Life 1/1, Shield 5656/5656, Ward 240/240, Mana 876/876, Spirit 7/147, Rage 0/30.
    - Khẳng định tích hợp Control Center: ô entry_max_hp hiển thị 'CI', gửi IPC recalibrate (1, 876, 5656), badge hiển thị [CI].
    """
    import os
    from PIL import Image
    from src.assistant_tool.optical_hp_sensor import OpticalHPSensor
    from src.assistant_tool.control_center import ControlCenterApp
    from src.assistant_tool.core_shm_bridge import CoreTelemetrySnapshot, PlayerTelemetry

    img_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "captures", "after_restoring_2_0.png"))
    assert os.path.exists(img_path), f"Không tìm thấy tệp ảnh thật: {img_path}"

    img = Image.open(img_path)
    assert img.size == (2560, 1440), "Ảnh không đúng kích thước chuẩn 16:9 2560x1440"

    sensor = OpticalHPSensor()
    v = sensor.detect_vitals_from_screen(img=img)

    assert v is not None
    assert v.is_detected is True
    assert v.detection_method == "winocr_screen"
    assert v.current_hp == 1
    assert v.max_hp == 1
    assert v.current_shield == 5656
    assert v.max_shield == 5656
    assert v.current_ward == 240
    assert v.max_ward == 240
    assert v.current_mana == 876
    assert v.max_mana == 876
    assert v.current_spirit == 7
    assert v.max_spirit == 147
    assert v.current_rage == 0
    assert v.max_rage == 30

    # Tích hợp Control Center
    with patch.object(ControlCenterApp, "_save_user_settings"), \
         patch.object(ControlCenterApp, "_load_user_settings", return_value={"expected_max_hp": "Auto"}):
        app = ControlCenterApp()
        try:
            mock_bridge = MagicMock()
            mock_bridge.is_connected.return_value = True
            mock_bridge.last_snapshot = CoreTelemetrySnapshot(
                is_online=True,
                player=PlayerTelemetry(current_hp=0, max_hp=0, current_mana=0, max_mana=0, current_es=0, max_es=0)
            )
            app.core_bridge = mock_bridge
            app.core_controller.is_running = MagicMock(return_value=True)

            with patch.object(app.optical_sensor, "detect_vitals_from_screen", return_value=v):
                app._last_optical_crosscheck = 0.0
                app._periodic_task()

                mock_bridge.send_recalibrate_vitals.assert_called_once_with(1, 876, 5656)
                assert app.entry_max_hp.get() == "CI"
                assert "[CI] Shield=5656, Ward=240" in app.lbl_hp.cget("text")
                assert "[CI] Shield=5656, Ward=240" in app.lbl_hp_sensor_badge.cget("text")
                assert "5656/5656" in app.lbl_vitals_es_ward.cget("text")
                assert "240/240" in app.lbl_vitals_es_ward.cget("text")
        finally:
            app.on_closing()


if __name__ == "__main__":
    test_optical_sensor_initialization()
    test_is_red_pixel()
    test_get_globe_roi_scaling()
    test_multi_slit_sampling_and_text_rejection()
    test_performance_under_2ms()
    test_gdi_handles_cleanup_safety()
    test_desktop_attachment_and_printwindow_fallback()
    test_core_controller_and_gui_max_hp_flags()
    test_control_center_optical_sensor_gui_integration()
    test_control_center_shm_unverified_mismatch_warning()
    test_optical_vitals_text_parsing_and_estimation()
    test_check_memory_discrepancy_778_vs_503()
    test_core_controller_auto_detect_max_hp_injection()
    test_control_center_quick_calibrate_and_discrepancy_alert()
    test_overlay_hud_click_calibrate_and_discrepancy_warning()
    test_shm_bridge_send_recalibrate_vitals()
    test_optical_sensor_winocr_semantic_regex_and_crop()
    test_core_controller_auto_detect_optical_es_mana_flags()
    test_control_center_collect_config_and_target_es_calibration()
    test_level_41_vitals_winocr_detection_and_ranged_core_flags()
    test_distorted_chat_ocr_vitals_recognition()
    test_control_center_vitals_validation_and_valid_recalibrate()
    test_optical_sensor_background_worker()
    test_ci_optical_vitals_parsing_and_thousand_separators()
    test_ci_recalibration_and_gui_display()
    test_authentic_16_9_vitals_extraction_after_restoring_2_0()
    print("\n✅ TẤT CẢ TEST OPTICAL HP SENSOR & BACKGROUND WORKER ĐỀU VƯỢT QUA 100%!")
