"""AutoPOE2 - Unit Tests cho Skills Management & Auto Skill Binder.

Kiểm thử tính toán hình học Action Bar đa độ phân giải,
bounding box pop-up, thuật toán phát hiện pop-up scanner và quy trình binder.
Tuân thủ Rule 1, Rule 4, Rule 5.
"""

from __future__ import annotations

import unittest
from unittest.mock import MagicMock, patch
from PIL import Image

from src.assistant_tool.skills.skill_constants import (
    ActionBarSlot,
    NormalizedSlotCoord,
    ACTION_BAR_DEFAULT_OFFSETS,
    SKILL_POPUP_GEOMETRY,
    get_slot_client_pos,
    get_popup_bounding_box,
)
from src.assistant_tool.skills.skill_popup_scanner import SkillPopupScanner
from src.assistant_tool.skills.skill_binder import SkillBinder


class TestSkillGeometry(unittest.TestCase):
    """Kiểm thử tính toán hình học tọa độ Action Bar và Pop-up."""

    def test_slot_client_pos_1189x891(self) -> None:
        """Kiểm tra quy đổi tọa độ slot trên độ phân giải windowed 1189x891."""
        # Slot E
        x_e, y_e = get_slot_client_pos(ActionBarSlot.SLOT_E, 1189, 891)
        self.assertAlmostEqual(x_e, 840, delta=2)
        self.assertAlmostEqual(y_e, 860, delta=2)

        # Slot Q
        x_q, y_q = get_slot_client_pos(ActionBarSlot.SLOT_Q, 1189, 891)
        self.assertAlmostEqual(x_q, 795, delta=2)
        self.assertAlmostEqual(y_q, 860, delta=2)

        # Slot R
        x_r, y_r = get_slot_client_pos(ActionBarSlot.SLOT_R, 1189, 891)
        self.assertAlmostEqual(x_r, 885, delta=2)
        self.assertAlmostEqual(y_r, 860, delta=2)

    def test_slot_client_pos_2560x1440(self) -> None:
        """Kiểm tra quy đổi tọa độ slot trên độ phân giải 2K 2560x1440."""
        x_e, y_e = get_slot_client_pos(ActionBarSlot.SLOT_E, 2560, 1440)
        self.assertGreater(x_e, 1800)
        self.assertGreater(y_e, 1380)

    def test_popup_bounding_box(self) -> None:
        """Kiểm tra tính toán bounding box của pop-up khi mở từ Slot E."""
        left, top, right, bottom = get_popup_bounding_box(ActionBarSlot.SLOT_E, 1189, 891)
        self.assertGreater(right, left)
        self.assertGreater(bottom, top)

        # Chiều rộng popup ~242px (+- 5px)
        width = right - left
        self.assertAlmostEqual(width, 242, delta=5)

        # Chiều cao popup ~295px (+- 5px)
        height = bottom - top
        self.assertAlmostEqual(height, 295, delta=5)

        # Đáy popup phải nằm ngay phía trên slot E (~840)
        self.assertLess(bottom, 860)


class TestSkillPopupScanner(unittest.TestCase):
    """Kiểm thử thuật toán quét pop-up menu."""

    def setUp(self) -> None:
        self.scanner = SkillPopupScanner()

    def test_is_popup_open_synthetic_dark(self) -> None:
        """Ảnh có nền tối và viền vàng kim phải được nhận diện là popup mở."""
        # Tạo ảnh giả lập viền vàng đồng và nền đen nội thất
        img = Image.new("RGB", (400, 400), color=(15, 12, 10))
        # Viền vàng đồng ở top=10
        for x in range(10, 390):
            img.putpixel((x, 10), (100, 80, 50))

        bbox = (10, 10, 390, 390)
        is_open = self.scanner.is_popup_open(img, bbox)
        self.assertTrue(is_open)

    def test_is_popup_open_synthetic_bright(self) -> None:
        """Ảnh sáng (môi trường ngoài map/town không mở popup) không được coi là popup."""
        img = Image.new("RGB", (400, 400), color=(180, 180, 180))
        bbox = (10, 10, 390, 390)
        is_open = self.scanner.is_popup_open(img, bbox)
        self.assertFalse(is_open)

    def test_real_captures_authentic_data(self) -> None:
        """Kiểm thử trên 100% dữ liệu ảnh chụp thật từ game client (Rule 5 SSoT)."""
        import os
        open_path = "captures/20260916_224410_720_LIVE_CLICK_SLOT_E.png"
        closed_path = "captures/20260916_224834_630_LIVE_CLOSE_POPUP_ESC.png"
        if not os.path.exists(open_path) or not os.path.exists(closed_path):
            self.skipTest("Bỏ qua test nếu không tìm thấy file capture thật")

        img_open = Image.open(open_path)
        img_closed = Image.open(closed_path)
        bbox = get_popup_bounding_box(ActionBarSlot.SLOT_E, img_open.width, img_open.height)

        self.assertTrue(self.scanner.is_popup_open(img_open, bbox), "Ảnh click slot E phải nhận diện popup MỞ")
        self.assertFalse(self.scanner.is_popup_open(img_closed, bbox), "Ảnh bấm ESC phải nhận diện popup ĐÓNG")

    def test_get_positions(self) -> None:
        """Kiểm tra tọa độ các nút con bên trong pop-up."""
        bbox = (715, 545, 957, 840)
        cx, cy = self.scanner.get_close_button_pos(bbox)
        self.assertAlmostEqual(cx, 948, delta=5)
        self.assertAlmostEqual(cy, 558, delta=5)

        ux, uy = self.scanner.get_unbind_action_pos(bbox)
        self.assertAlmostEqual(ux, 836, delta=5)
        self.assertAlmostEqual(uy, 610, delta=5)

        gx, gy = self.scanner.get_gem_skill_pos(bbox, row=0, col=0)
        self.assertAlmostEqual(gx, 744, delta=5)
        self.assertAlmostEqual(gy, 775, delta=5)


class TestSkillBinder(unittest.TestCase):
    """Kiểm thử FSM điều phối của SkillBinder."""

    @patch("src.assistant_tool.skills.skill_binder.post_message_key")
    @patch("src.assistant_tool.skills.skill_binder.post_message_click")
    @patch.object(SkillBinder, "get_client_size", return_value=(1189, 891))
    def test_bind_gem_skill(
        self, mock_client_size: MagicMock, mock_click: MagicMock, mock_key: MagicMock
    ) -> None:
        binder = SkillBinder(hwnd=12345)
        res = binder.bind_gem_skill(ActionBarSlot.SLOT_E, row=0, col=1, delay_between=0.0)
        self.assertTrue(res)

        # Phải gọi click 2 lần (1 lần mở slot, 1 lần click ngọc)
        self.assertEqual(mock_click.call_count, 2)
        # Phải gửi phím ESC để đóng popup
        mock_key.assert_called_once()

    @patch("src.assistant_tool.skills.skill_binder.post_message_key")
    @patch("src.assistant_tool.skills.skill_binder.post_message_click")
    @patch.object(SkillBinder, "get_client_size", return_value=(1189, 891))
    def test_unbind_slot(
        self, mock_client_size: MagicMock, mock_click: MagicMock, mock_key: MagicMock
    ) -> None:
        binder = SkillBinder(hwnd=12345)
        res = binder.unbind_slot(ActionBarSlot.SLOT_E, delay_between=0.0)
        self.assertTrue(res)
        self.assertEqual(mock_click.call_count, 2)
        mock_key.assert_called_once()


if __name__ == "__main__":
    unittest.main()
