"""AutoPOE2 - Test Suite Kiểm Chứng Dynamic Resolution Scaling Engine (Doc 63 / SSoT 2026).
=======================================================================================
Kiểm thử toàn diện:
1. Tính bất biến toán học của co giãn tuyến tính và tính đối xứng thuận nghịch (Round-trip Idempotency <= 1px).
2. Sai số tọa độ <= 1px trên 4 UI Elements trọng yếu:
   - Nút TRAVERSE (Text center & Frame center).
   - Khay cắm Waystone (Socket Map Device & Khay Waystone Hàng 1 Atlas).
   - Lưới Hòm đồ Stash (12x12) và Túi đồ Inventory (12x5).
   - Bảng điều khiển Sàn Ngoại Hối Faustus Currency Exchange.
3. Độ phủ các độ phân giải: 1920x1080 (SSoT), 2560x1440 (1440p), 1189x891 (Windowed PID 22632), 1280x720 (720p).
4. Tính kháng lỗi trên các độ phân giải bất thường: Ultrawide (21:9, 32:9), kích thước siêu nhỏ (640x480), kích thước dị biệt.

Tuân thủ nghiêm ngặt:
- Rule 5: Sử dụng dữ liệu thực nghiệm và ground-truth.
- Rule 14: Quality Gate, 100% tests PASS.
"""

from __future__ import annotations

import math
import os
import unittest
from PIL import Image

from src.common.coordinate_transform import (
    CoordinateScaler,
    Resolution,
    ScalingMode,
    RES_1080P,
    RES_1440P,
    RES_720P,
    RES_4K,
    RES_WINDOWED_DEFAULT,
    get_faustus_element_pos,
    get_inventory_slot_pos,
    get_stash_slot_pos,
    get_traverse_button_bbox,
    get_traverse_button_pos,
    get_waystone_socket_pos,
    get_waystone_tray_slot_pos,
    resolve_resolution,
)
from src.common.vision_filters import (
    find_traverse_button_dual_pass,
    match_traverse_template_edge,
)
from src.common.vision_ocr import (
    find_map_device_screen_pos,
)


