"""Always-on-top agent activity pill for Linux desktop sessions."""

from __future__ import annotations

import logging
import sys
import threading
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Callable

from iuve_connect_agents.shared.agent_status_i18n import (
    agent_status_label,
    normalize_agent_status_locale,
    resolve_agent_status_locale,
)

logger = logging.getLogger(__name__)

OVERLAY_MARGIN_PX = 12
ERROR_VISIBLE_MS = 8000

_OVERLAY_CSS = b"""
window.agent-status-overlay {
  background-color: transparent;
}
box.agent-status-pill {
  background-color: alpha(#111111, 0.78);
  border-radius: 18px;
  padding: 8px 14px;
}
label.agent-status-text {
  color: #f5f5f5;
  font-size: 13px;
  font-weight: 600;
}
box.agent-status-dot {
  min-width: 10px;
  min-height: 10px;
  border-radius: 999px;
}
box.agent-status-dot.working {
  background-color: #34c759;
}
box.agent-status-dot.error {
  background-color: #ff3b30;
}
box.agent-status-dot.blocked {
  background-color: #ff9500;
}
box.agent-status-dot.idle {
  background-color: #8e8e93;
}
"""


class AgentStatusState(str, Enum):
    IDLE = "idle"
    WORKING = "working"
    ERROR = "error"
    BLOCKED = "blocked"


def _coerce_agent_status_state(state: AgentStatusState | str) -> AgentStatusState:
    if isinstance(state, AgentStatusState):
        return state
    return AgentStatusState(str(state).strip().lower())


@dataclass(frozen=True)
class AgentStatusPresentation:
    state: AgentStatusState
    label: str
    visible: bool


def presentation_for_state(state: AgentStatusState | str, *, lang: str) -> AgentStatusPresentation:
    normalized = _coerce_agent_status_state(state)
    locale = normalize_agent_status_locale(lang)
    if normalized is AgentStatusState.IDLE:
        return AgentStatusPresentation(state=normalized, label="", visible=False)
    label = agent_status_label(locale, normalized.value)
    return AgentStatusPresentation(state=normalized, label=label, visible=True)


class AgentStatusStateMachine:
    """Headless overlay state machine (safe for unit tests)."""

    def __init__(self, *, lang: str = "en") -> None:
        self._lock = threading.Lock()
        self._lang = normalize_agent_status_locale(lang)
        self._state = AgentStatusState.IDLE

    @property
    def lang(self) -> str:
        with self._lock:
            return self._lang

    @property
    def state(self) -> AgentStatusState:
        with self._lock:
            return self._state

    def set_locale(self, lang: str) -> None:
        with self._lock:
            self._lang = normalize_agent_status_locale(lang)

    def set_state(self, state: AgentStatusState | str) -> AgentStatusPresentation:
        normalized = _coerce_agent_status_state(state)
        with self._lock:
            self._state = normalized
            locale = self._lang
        return presentation_for_state(normalized, lang=locale)

    def snapshot(self) -> AgentStatusPresentation:
        with self._lock:
            state = self._state
            locale = self._lang
        return presentation_for_state(state, lang=locale)


class _NoOpAgentStatusOverlay:
    def __init__(self, *, lang: str = "en") -> None:
        self._machine = AgentStatusStateMachine(lang=lang)

    def ensure_started(self) -> None:
        return None

    def set_locale(self, lang: str) -> None:
        self._machine.set_locale(lang)

    def set_state(self, state: AgentStatusState | str) -> None:
        self._machine.set_state(state)

    def snapshot(self) -> AgentStatusPresentation:
        return self._machine.snapshot()

    def shutdown(self) -> None:
        return None


