"""Screen overlay visualization for LED calibration testing."""

from __future__ import annotations

import colorsys
import logging
import sys
import threading
from typing import TYPE_CHECKING, Dict, List, Optional

if TYPE_CHECKING:
    import tkinter as tk

from wled_controller.core.capture.calibration import CalibrationConfig
from wled_controller.core.capture_engines.base import DisplayInfo

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# OverlayWindow — one Toplevel per target, no own thread/mainloop
# ---------------------------------------------------------------------------

class OverlayWindow:
    """Transparent overlay window for LED calibration visualization.

    Does NOT own a Tcl interpreter or event loop — it is a Toplevel child of
    the shared root managed by OverlayManager.  All methods must be called
    from the Tkinter thread.
    """

    def __init__(
        self,
        display_info: DisplayInfo,
        calibration: CalibrationConfig,
        target_id: str,
        target_name: str = None,
    ):
        self.display_info = display_info
        self.calibration = calibration
        self.target_id = target_id
        self.target_name = target_name or target_id
        self._window: Optional[tk.Toplevel] = None
        self._canvas: Optional[tk.Canvas] = None
        self.running = False

    # ----- Lifecycle (must run in Tk thread) -----

    def start(self, root: tk.Tk) -> None:
        """Create and show the overlay Toplevel (runs in Tk thread)."""
        import tkinter as tk  # lazy import — tkinter unavailable in headless CI

        self._window = tk.Toplevel(root)
        self._setup_window()
        self._draw_visualization()
        self.running = True
        logger.info(f"Overlay window started for {self.target_id}")

    def stop(self) -> None:
        """Destroy the overlay Toplevel (runs in Tk thread)."""
        if self._window is not None:
            try:
                self._window.destroy()
            except Exception as e:
                logger.debug(f"Cleanup error for {self.target_id}: {e}")
            self._window = None
            self._canvas = None
        self.running = False
        logger.info(f"Overlay window stopped for {self.target_id}")

    # ----- Window setup -----

    def _setup_window(self) -> None:
        win = self._window
        geometry = (
            f"{self.display_info.width}x{self.display_info.height}"
            f"+{self.display_info.x}+{self.display_info.y}"
        )
        win.geometry(geometry)
        win.overrideredirect(True)
        win.attributes("-topmost", True)

        import tkinter as tk

        self._canvas = tk.Canvas(
            win,
            width=self.display_info.width,
            height=self.display_info.height,
            highlightthickness=0,
            bg="black",
        )
        self._canvas.pack()

        win.wm_attributes("-transparentcolor", "black")
        win.attributes("-alpha", 0.85)

        if sys.platform == "win32":
            try:
                import ctypes
                hwnd = ctypes.windll.user32.GetParent(win.winfo_id())
                style = ctypes.windll.user32.GetWindowLongW(hwnd, -20)  # GWL_EXSTYLE
                style |= 0x80000 | 0x20  # WS_EX_LAYERED | WS_EX_TRANSPARENT
                ctypes.windll.user32.SetWindowLongW(hwnd, -20, style)
            except Exception as e:
                logger.warning(f"Could not set click-through: {e}")

    # ----- Drawing -----

    def _draw_visualization(self) -> None:
        w = self.display_info.width
        h = self.display_info.height
        bw = self.calibration.border_width
        self._draw_border_zones(w, h, bw)
        self._draw_led_axes(w, h, bw)
        self._draw_info_text(w, h)

    def _draw_border_zones(self, w: int, h: int, bw: int) -> None:
        c = self._canvas
        c.create_rectangle(0, 0, w, bw, fill="#FF0000", stipple="gray25", outline="#FF0000", width=2)
        c.create_rectangle(w - bw, 0, w, h, fill="#00FF00", stipple="gray25", outline="#00FF00", width=2)
        c.create_rectangle(0, h - bw, w, h, fill="#0000FF", stipple="gray25", outline="#0000FF", width=2)
        c.create_rectangle(0, 0, bw, h, fill="#FFFF00", stipple="gray25", outline="#FFFF00", width=2)

    def _draw_led_axes(self, w: int, h: int, bw: int) -> None:
        segments = self.calibration.segments
        total_leds = self.calibration.get_total_leds()

        if total_leds <= 50:
            tick_interval = 5
        elif total_leds <= 100:
            tick_interval = 10
        elif total_leds <= 200:
            tick_interval = 20
        else:
            tick_interval = 50

        led_index = 0
        for seg in segments:
            edge = seg.edge
            count = seg.led_count
            span_start, span_end = self.calibration.get_edge_span(edge)

            if edge == "top":
                edge_len = w
                start_px = span_start * edge_len
                end_px = span_end * edge_len
                y_axis = bw / 2
                self._canvas.create_line(start_px, y_axis, end_px, y_axis, fill="white", width=3)
                for i in range(count):
                    display_idx = (led_index + self.calibration.offset) % total_leds
                    if i == 0 or i == count - 1 or display_idx % tick_interval == 0:
                        frac = i / (count - 1) if count > 1 else 0.5
                        if seg.reverse:
                            frac = 1.0 - frac
                        x = start_px + frac * (end_px - start_px)
                        tick_len = 15 if display_idx % tick_interval == 0 else 10
                        self._canvas.create_line(x, y_axis - tick_len, x, y_axis + tick_len, fill="white", width=2)
                        label_y = bw + 20
                        self._canvas.create_rectangle(x - 30, label_y - 12, x + 30, label_y + 12, fill="white", outline="black", width=2)
                        self._canvas.create_text(x, label_y, text=str(display_idx), fill="black", font=("Arial", 13, "bold"))
                    led_index += 1

            elif edge == "bottom":
                edge_len = w
                start_px = span_start * edge_len
                end_px = span_end * edge_len
                y_axis = h - bw / 2
                self._canvas.create_line(start_px, y_axis, end_px, y_axis, fill="white", width=3)
                for i in range(count):
                    display_idx = (led_index + self.calibration.offset) % total_leds
                    if i == 0 or i == count - 1 or display_idx % tick_interval == 0:
                        frac = i / (count - 1) if count > 1 else 0.5
                        if seg.reverse:
                            frac = 1.0 - frac
                        x = start_px + frac * (end_px - start_px)
                        tick_len = 15 if display_idx % tick_interval == 0 else 10
                        self._canvas.create_line(x, y_axis - tick_len, x, y_axis + tick_len, fill="white", width=2)
                        label_y = h - bw - 20
                        self._canvas.create_rectangle(x - 30, label_y - 12, x + 30, label_y + 12, fill="white", outline="black", width=2)
                        self._canvas.create_text(x, label_y, text=str(display_idx), fill="black", font=("Arial", 13, "bold"))
                    led_index += 1

            elif edge == "left":
                edge_len = h
                start_px = span_start * edge_len
                end_px = span_end * edge_len
                x_axis = bw / 2
                self._canvas.create_line(x_axis, start_px, x_axis, end_px, fill="white", width=3)
                for i in range(count):
                    display_idx = (led_index + self.calibration.offset) % total_leds
                    if i == 0 or i == count - 1 or display_idx % tick_interval == 0:
                        frac = i / (count - 1) if count > 1 else 0.5
                        if seg.reverse:
                            frac = 1.0 - frac
                        y = start_px + frac * (end_px - start_px)
                        tick_len = 15 if display_idx % tick_interval == 0 else 10
                        self._canvas.create_line(x_axis - tick_len, y, x_axis + tick_len, y, fill="white", width=2)
                        label_x = bw + 40
                        self._canvas.create_rectangle(label_x - 30, y - 12, label_x + 30, y + 12, fill="white", outline="black", width=2)
                        self._canvas.create_text(label_x, y, text=str(display_idx), fill="black", font=("Arial", 13, "bold"))
                    led_index += 1

            elif edge == "right":
                edge_len = h
                start_px = span_start * edge_len
                end_px = span_end * edge_len
                x_axis = w - bw / 2
                self._canvas.create_line(x_axis, start_px, x_axis, end_px, fill="white", width=3)
                for i in range(count):
                    display_idx = (led_index + self.calibration.offset) % total_leds
                    if i == 0 or i == count - 1 or display_idx % tick_interval == 0:
                        frac = i / (count - 1) if count > 1 else 0.5
                        if seg.reverse:
                            frac = 1.0 - frac
                        y = start_px + frac * (end_px - start_px)
                        tick_len = 15 if display_idx % tick_interval == 0 else 10
                        self._canvas.create_line(x_axis - tick_len, y, x_axis + tick_len, y, fill="white", width=2)
                        label_x = w - bw - 40
                        self._canvas.create_rectangle(label_x - 30, y - 12, label_x + 30, y + 12, fill="white", outline="black", width=2)
                        self._canvas.create_text(label_x, y, text=str(display_idx), fill="black", font=("Arial", 13, "bold"))
                    led_index += 1

    def _draw_info_text(self, w: int, h: int) -> None:
        info_lines = [
            f"Target: {self.target_name}",
            f"Total LEDs: {self.calibration.get_total_leds()}",
            f"Start: {self.calibration.start_position.replace('_', ' ').title()}",
            f"Direction: {'CW' if self.calibration.layout == 'clockwise' else 'CCW'}",
            f"Offset: {self.calibration.offset}",
            f"Border Width: {self.calibration.border_width}px",
            "",
            f"Top: {self.calibration.leds_top} LEDs",
            f"Right: {self.calibration.leds_right} LEDs",
            f"Bottom: {self.calibration.leds_bottom} LEDs",
            f"Left: {self.calibration.leds_left} LEDs",
        ]
        text_x = 30
        text_y = 30
        line_height = 28
        padding = 20
        box_width = 320
        box_height = len(info_lines) * line_height + padding * 2
        self._canvas.create_rectangle(
            text_x - padding, text_y - padding,
            text_x + box_width, text_y + box_height,
            fill="#1a1a1a", outline="white", width=3,
        )
        for i, line in enumerate(info_lines):
            self._canvas.create_text(
                text_x, text_y + i * line_height,
                text=line, anchor="nw", fill="yellow", font=("Arial", 14, "bold"),
            )

    @staticmethod
    def _hsv_to_rgb(h: float, s: float, v: float) -> str:
        r, g, b = colorsys.hsv_to_rgb(h / 360, s, v)
        return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"


