"""Unit tests for winlayer.py -- runnable on any platform (macOS/Linux/CI)
since all real Win32 calls are injected. Run with:
    python3 -m unittest discover -s togen/display_agent_win/tests
"""
import os
import sys
import unittest

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

import winlayer  # noqa: E402


MONITORS = [
    {"index": 1, "rect": (0, 0, 1920, 1080), "primary": True},
    {"index": 2, "rect": (1920, 0, 3840, 1080), "primary": False},
]


class SanitizeLayerTests(unittest.TestCase):
    def test_valid_values_pass_through(self):
        for v in winlayer.VALID_LAYERS:
            self.assertEqual(winlayer.sanitize_layer(v), v)

    def test_case_insensitive(self):
        self.assertEqual(winlayer.sanitize_layer("TOP"), "top")
        self.assertEqual(winlayer.sanitize_layer(" Bottom "), "bottom")

    def test_unknown_or_missing_defaults_to_top(self):
        self.assertEqual(winlayer.sanitize_layer("sideways"), "top")
        self.assertEqual(winlayer.sanitize_layer(None), "top")
        self.assertEqual(winlayer.sanitize_layer(""), "top")
        self.assertEqual(winlayer.sanitize_layer(123), "top")


class ResolveMonitorTests(unittest.TestCase):
    def test_primary_spec(self):
        m = winlayer.resolve_monitor(MONITORS, "primary")
        self.assertEqual(m["index"], 1)

    def test_blank_spec_defaults_to_primary(self):
        m = winlayer.resolve_monitor(MONITORS, "")
        self.assertEqual(m["index"], 1)
        m = winlayer.resolve_monitor(MONITORS, None)
        self.assertEqual(m["index"], 1)

    def test_numeric_index_spec(self):
        m = winlayer.resolve_monitor(MONITORS, 2)
        self.assertEqual(m["index"], 2)
        m = winlayer.resolve_monitor(MONITORS, "2")
        self.assertEqual(m["index"], 2)

    def test_out_of_range_index_falls_back_to_primary(self):
        m = winlayer.resolve_monitor(MONITORS, 99)
        self.assertEqual(m["index"], 1)

    def test_no_monitors_returns_none(self):
        self.assertIsNone(winlayer.resolve_monitor([], "primary"))

    def test_no_primary_flag_falls_back_to_first(self):
        monitors = [{"index": 1, "rect": (0, 0, 100, 100), "primary": False}]
        m = winlayer.resolve_monitor(monitors, "primary")
        self.assertEqual(m["index"], 1)


class SanitizeWindowedGeometryTests(unittest.TestCase):
    RECT = (0, 0, 1920, 1080)

    def test_defaults_center_at_60_percent(self):
        geom = winlayer.sanitize_windowed_geometry({}, self.RECT)
        self.assertEqual(geom["width"], int(1920 * 0.6))
        self.assertEqual(geom["height"], int(1080 * 0.6))

    def test_explicit_geometry_respected(self):
        geom = winlayer.sanitize_windowed_geometry(
            {"x": 100, "y": 50, "width": 800, "height": 600}, self.RECT)
        self.assertEqual(geom, {"x": 100, "y": 50, "width": 800, "height": 600})

    def test_oversized_dimensions_clamped_to_monitor(self):
        geom = winlayer.sanitize_windowed_geometry(
            {"width": 99999, "height": 99999}, self.RECT)
        self.assertLessEqual(geom["width"], 1920)
        self.assertLessEqual(geom["height"], 1080)

    def test_non_numeric_values_fall_back_to_default(self):
        geom = winlayer.sanitize_windowed_geometry(
            {"width": "not-a-number"}, self.RECT)
        self.assertEqual(geom["width"], int(1920 * 0.6))

    def test_none_geometry_does_not_crash(self):
        geom = winlayer.sanitize_windowed_geometry(None, self.RECT)
        self.assertIn("width", geom)


