"""Windows Overtake indicator: desktop edge, stop banner and agent cursor.

Tk owns small, click-through top-level windows so the indicator stays above
Chrome and other applications without stealing focus or intercepting input.
Importing this module is safe on every platform; platform APIs and Tk are
touched only when a Windows overlay starts.
"""

from __future__ import annotations

from dataclasses import dataclass
import math
import sys
import threading
import time
from typing import Protocol

from .state import OverlaySnapshot, OverlayStateStore


TRANSPARENT_KEY = "#010203"


@dataclass(frozen=True, slots=True)
class OverlayStatus:
    available: bool
    running: bool
    backend: str
    error: str | None = None

    def as_dict(self) -> dict[str, object]:
        return {
            "available": self.available,
            "running": self.running,
            "backend": self.backend,
            "error": self.error,
        }


class Overlay(Protocol):
    @property
    def status(self) -> OverlayStatus: ...

    def start(self) -> bool: ...

    def stop(self) -> None: ...


class NullOverlay:
    """Graceful no-op for non-Windows and headless sessions."""

    def __init__(self, reason: str = "overlay disabled") -> None:
        self._reason = reason

    @property
    def status(self) -> OverlayStatus:
        return OverlayStatus(False, False, "null", self._reason)

    def start(self) -> bool:
        return False

    def stop(self) -> None:
        return None


def scaled_cursor_diameter(system_cursor_pixels: int, scale: float = 3.0) -> int:
    """Size of the visible agent halo relative to the current Windows cursor."""

    return max(12, int(round(system_cursor_pixels * scale)))


def _geometry(width: int, height: int, x: int, y: int) -> str:
    x_part = f"+{x}" if x >= 0 else str(x)
    y_part = f"+{y}" if y >= 0 else str(y)
    return f"{width}x{height}{x_part}{y_part}"