class GtkAgentStatusOverlay:
    """GTK4 floating pill anchored bottom-right of the primary monitor."""

    def __init__(
        self,
        *,
        gtk_module,
        glib_module,
        lang: str = "en",
    ) -> None:
        self._gtk = gtk_module
        self._glib = glib_module
        self._machine = AgentStatusStateMachine(lang=lang)
        self._window = None
        self._dot = None
        self._label = None
        self._css_provider = None
        self._hide_timer_id: int | None = None
        self._started = False
        self._start_lock = threading.Lock()

    def ensure_started(self) -> None:
        with self._start_lock:
            if self._started:
                return
            self._build_window()
            self._started = True
        self._apply_presentation(self._machine.snapshot())

    def set_locale(self, lang: str) -> None:
        self._machine.set_locale(lang)
        if self._started:
            self._schedule_apply(self._machine.snapshot())

    def set_state(self, state: AgentStatusState | str) -> None:
        presentation = self._machine.set_state(state)
        if not self._started and presentation.visible:
            self.ensure_started()
        self._schedule_apply(presentation)

    def snapshot(self) -> AgentStatusPresentation:
        return self._machine.snapshot()

    def shutdown(self) -> None:
        def _close() -> bool:
            if self._hide_timer_id is not None:
                try:
                    self._glib.source_remove(self._hide_timer_id)
                except Exception:
                    pass
                self._hide_timer_id = None
            if self._window is not None:
                try:
                    self._window.destroy()
                except Exception:
                    pass
                self._window = None
            self._started = False
            return False

        self._glib.idle_add(_close)

    def _build_window(self) -> None:
        window = self._gtk.Window()
        window.add_css_class("agent-status-overlay")
        window.set_decorated(False)
        window.set_resizable(False)
        window.set_default_size(1, 1)
        try:
            window.set_title("IuVe Agent Status")
        except Exception:
            pass
        try:
            window.set_accept_focus(False)
        except Exception:
            pass
        try:
            window.set_focus_on_click(False)
        except Exception:
            pass
        try:
            window.set_can_focus(False)
        except Exception:
            pass

        pill = self._gtk.Box(orientation=self._gtk.Orientation.HORIZONTAL, spacing=8)
        pill.add_css_class("agent-status-pill")
        pill.set_valign(self._gtk.Align.END)
        pill.set_halign(self._gtk.Align.END)

        self._dot = self._gtk.Box()
        self._dot.add_css_class("agent-status-dot")
        pill.append(self._dot)

        self._label = self._gtk.Label(label="")
        self._label.add_css_class("agent-status-text")
        self._label.set_xalign(0.0)
        pill.append(self._label)

        window.set_child(pill)
        self._css_provider = self._gtk.CssProvider()
        self._css_provider.load_from_data(_OVERLAY_CSS)
        display = window.get_display()
        if display is not None:
            self._gtk.StyleContext.add_provider_for_display(
                display,
                self._css_provider,
                self._gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
            )

        def _on_realize(_widget) -> None:
            self._position_bottom_right(window)
            try:
                surface = window.get_surface()
                if surface is not None and hasattr(surface, "set_keep_above"):
                    surface.set_keep_above(True)
            except Exception:
                logger.debug("agent_status_overlay_keep_above_failed", exc_info=True)

        window.connect("realize", _on_realize)
        self._window = window

    def _position_bottom_right(self, window) -> None:
        try:
            from gi.repository import Gdk  # type: ignore

            display = window.get_display()
            monitor = display.get_primary_monitor() if display is not None else None
            if monitor is None:
                return
            geometry = monitor.get_geometry()
            surface = window.get_surface()
            if surface is None:
                return
            width = max(int(window.get_width() or 220), 180)
            height = max(int(window.get_height() or 40), 36)
            x = geometry.x + max(OVERLAY_MARGIN_PX, geometry.width - width - OVERLAY_MARGIN_PX)
            y = geometry.y + max(OVERLAY_MARGIN_PX, geometry.height - height - OVERLAY_MARGIN_PX)
            layout = Gdk.ToplevelLayout.new()
            surface.set_geometry(layout, x, y, width, height)
        except Exception:
            logger.debug("agent_status_overlay_position_failed", exc_info=True)

    def _schedule_apply(self, presentation: AgentStatusPresentation) -> None:
        def _apply() -> bool:
            self._apply_presentation(presentation)
            return False

        self._glib.idle_add(_apply)

    def _apply_presentation(self, presentation: AgentStatusPresentation) -> None:
        if self._window is None:
            return
        if self._hide_timer_id is not None:
            try:
                self._glib.source_remove(self._hide_timer_id)
            except Exception:
                pass
            self._hide_timer_id = None

        if not presentation.visible:
            try:
                self._window.set_visible(False)
            except Exception:
                pass
            return

        if self._dot is not None:
            for css_class in ("working", "error", "blocked", "idle"):
                try:
                    self._dot.remove_css_class(css_class)
                except Exception:
                    pass
            try:
                self._dot.add_css_class(presentation.state.value)
            except Exception:
                pass
        if self._label is not None:
            self._label.set_text(presentation.label)

        try:
            self._window.set_visible(True)
        except Exception:
            pass
        self._position_bottom_right(self._window)

        if presentation.state is AgentStatusState.ERROR:
            def _hide_after_error() -> bool:
                self._hide_timer_id = None
                self.set_state(AgentStatusState.IDLE)
                return False

            self._hide_timer_id = self._glib.timeout_add(ERROR_VISIBLE_MS, _hide_after_error)