class WallpaperReparenterTests(unittest.TestCase):
    def _reparenter(self, progman=1, workerw=2, set_parent_ok=True, has_child=True):
        return winlayer.WallpaperReparenter(
            find_progman=lambda: progman,
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [10, 11],
            has_shelldll_defview_child=lambda h: has_child if h == 11 else False,
            get_next_sibling=lambda h: workerw,
            set_parent=lambda hwnd, parent: set_parent_ok,
        )

    def test_happy_path(self):
        r = self._reparenter()
        ok, reason = r.reparent(hwnd=999)
        self.assertTrue(ok)
        self.assertEqual(reason, "ok")

    def test_no_progman_fails(self):
        r = self._reparenter(progman=None)
        ok, reason = r.reparent(hwnd=999)
        self.assertFalse(ok)
        self.assertIn("Progman", reason)

    def test_no_workerw_found_fails(self):
        r = self._reparenter(has_child=False)
        ok, reason = r.reparent(hwnd=999)
        self.assertFalse(ok)
        self.assertIn("WorkerW", reason)

    def test_set_parent_failure(self):
        r = self._reparenter(set_parent_ok=False)
        ok, reason = r.reparent(hwnd=999)
        self.assertFalse(ok)
        self.assertIn("SetParent", reason)

    def test_win11_workerw_child_of_progman(self):
        # Win11 22H2+: no WorkerW sibling appears; the spawned WorkerW is a
        # child of Progman and must be found via find_workerw_child.
        parents = []
        r = winlayer.WallpaperReparenter(
            find_progman=lambda: 1,
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [10, 11],
            has_shelldll_defview_child=lambda h: False,
            get_next_sibling=lambda h: None,
            set_parent=lambda hwnd, parent: parents.append(parent) or True,
            find_workerw_child=lambda progman: 42 if progman == 1 else None,
        )
        ok, reason = r.reparent(hwnd=999)
        self.assertTrue(ok)
        self.assertEqual(parents, [42])

    def test_win11_no_workerw_child_still_fails(self):
        r = winlayer.WallpaperReparenter(
            find_progman=lambda: 1,
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [10],
            has_shelldll_defview_child=lambda h: False,
            get_next_sibling=lambda h: None,
            set_parent=lambda hwnd, parent: True,
            find_workerw_child=lambda progman: None,
        )
        ok, reason = r.reparent(hwnd=999)
        self.assertFalse(ok)
        self.assertIn("WorkerW", reason)


class _FakeWin32Ops:
    def __init__(self):
        self.pos_calls = []
        self.style_calls = []
        self.parent_calls = []

    def set_window_pos(self, hwnd, insert_after, x, y, w, h, flags):
        self.pos_calls.append((hwnd, insert_after, x, y, w, h, flags))

    def set_window_style(self, hwnd, popup, caption, thickframe):
        self.style_calls.append((hwnd, popup, caption, thickframe))

    def set_parent(self, hwnd, new_parent):
        self.parent_calls.append((hwnd, new_parent))