class WindowsOverlay:
    """Native edge, banner and cursor windows rendered on a Tk thread."""

    _WS_EX_TRANSPARENT = 0x00000020
    _WS_EX_TOOLWINDOW = 0x00000080
    _WS_EX_LAYERED = 0x00080000
    _WS_EX_NOACTIVATE = 0x08000000
    _GWL_EXSTYLE = -20

    def __init__(
        self,
        state: OverlayStateStore,
        *,
        cursor_scale: float = 3.0,
        fps: int = 60,
    ) -> None:
        self._state = state
        self._cursor_scale = cursor_scale
        self._frame_ms = max(16, int(1000 / max(1, fps)))
        self._stop = threading.Event()
        self._ready = threading.Event()
        self._thread: threading.Thread | None = None
        self._root = None
        self._edges: list[object] = []
        self._edge_canvases: list[object] = []
        self._banner = None
        self._banner_canvas = None
        self._halo = None
        self._halo_canvas = None
        self._bounds = (0, 0, 1920, 1080)
        self._halo_size = 40
        self._edge_signature: tuple[object, ...] | None = None
        self._banner_visible = False
        self._status = OverlayStatus(True, False, "tk-windows")

    @property
    def status(self) -> OverlayStatus:
        return self._status

    def start(self) -> bool:
        if sys.platform != "win32":
            self._status = OverlayStatus(False, False, "null", "not running on Windows")
            return False
        if self._thread and self._thread.is_alive():
            return True
        self._stop.clear()
        self._ready.clear()
        self._thread = threading.Thread(target=self._run, name="omni-desktop-overlay", daemon=True)
        self._thread.start()
        self._ready.wait(timeout=2.0)
        return self._status.running

    def stop(self) -> None:
        self._stop.set()
        thread = self._thread
        if thread and thread.is_alive() and thread is not threading.current_thread():
            thread.join(timeout=2.0)

    def _run(self) -> None:
        try:
            import tkinter as tk

            root = tk.Tk()
            root.withdraw()
            self._root = root
            self._bounds = self._virtual_screen_bounds(root)
            self._halo_size = self._system_cursor_diameter()
            self._edges, self._edge_canvases = self._make_edge_windows(root, tk)
            self._banner, self._banner_canvas = self._make_banner_window(root, tk)
            self._halo, self._halo_canvas = self._make_halo_window(root, tk)
            self._status = OverlayStatus(True, True, "tk-windows")
            self._ready.set()
            root.after(self._frame_ms, self._tick)
            root.mainloop()
        except Exception as exc:  # TkError and absent interactive desktop both land here.
            self._status = OverlayStatus(False, False, "null", f"overlay unavailable: {type(exc).__name__}")
            self._ready.set()
        finally:
            self._destroy()
            self._status = OverlayStatus(
                self._status.available,
                False,
                self._status.backend,
                self._status.error,
            )
            self._ready.set()

    def _make_edge_windows(self, root: object, tk: object) -> tuple[list[object], list[object]]:
        """Use four narrow native windows instead of one transparent screen.

        A full-screen color-keyed Tk window can disappear behind Chromium's
        app-mode surface on some Windows builds. Four opaque, click-through
        strips remain visible over every application and touch only the edge.
        """

        x, y, width, height = self._bounds
        thickness = 7
        geometries = (
            (width, thickness, x, y),
            (width, thickness, x, y + height - thickness),
            (thickness, max(1, height - thickness * 2), x, y + thickness),
            (thickness, max(1, height - thickness * 2), x + width - thickness, y + thickness),
        )
        windows: list[object] = []
        canvases: list[object] = []
        for edge_width, edge_height, left, top in geometries:
            window = tk.Toplevel(root)
            self._configure_window(window, 0.82)
            window.geometry(_geometry(edge_width, edge_height, left, top))
            canvas = tk.Canvas(window, bg=TRANSPARENT_KEY, highlightthickness=0, bd=0)
            canvas.pack(fill="both", expand=True)
            window.withdraw()
            window.update_idletasks()
            self._make_click_through(window)
            windows.append(window)
            canvases.append(canvas)
        return windows, canvases

    def _make_banner_window(self, root: object, tk: object) -> tuple[object, object]:
        _, y, _, _ = self._bounds
        width, height = 410, 52
        window = tk.Toplevel(root)
        self._configure_window(window, 0.97)
        window.geometry(_geometry(width, height, 0, y + 16))
        canvas = tk.Canvas(window, width=width, height=height, bg=TRANSPARENT_KEY, highlightthickness=0, bd=0)
        canvas.pack(fill="both", expand=True)
        window.withdraw()
        window.update_idletasks()
        self._make_click_through(window)
        return window, canvas

    def _make_halo_window(self, root: object, tk: object) -> tuple[object, object]:
        outer = self._halo_size + 12
        window = tk.Toplevel(root)
        self._configure_window(window, 0.78)
        window.geometry(_geometry(outer, outer, 0, 0))
        canvas = tk.Canvas(
            window,
            width=outer,
            height=outer,
            bg=TRANSPARENT_KEY,
            highlightthickness=0,
            bd=0,
        )
        canvas.pack(fill="both", expand=True)
        window.withdraw()
        window.update_idletasks()
        self._make_click_through(window)
        return window, canvas

    @staticmethod
    def _configure_window(window: object, alpha: float) -> None:
        window.overrideredirect(True)
        window.attributes("-topmost", True)
        window.attributes("-alpha", alpha)
        window.configure(bg=TRANSPARENT_KEY)
        try:
            window.attributes("-transparentcolor", TRANSPARENT_KEY)
        except Exception:
            pass

    def _make_click_through(self, window: object) -> None:
        import ctypes

        hwnd = int(window.winfo_id())
        user32 = ctypes.windll.user32
        style = user32.GetWindowLongW(hwnd, self._GWL_EXSTYLE)
        style |= (
            self._WS_EX_LAYERED
            | self._WS_EX_TRANSPARENT
            | self._WS_EX_TOOLWINDOW
            | self._WS_EX_NOACTIVATE
        )
        user32.SetWindowLongW(hwnd, self._GWL_EXSTYLE, style)

    def _tick(self) -> None:
        if self._stop.is_set():
            self._destroy()
            return
        snapshot = self._state.snapshot()
        if (
            snapshot.overtake_active
            or snapshot.active
            or snapshot.remote_active
            or snapshot.camera_active
        ) and not snapshot.killed:
            self._render(snapshot)
        else:
            self._hide()
        if self._root is not None:
            self._root.after(self._frame_ms, self._tick)

    def _render(self, snapshot: OverlaySnapshot) -> None:
        if not self._edges or not self._edge_canvases or not all((self._halo, self._halo_canvas)):
            return
        pulse = 0.5 + (0.5 * math.sin(time.monotonic() * 5.0))
        edge_color = (
            "#ff746d"
            if snapshot.camera_active
            else "#74d9ff"
            if snapshot.remote_active
            else "#f1c453"
            if snapshot.overtake_active
            else snapshot.color
        )
        visual_intensity = max(snapshot.intensity, 0.74 if snapshot.camera_active else 0.52 if snapshot.remote_active else 0.0)
        if snapshot.overtake_active:
            self._show_overtake_banner()
        else:
            self._hide_overtake_banner()

        edge_signature = (
            edge_color,
            round(visual_intensity, 2),
            snapshot.remote_active,
            snapshot.remote_node_name,
            snapshot.camera_active,
            snapshot.camera_device_name,
        )
        if edge_signature != self._edge_signature:
            self._edge_signature = edge_signature
            for canvas in self._edge_canvases:
                canvas.configure(bg=edge_color)
        for window in self._edges:
            window.deiconify()
            window.lift()
        if self._banner_visible and self._banner is not None:
            self._banner.lift()

        if not snapshot.active and not snapshot.overtake_active:
            self._halo.withdraw()
            return
        pointer_x, pointer_y = self._pointer(snapshot)
        outer = self._halo_size + 12
        left = int(pointer_x - outer / 2)
        top = int(pointer_y - outer / 2)
        self._halo.geometry(_geometry(outer, outer, left, top))
        self._halo_canvas.delete("all")
        margin = 6 + int(pulse * 2)
        self._halo_canvas.create_oval(
            margin,
            margin,
            outer - margin,
            outer - margin,
            outline=snapshot.color,
            width=max(2, int(2 + snapshot.intensity * 2)),
        )
        self._draw_cursor(snapshot, outer, pulse)
        self._halo.deiconify()
        self._halo.lift()

    def _show_overtake_banner(self) -> None:
        if self._banner is None or self._banner_canvas is None:
            return
        origin_x, origin_y, screen_width, _ = self._bounds
        if self._banner_visible:
            return
        width, height = 410, 52
        self._banner.geometry(_geometry(width, height, origin_x + int((screen_width - width) / 2), origin_y + 16))
        canvas = self._banner_canvas
        canvas.delete("all")
        radius = 12
        points = [
            4 + radius, 4, width - 4 - radius, 4,
            width - 4, 4, width - 4, 4 + radius,
            width - 4, height - 4 - radius, width - 4, height - 4,
            width - 4 - radius, height - 4, 4 + radius, height - 4,
            4, height - 4, 4, height - 4 - radius,
            4, 4 + radius, 4, 4,
        ]
        canvas.create_polygon(points, smooth=True, splinesteps=24, fill="#121820", outline="#384453", width=1)
        canvas.create_oval(18, 21, 28, 31, fill="#f2c23e", outline="")
        canvas.create_text(40, 26, text="Nelloz is using your computer", fill="#f7f9fc", anchor="w", font=("Segoe UI", 10, "bold"))
        canvas.create_line(286, 15, 286, 37, fill="#3b4654", width=1)
        canvas.create_rectangle(303, 15, 343, 37, fill="#202a35", outline="#667383", width=1)
        canvas.create_text(323, 26, text="ESC", fill="#fff1bd", anchor="center", font=("Segoe UI", 8, "bold"))
        canvas.create_text(355, 26, text="Stop", fill="#d5dde8", anchor="w", font=("Segoe UI", 9))
        self._banner.deiconify()
        self._banner.lift()
        self._banner_visible = True

    def _hide_overtake_banner(self) -> None:
        for window in (self._banner,):
            if window is not None:
                try:
                    window.withdraw()
                except Exception:
                    pass
        self._banner_visible = False

    def _draw_cursor(self, snapshot: OverlaySnapshot, outer: int, pulse: float) -> None:
        if self._halo_canvas is None:
            return
        center = outer / 2
        color = "#f2c23e" if snapshot.overtake_active else snapshot.color
        if snapshot.activity.value == "click":
            ring = 9 + int(pulse * 5)
            self._halo_canvas.create_oval(
                center - ring,
                center - ring,
                center + ring,
                center + ring,
                outline="#f8fbff",
                width=2,
            )
        elif snapshot.activity.value == "type":
            ring = 7 + int(pulse * 2)
            self._halo_canvas.create_oval(
                center - ring,
                center - ring,
                center + ring,
                center + ring,
                outline="#d1baff",
                width=1,
            )
        cursor_scale = max(1.4, min(3.0, self._halo_size / 32.0))
        pointer = [
            center - (7 * cursor_scale), center - (13 * cursor_scale),
            center + (10 * cursor_scale), center + (5 * cursor_scale),
            center + (2 * cursor_scale), center + (6 * cursor_scale),
            center + (6 * cursor_scale), center + (14 * cursor_scale),
            center + (2 * cursor_scale), center + (16 * cursor_scale),
            center - (2 * cursor_scale), center + (8 * cursor_scale),
            center - (10 * cursor_scale), center + (11 * cursor_scale),
        ]
        self._halo_canvas.create_polygon(
            pointer,
            fill="#fbfdff",
            outline="#102039" if color != "#ff5d54" else "#5a1616",
            width=1,
        )

    def _pointer(self, snapshot: OverlaySnapshot) -> tuple[float, float]:
        origin_x, origin_y, width, height = self._bounds
        if snapshot.pointer_x is not None and snapshot.pointer_y is not None:
            if snapshot.pointer_normalized:
                return (
                    origin_x + max(0.0, min(1.0, snapshot.pointer_x)) * width,
                    origin_y + max(0.0, min(1.0, snapshot.pointer_y)) * height,
                )
            return snapshot.pointer_x, snapshot.pointer_y
        try:
            import ctypes
            from ctypes import wintypes

            point = wintypes.POINT()
            if ctypes.windll.user32.GetCursorPos(ctypes.byref(point)):
                return float(point.x), float(point.y)
        except Exception:
            pass
        return origin_x + width / 2, origin_y + height / 2

    def _hide(self) -> None:
        for window in (*self._edges, self._banner, self._halo):
            if window is not None:
                try:
                    window.withdraw()
                except Exception:
                    pass
        self._edge_signature = None
        self._banner_visible = False

    def _destroy(self) -> None:
        self._hide()
        root, self._root = self._root, None
        if root is not None:
            try:
                root.destroy()
            except Exception:
                pass

    def _system_cursor_diameter(self) -> int:
        try:
            import ctypes

            user32 = ctypes.windll.user32
            system_size = max(user32.GetSystemMetrics(13), user32.GetSystemMetrics(14), 32)
        except Exception:
            system_size = 32
        return scaled_cursor_diameter(system_size, self._cursor_scale)

    @staticmethod
    def _virtual_screen_bounds(root: object) -> tuple[int, int, int, int]:
        try:
            import ctypes

            user32 = ctypes.windll.user32
            x = int(user32.GetSystemMetrics(76))
            y = int(user32.GetSystemMetrics(77))
            width = int(user32.GetSystemMetrics(78))
            height = int(user32.GetSystemMetrics(79))
            if width > 0 and height > 0:
                return x, y, width, height
        except Exception:
            pass
        return 0, 0, int(root.winfo_screenwidth()), int(root.winfo_screenheight())


def create_overlay(
    state: OverlayStateStore,
    *,
    enabled: bool = True,
    cursor_scale: float = 3.0,
    platform_name: str | None = None,
) -> Overlay:
    platform_name = sys.platform if platform_name is None else platform_name
    if not enabled:
        return NullOverlay("overlay disabled by configuration")
    if platform_name != "win32":
        return NullOverlay("overlay is available on interactive Windows desktops only")
    return WindowsOverlay(state, cursor_scale=cursor_scale)