# ---------------------------------------------------------------------------
# OverlayManager — single persistent Tk root, all ops via root.after()
# ---------------------------------------------------------------------------

class OverlayManager:
    """Manages overlay windows. Uses one persistent hidden Tk root so that
    Toplevel windows can be created/destroyed repeatedly without crashing
    the Tcl interpreter."""

    def __init__(self):
        self._overlays: Dict[str, OverlayWindow] = {}
        self._lock = threading.Lock()
        self._tk_root: Optional[tk.Tk] = None
        self._tk_thread: Optional[threading.Thread] = None
        self._tk_ready = threading.Event()
        self._start_tk_thread()

    # ----- Internal: persistent Tk thread -----

    def _start_tk_thread(self) -> None:
        def _run():
            try:
                import tkinter as tk  # lazy import — tkinter unavailable in embedded Python / headless CI
            except ImportError:
                logger.warning("tkinter not available — screen overlay disabled")
                self._tk_ready.set()
                return

            try:
                self._tk_root = tk.Tk()
                self._tk_root.withdraw()  # invisible root — never shown
                self._tk_ready.set()
                self._tk_root.mainloop()
            except Exception as e:
                logger.error(f"Tkinter thread crashed: {e}", exc_info=True)
                self._tk_ready.set()  # unblock waiters even on failure

        self._tk_thread = threading.Thread(
            target=_run, name="TkOverlayRoot", daemon=True
        )
        self._tk_thread.start()
        self._tk_ready.wait(timeout=5.0)

    def _run_in_tk(self, fn) -> None:
        """Schedule *fn* on the Tk thread and block until it completes."""
        if self._tk_root is None:
            raise RuntimeError("Tkinter root not available")
        done = threading.Event()
        exc_box: List[Optional[BaseException]] = [None]

        def wrapper():
            try:
                fn()
            except Exception as e:
                exc_box[0] = e
            finally:
                done.set()

        self._tk_root.after(0, wrapper)
        done.wait(timeout=5.0)
        if exc_box[0] is not None:
            raise exc_box[0]

    # ----- Public API -----

    def start_overlay(
        self,
        target_id: str,
        display_info: DisplayInfo,
        calibration: CalibrationConfig,
        target_name: str = None,
    ) -> None:
        with self._lock:
            if target_id in self._overlays:
                raise RuntimeError(f"Overlay already running for {target_id}")

            overlay = OverlayWindow(display_info, calibration, target_id, target_name)
            self._run_in_tk(lambda: overlay.start(self._tk_root))

            if not overlay.running:
                raise RuntimeError(f"Overlay failed to start for {target_id}")

            self._overlays[target_id] = overlay
            logger.info(f"Started overlay for target {target_id}")

    def stop_overlay(self, target_id: str) -> None:
        with self._lock:
            overlay = self._overlays.pop(target_id, None)
            if overlay:
                self._run_in_tk(overlay.stop)
                logger.info(f"Stopped overlay for target {target_id}")

    def is_running(self, target_id: str) -> bool:
        with self._lock:
            return target_id in self._overlays

    def stop_all(self) -> None:
        with self._lock:
            for target_id in list(self._overlays.keys()):
                overlay = self._overlays.pop(target_id)
                self._run_in_tk(overlay.stop)
            logger.info("Stopped all overlays")