class LayerEngineTests(unittest.TestCase):
    def _engine(self, ops=None):
        return winlayer.LayerEngine(
            enumerate_monitors=lambda: MONITORS,
            wallpaper_reparenter=None,
            win32_ops=ops or _FakeWin32Ops(),
        )

    def test_top_layer_uses_topmost(self):
        ops = _FakeWin32Ops()
        engine = self._engine(ops)
        effective = engine.apply(123, {"layer": "top", "monitor": "primary"})
        self.assertEqual(effective, "top")
        self.assertEqual(ops.pos_calls[-1][1], winlayer._HWND_TOPMOST)
        engine.stop_reassert()

    def test_bottom_layer_uses_hwnd_bottom(self):
        ops = _FakeWin32Ops()
        engine = self._engine(ops)
        effective = engine.apply(123, {"layer": "bottom", "monitor": "primary"})
        self.assertEqual(effective, "bottom")
        self.assertEqual(ops.pos_calls[-1][1], winlayer._HWND_BOTTOM)
        engine.stop_reassert()

    def test_windowed_layer_uses_requested_geometry(self):
        ops = _FakeWin32Ops()
        engine = self._engine(ops)
        effective = engine.apply(123, {
            "layer": "windowed", "monitor": "primary",
            "windowed": {"x": 10, "y": 20, "width": 640, "height": 480},
        })
        self.assertEqual(effective, "windowed")
        call = ops.pos_calls[-1]
        self.assertEqual(call[2:6], (10, 20, 640, 480))
        engine.stop_reassert()

    def test_wallpaper_success(self):
        ops = _FakeWin32Ops()
        reparenter = winlayer.WallpaperReparenter(
            find_progman=lambda: 1,
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [11],
            has_shelldll_defview_child=lambda h: True,
            get_next_sibling=lambda h: 2,
            set_parent=lambda hwnd, parent: True,
        )
        engine = winlayer.LayerEngine(
            enumerate_monitors=lambda: MONITORS,
            wallpaper_reparenter=reparenter,
            win32_ops=ops,
        )
        effective = engine.apply(123, {"layer": "wallpaper", "monitor": "primary"})
        self.assertEqual(effective, "wallpaper")
        engine.stop_reassert()

    def test_leaving_wallpaper_unparents_back_to_desktop(self):
        ops = _FakeWin32Ops()
        reparenter = winlayer.WallpaperReparenter(
            find_progman=lambda: 1,
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [11],
            has_shelldll_defview_child=lambda h: True,
            get_next_sibling=lambda h: 2,
            set_parent=lambda hwnd, parent: True,
        )
        engine = winlayer.LayerEngine(
            enumerate_monitors=lambda: MONITORS,
            wallpaper_reparenter=reparenter,
            win32_ops=ops,
        )
        engine.apply(123, {"layer": "wallpaper", "monitor": "primary"})
        effective = engine.apply(123, {"layer": "top", "monitor": "primary"})
        self.assertEqual(effective, "top")
        # the window must be restored to a top-level desktop window first
        self.assertEqual(ops.parent_calls, [(123, None)])
        # and not re-unparented on subsequent non-wallpaper applies
        engine.apply(123, {"layer": "bottom", "monitor": "primary"})
        self.assertEqual(ops.parent_calls, [(123, None)])
        engine.stop_reassert()

    def test_wallpaper_failure_falls_back_to_bottom(self):
        ops = _FakeWin32Ops()
        reparenter = winlayer.WallpaperReparenter(
            find_progman=lambda: None,  # Progman missing -> forces failure
            send_progman_spawn=lambda h: None,
            enum_top_windows=lambda: [],
            has_shelldll_defview_child=lambda h: False,
            get_next_sibling=lambda h: None,
            set_parent=lambda hwnd, parent: True,
        )
        engine = winlayer.LayerEngine(
            enumerate_monitors=lambda: MONITORS,
            wallpaper_reparenter=reparenter,
            win32_ops=ops,
        )
        effective = engine.apply(123, {"layer": "wallpaper", "monitor": "primary"})
        self.assertEqual(effective, "bottom")
        self.assertEqual(ops.pos_calls[-1][1], winlayer._HWND_BOTTOM)
        engine.stop_reassert()

    def test_no_monitor_available_leaves_layer_unapplied(self):
        ops = _FakeWin32Ops()
        engine = winlayer.LayerEngine(
            enumerate_monitors=lambda: [], wallpaper_reparenter=None, win32_ops=ops)
        effective = engine.apply(123, {"layer": "top", "monitor": "primary"})
        self.assertEqual(effective, "top")
        self.assertEqual(ops.pos_calls, [])  # never touched Win32 -- no display attached

    def test_unknown_layer_defaults_to_top(self):
        ops = _FakeWin32Ops()
        engine = self._engine(ops)
        effective = engine.apply(123, {"layer": "sideways", "monitor": "primary"})
        self.assertEqual(effective, "top")
        engine.stop_reassert()


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