class TestDynamicResolutionScaling(unittest.TestCase):
    """Test suite kiểm thử Dynamic Resolution Scaling Engine."""

    def test_resolution_dataclass_and_aspect_ratio_invariants(self) -> None:
        """Kiểm chứng tính bất biến của Resolution và phân loại tỷ lệ khung hình."""
        # 16:9 Chuẩn
        res_1080 = Resolution(1920, 1080)
        self.assertTrue(res_1080.is_standard_16_9)
        self.assertFalse(res_1080.is_windowed_aspect)
        self.assertFalse(res_1080.is_ultrawide)
        self.assertAlmostEqual(res_1080.aspect_ratio, 16.0 / 9.0, places=3)

        # 1440p Chuẩn
        res_1440 = Resolution(2560, 1440)
        self.assertTrue(res_1440.is_standard_16_9)

        # 720p Chuẩn
        res_720 = Resolution(1280, 720)
        self.assertTrue(res_720.is_standard_16_9)

        # Windowed PID 22632: 1189x891 (Aspect ~1.334 ~ 4:3)
        res_win = Resolution(1189, 891)
        self.assertFalse(res_win.is_standard_16_9)
        self.assertTrue(res_win.is_windowed_aspect)
        self.assertFalse(res_win.is_ultrawide)
        self.assertAlmostEqual(res_win.aspect_ratio, 1189.0 / 891.0, places=3)

        # Ultrawide 21:9 & 32:9
        res_uw = Resolution(3440, 1440)
        self.assertTrue(res_uw.is_ultrawide)

        # Biên bất thường: Không chấp nhận kích thước <= 0
        with self.assertRaises(ValueError):
            Resolution(0, 1080)
        with self.assertRaises(ValueError):
            Resolution(1920, -1)

    def test_linear_scaling_round_trip_idempotency(self) -> None:
        """Kiểm chứng tính bất biến chuyển đổi thuận nghịch (Round-trip Idempotency <= 1px).

        Bất biến: INV-SCALE-IDEMPOTENT-1PX:
        Tọa độ chuyển từ A -> B -> A phải sai số tối đa <= 1 pixel do làm tròn.
        """
        resolutions_to_test = [
            RES_1440P,
            RES_720P,
            RES_4K,
            RES_WINDOWED_DEFAULT,
            Resolution(1366, 768),
            Resolution(2560, 1080),
        ]

        # Kiểm thử 100 điểm phân bố đều trên màn hình 1080p
        test_points = [
            (x, y)
            for x in range(100, 1800, 200)
            for y in range(100, 1000, 150)
        ]

        for target_res in resolutions_to_test:
            scaler = CoordinateScaler(source_res=RES_1080P, target_res=target_res)
            for src_x, src_y in test_points:
                # A -> B
                tgt_x, tgt_y = scaler.transform_point(src_x, src_y)
                # B -> A
                rec_x, rec_y = scaler.inverse_transform_point(tgt_x, tgt_y)

                dx = abs(rec_x - src_x)
                dy = abs(rec_y - src_y)

                self.assertLessEqual(
                    dx, 1,
                    f"Sai số trục X vượt ngưỡng 1px: src=({src_x}, {src_y}), tgt=({tgt_x}, {tgt_y}), rec=({rec_x}, {rec_y}) trên res={target_res}"
                )
                self.assertLessEqual(
                    dy, 1,
                    f"Sai số trục Y vượt ngưỡng 1px: src=({src_x}, {src_y}), tgt=({tgt_x}, {tgt_y}), rec=({rec_x}, {rec_y}) trên res={target_res}"
                )

    def test_traverse_button_coordinates_across_resolutions(self) -> None:
        """Kiểm chứng tọa độ nút TRAVERSE trên 1080p, 1440p, 720p, 1189x891.

        Ground-truth:
        - 1440p: (1091, 747) text center, (1071, 752) frame center.
        - 1080p: (818, 560) text center, (803, 564) frame center.
        Sai số tối đa <= 1px.
        """
        # 1. Kiểm chứng trên 1440p
        pos_1440 = get_traverse_button_pos(RES_1440P, use_frame_center=False)
        self.assertEqual(pos_1440, (1091, 747))

        frame_1440 = get_traverse_button_pos(RES_1440P, use_frame_center=True)
        self.assertEqual(frame_1440, (1071, 752))

        # 2. Kiểm chứng trên 1080p SSoT
        pos_1080 = get_traverse_button_pos(RES_1080P, use_frame_center=False)
        self.assertEqual(pos_1080, (818, 560))

        frame_1080 = get_traverse_button_pos(RES_1080P, use_frame_center=True)
        self.assertEqual(frame_1080, (803, 564))

        # 3. Kiểm chứng trên 720p (scale factor chính xác 2/3 so với 1080p, 1/2 so với 1440p)
        pos_720 = get_traverse_button_pos(RES_720P, use_frame_center=False)
        # 1091 * 0.5 = 545.5 -> 546; 747 * 0.5 = 373.5 -> 374
        self.assertIn(pos_720[0], (545, 546))
        self.assertIn(pos_720[1], (373, 374))

        # 4. Kiểm chứng trên Windowed thực tế PID 22632: 1189x891
        pos_win = get_traverse_button_pos(RES_WINDOWED_DEFAULT, use_frame_center=False)
        # sx = 1189 / 1920 = 0.61927; sy = 891 / 1080 = 0.825
        # 818.25 * 0.6192708 = 506.718 -> 507; 560.25 * 0.825 = 462.206 -> 462
        self.assertEqual(pos_win, (507, 462))

        # 5. Kiểm chứng BBox của nút TRAVERSE trên 1440p và 1080p
        bbox_1440 = get_traverse_button_bbox(RES_1440P)
        self.assertEqual(bbox_1440, (999, 730, 1142, 774))

        bbox_1080 = get_traverse_button_bbox(RES_1080P)
        self.assertEqual(bbox_1080, (749, 548, 856, 580))

    def test_waystone_tray_and_socket_coordinates(self) -> None:
        """Kiểm chứng tọa độ khay cắm Waystone (Socket & Tray Row 1) qua các độ phân giải."""
        # 1. Socket Map Device trung tâm
        sock_1440 = get_waystone_socket_pos(RES_1440P)
        self.assertEqual(sock_1440, (1054, 735))

        sock_1080 = get_waystone_socket_pos(RES_1080P)
        self.assertIn(sock_1080[0], (790, 791))
        self.assertEqual(sock_1080[1], 551)

        sock_720 = get_waystone_socket_pos(RES_720P)
        self.assertEqual(sock_720, (527, 368))

        sock_win = get_waystone_socket_pos(RES_WINDOWED_DEFAULT)
        # 790.5 * (1189/1920) = 489.53 -> 490; 551.25 * 0.825 = 454.78 -> 455
        self.assertEqual(sock_win, (490, 455))

        # 2. Khay Waystone Hàng 1 (5 slots cách đều nhau)
        # Slot 0 và Slot 4 trên 1080p
        slot0_1080 = get_waystone_tray_slot_pos(0, RES_1080P)
        slot4_1080 = get_waystone_tray_slot_pos(4, RES_1080P)
        self.assertEqual(slot0_1080, (240, 700))
        self.assertEqual(slot4_1080, (480, 700))  # 240 + 4 * 60 = 480

        # Trên 1440p (scale factor 4/3)
        slot0_1440 = get_waystone_tray_slot_pos(0, RES_1440P)
        slot4_1440 = get_waystone_tray_slot_pos(4, RES_1440P)
        self.assertEqual(slot0_1440, (320, 933))
        self.assertEqual(slot4_1440, (640, 933))  # 480 * 4/3 = 640

    def test_stash_and_inventory_grid_scaling(self) -> None:
        """Kiểm chứng độ chính xác không tích lũy sai số của lưới Stash (12x12) và Inventory (12x5)."""
        # 1. Túi đồ Inventory trên 1080p SSoT
        # Ô (0, 0): origin=(1272, 590), cell=(52.5, 52.5) -> center = (1272 + 26.25, 590 + 26.25) = (1298.25, 616.25) -> (1298, 616)
        inv_0_0_1080 = get_inventory_slot_pos(0, 0, RES_1080P)
        self.assertEqual(inv_0_0_1080, (1298, 616))

        # Ô góc dưới phải (11, 4): center = (1272 + 11.5*52.5, 590 + 4.5*52.5) = (1875.75, 826.25) -> (1876, 826)
        inv_11_4_1080 = get_inventory_slot_pos(11, 4, RES_1080P)
        self.assertEqual(inv_11_4_1080, (1876, 826))

        # 2. Túi đồ Inventory trên 1440p
        inv_0_0_1440 = get_inventory_slot_pos(0, 0, RES_1440P)
        inv_11_4_1440 = get_inventory_slot_pos(11, 4, RES_1440P)
        self.assertEqual(inv_0_0_1440, (1731, 822))
        self.assertEqual(inv_11_4_1440, (2501, 1102))

        # 3. Túi đồ Inventory trên Windowed 1189x891 (Hỗ trợ Right-anchored pouch margin)
        inv_0_0_win = get_inventory_slot_pos(0, 0, RES_WINDOWED_DEFAULT)
        self.assertGreater(inv_0_0_win[0], 600)
        self.assertLess(inv_0_0_win[0], 1189)
        self.assertGreater(inv_0_0_win[1], 400)
        self.assertLess(inv_0_0_win[1], 891)

        # 4. Hòm đồ Stash trên 1080p
        # Ô (0, 0): origin=(180, 190), cell=(52, 52) -> center = (180 + 26, 190 + 26) = (206, 216)
        stash_0_0_1080 = get_stash_slot_pos(0, 0, RES_1080P)
        self.assertEqual(stash_0_0_1080, (206, 216))

        # Ô (11, 11): center = (180 + 11.5*52, 190 + 11.5*52) = (778, 788)
        stash_11_11_1080 = get_stash_slot_pos(11, 11, RES_1080P)
        self.assertEqual(stash_11_11_1080, (778, 788))

        # 5. Hòm đồ Stash trên 1440p
        stash_0_0_1440 = get_stash_slot_pos(0, 0, RES_1440P)
        stash_11_11_1440 = get_stash_slot_pos(11, 11, RES_1440P)
        self.assertEqual(stash_0_0_1440, (275, 288))
        self.assertEqual(stash_11_11_1440, (1037, 1051))

    def test_faustus_currency_exchange_elements_scaling(self) -> None:
        """Kiểm chứng tọa độ các thành phần của Sàn Ngoại Hối Faustus qua các độ phân giải."""
        faustus_elements = [
            "panel_center",
            "tab_i_want",
            "tab_i_have",
            "amount_input",
            "submit_order_btn",
            "close_panel_btn",
        ]

        # Kiểm chứng 1080p
        for elem in faustus_elements:
            pos_1080 = get_faustus_element_pos(elem, RES_1080P)
            self.assertIsInstance(pos_1080, tuple)
            self.assertEqual(len(pos_1080), 2)
            self.assertGreater(pos_1080[0], 0)
            self.assertGreater(pos_1080[1], 0)

        # Kiểm chứng 1440p
        panel_1440 = get_faustus_element_pos("panel_center", RES_1440P)
        self.assertEqual(panel_1440, (1280, 720))

        submit_1440 = get_faustus_element_pos("submit_order_btn", RES_1440P)
        self.assertEqual(submit_1440, (1280, 1040))

        # Kiểm chứng Windowed 1189x891
        panel_win = get_faustus_element_pos("panel_center", RES_WINDOWED_DEFAULT)
        # 960 * (1189/1920) = 594.5 (round half-to-even: 594); 540 * (891/1080) = 445.5 -> 446
        self.assertIn(panel_win[0], (594, 595))
        self.assertEqual(panel_win[1], 446)

    def test_extreme_and_abnormal_resolutions_resilience(self) -> None:
        """Kiểm chứng tính kháng lỗi trên các độ phân giải bất thường (Ultrawide, Low-res, non-16:9)."""
        extreme_resolutions = [
            Resolution(640, 480),    # Cực thấp VGA
            Resolution(800, 600),    # SVGA
            Resolution(1024, 768),   # XGA 4:3
            Resolution(1680, 1050),  # 16:10
            Resolution(2560, 1080),  # 21:9 UW-FHD
            Resolution(3440, 1440),  # 21:9 UW-QHD
            Resolution(5120, 1440),  # 32:9 Super Ultrawide
        ]

        for res in extreme_resolutions:
            # Nút TRAVERSE
            trav_pos = get_traverse_button_pos(res)
            self.assertTrue(0 < trav_pos[0] < res.width)
            self.assertTrue(0 < trav_pos[1] < res.height)

            # Waystone Socket
            sock_pos = get_waystone_socket_pos(res)
            self.assertTrue(0 < sock_pos[0] < res.width)
            self.assertTrue(0 < sock_pos[1] < res.height)

            # Stash Slot
            stash_pos = get_stash_slot_pos(0, 0, res)
            self.assertTrue(0 < stash_pos[0] < res.width)
            self.assertTrue(0 < stash_pos[1] < res.height)

            # Faustus Panel
            faustus_pos = get_faustus_element_pos("panel_center", res)
            self.assertTrue(0 < faustus_pos[0] < res.width)
            self.assertTrue(0 < faustus_pos[1] < res.height)

    def test_integration_with_dual_pass_locator_on_real_capture(self) -> None:
        """Kiểm chứng tích hợp thực tế giữa CoordinateScaler và Dual-Pass Locator trên capture 2560x1440."""
        capture_path = os.path.join(
            os.path.dirname(__file__), "..", "captures", "20260913_102100_639_AFTER_CLICK_ATLAS_NODE.png"
        )
        if not os.path.exists(capture_path):
            self.skipTest(f"Không tìm thấy file capture: {capture_path}")

        img = Image.open(capture_path)
        w, h = img.size
        self.assertEqual((w, h), (2560, 1440))

        # Gọi hàm Dual-Pass Locator đã được nâng cấp bằng CoordinateScaler
        coords = find_traverse_button_dual_pass(img)
        self.assertIsNotNone(coords)
        self.assertAlmostEqual(coords[0], 1091, delta=3)
        self.assertAlmostEqual(coords[1], 747, delta=3)


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