_overlay_singleton: GtkAgentStatusOverlay | _NoOpAgentStatusOverlay | None = None
_overlay_lock = threading.Lock()


def _ensure_display_env() -> None:
    """Best-effort DISPLAY/WAYLAND inheritance for systemd --user agent sessions."""
    import os

    if str(os.environ.get("DISPLAY", "")).strip() or str(os.environ.get("WAYLAND_DISPLAY", "")).strip():
        return
    # Explicit override for packaged user units.
    for key in ("IUVE_AGENT_DISPLAY", "IUVE_DISPLAY"):
        value = str(os.environ.get(key, "")).strip()
        if value:
            os.environ.setdefault("DISPLAY", value)
            return
    # Common single-seat desktop default when launched via user systemd without import-environment.
    if Path("/tmp/.X11-unix/X0").exists():
        os.environ.setdefault("DISPLAY", ":0")


def _linux_desktop_session() -> bool:
    if not sys.platform.startswith("linux"):
        return False
    import os

    _ensure_display_env()
    return bool(str(os.environ.get("DISPLAY", "")).strip() or str(os.environ.get("WAYLAND_DISPLAY", "")).strip())


def get_agent_status_overlay(
    *,
    gtk_module=None,
    glib_module=None,
    lang: str | None = None,
) -> GtkAgentStatusOverlay | _NoOpAgentStatusOverlay:
    global _overlay_singleton
    with _overlay_lock:
        if _overlay_singleton is not None:
            if lang:
                _overlay_singleton.set_locale(lang)
            return _overlay_singleton

        locale = resolve_agent_status_locale(
            cached_lang=lang,
            env_override=str(__import__("os").environ.get("IUVE_AGENT_UI_LANG", "")).strip(),
        )
        if gtk_module is None or glib_module is None:
            try:
                import gi

                gi.require_version("Gtk", "4.0")
                from gi.repository import Gtk as _Gtk, GLib as _GLib  # type: ignore

                gtk_module = gtk_module or _Gtk
                glib_module = glib_module or _GLib
            except Exception:
                logger.debug("agent_status_overlay_gtk_autoload_failed", exc_info=True)

        if _linux_desktop_session() and gtk_module is not None and glib_module is not None:
            _overlay_singleton = GtkAgentStatusOverlay(
                gtk_module=gtk_module,
                glib_module=glib_module,
                lang=locale,
            )
        else:
            _overlay_singleton = _NoOpAgentStatusOverlay(lang=locale)
        return _overlay_singleton


def bind_agent_status_overlay(
    *,
    gtk_module,
    glib_module,
    resolve_lang: Callable[[], str] | None = None,
) -> GtkAgentStatusOverlay | _NoOpAgentStatusOverlay:
    overlay = get_agent_status_overlay(gtk_module=gtk_module, glib_module=glib_module)
    overlay.ensure_started()
    if resolve_lang is not None:
        try:
            overlay.set_locale(resolve_lang())
        except Exception:
            logger.debug("agent_status_overlay_locale_resolve_failed", exc_info=True)
    return overlay
