from __future__ import annotations

from typing import Any, Callable

from ui.style import add_css_class
from ui.view_model import DashboardCallbacks


def _load_gtk_modules() -> Any:
    try:
        import gi

        gi.require_version("Gtk", "3.0")
        from gi.repository import Gtk

        return Gtk
    except Exception:
        return _FallbackGtk


class _FallbackWidget:
    def __init__(self, text: str = "") -> None:
        self.text = text
        self.label = text
        self.visible = True
        self.sensitive = True
        self.hexpand = False
        self.halign: int | None = None
        self.valign: int | None = None
        self.children: list[Any] = []
        self.css_classes: list[str] = []

    def set_text(self, text: str) -> None:
        self.text = text
        self.label = text

    def get_text(self) -> str:
        return self.text

    def set_label(self, label: str) -> None:
        self.text = label
        self.label = label

    def set_visible(self, visible: bool) -> None:
        self.visible = bool(visible)

    def set_sensitive(self, sensitive: bool) -> None:
        self.sensitive = bool(sensitive)

    def set_hexpand(self, hexpand: bool) -> None:
        self.hexpand = bool(hexpand)

    def set_halign(self, halign: int) -> None:
        self.halign = halign

    def set_valign(self, valign: int) -> None:
        self.valign = valign

    def set_xalign(self, _xalign: float) -> None:
        return

    def add(self, child: Any) -> None:
        self.children.append(child)

    def pack_start(self, child: Any, *_args: Any) -> None:
        self.children.append(child)

    def remove(self, child: Any) -> None:
        if child in self.children:
            self.children.remove(child)

    def get_children(self) -> list[Any]:
        return list(self.children)

    def show_all(self) -> None:
        self.visible = True


class _FallbackLabel(_FallbackWidget):
    pass


class _FallbackButton(_FallbackWidget):
    def __init__(self, label: str = "") -> None:
        super().__init__(label)
        self._callback: Callable[..., Any] | None = None

    def connect(self, signal: str, callback: Callable[..., Any]) -> None:
        if signal == "clicked":
            self._callback = callback

    def emit(self, signal: str) -> None:
        if signal == "clicked" and self.sensitive and self._callback is not None:
            self._callback(self)


class _FallbackBox(_FallbackWidget):
    def __init__(self, orientation: int | None = None, spacing: int = 0) -> None:
        super().__init__(text="")
        self.orientation = orientation
        self.spacing = spacing


class _FallbackPopover(_FallbackWidget):
    def __init__(self) -> None:
        super().__init__(text="")
        self.visible = False
        self.relative_to: Any | None = None

    def set_relative_to(self, widget: Any) -> None:
        self.relative_to = widget

    def popup(self) -> None:
        self.visible = True

    def popdown(self) -> None:
        self.visible = False

    def hide(self) -> None:
        self.visible = False


class _FallbackGtk:
    class Orientation:
        VERTICAL = 1
        HORIZONTAL = 2

    class Align:
        START = 1
        CENTER = 2
        END = 3

    Label = _FallbackLabel
    Box = _FallbackBox
    Button = _FallbackButton
    Popover = _FallbackPopover


class Footer:
    def __init__(
        self,
        add_label: str,
        add_tools: tuple[tuple[str, str], ...],
        refresh_text: str,
        callbacks: DashboardCallbacks,
        gtk_module: Any | None = None,
        busy_tools: frozenset[str] = frozenset(),
    ) -> None:
        self._callbacks = callbacks
        self._add_tools: tuple[tuple[str, str], ...] = tuple(add_tools)
        self._busy_tools: frozenset[str] = frozenset(busy_tools)
        self.gtk = gtk_module if gtk_module is not None else _load_gtk_modules()

        orientation = getattr(self.gtk, "Orientation", None)
        horizontal = getattr(orientation, "HORIZONTAL", 0)
        vertical = getattr(orientation, "VERTICAL", 0)

        self.widget = self._new_box(self.gtk, horizontal, spacing=6)
        add_css_class(self.widget, "footer")

        self.add_button = self._new_button(self.gtk, add_label)
        add_css_class(self.add_button, "flat")
        self._set_compact(self.add_button, align="START")
        self.add_button.connect("clicked", self._handle_add_clicked)

        self.timestamp_label = self._new_label(self.gtk, refresh_text)
        add_css_class(self.timestamp_label, "footer-timestamp")
        if hasattr(self.timestamp_label, "set_hexpand"):
            self.timestamp_label.set_hexpand(True)
        if hasattr(self.timestamp_label, "set_xalign"):
            self.timestamp_label.set_xalign(1.0)

        self.reload_button = self._new_button(self.gtk, "Reload All")
        add_css_class(self.reload_button, "footer-button")
        self._set_compact(self.reload_button)
        self.reload_button.connect("clicked", self._handle_reload_clicked)

        self.settings_button = self._new_button(self.gtk, "⚙")
        add_css_class(self.settings_button, "footer-button")
        self._set_compact(self.settings_button)
        self.settings_button.connect("clicked", self._handle_settings_clicked)

        self._pack(self.widget, self.add_button)
        self._pack(self.widget, self.timestamp_label, expand=True)
        self._pack(self.widget, self.reload_button)
        self._pack(self.widget, self.settings_button)

        self._popover_box = self._new_box(self.gtk, vertical, spacing=0)
        self.popover = self._new_popover(self.gtk, self.add_button)
        self._add_to(self.popover, self._popover_box)
        self.add_item_buttons: dict[str, Any] = {}
        self._rebuild_add_items()
        self._apply_busy()

    def update(
        self,
        add_label: str,
        add_tools: tuple[tuple[str, str], ...],
        refresh_text: str,
        busy_tools: frozenset[str] = frozenset(),
    ) -> None:
        self.add_button.set_label(add_label)
        self.timestamp_label.set_text(refresh_text)
        new_tools = tuple(add_tools)
        if new_tools != self._add_tools:
            self._add_tools = new_tools
            self._rebuild_add_items()
        self._busy_tools = frozenset(busy_tools)
        self._apply_busy()

    def _apply_busy(self) -> None:
        if len(self._add_tools) == 1:
            self.add_button.set_sensitive(self._add_tools[0][0] not in self._busy_tools)
        else:
            self.add_button.set_sensitive(True)
        for tool, button in self.add_item_buttons.items():
            button.set_sensitive(tool not in self._busy_tools)

    def _handle_add_clicked(self, *_args: Any) -> None:
        if len(self._add_tools) == 1:
            self._callbacks.on_add(self._add_tools[0][0])
            return
        if not self._add_tools:
            return
        if hasattr(self._popover_box, "show_all"):
            self._popover_box.show_all()
        if hasattr(self.popover, "popup"):
            self.popover.popup()
        elif hasattr(self.popover, "show_all"):
            self.popover.show_all()

    def _handle_reload_clicked(self, *_args: Any) -> None:
        self._callbacks.on_reload(None, None)

    def _handle_settings_clicked(self, *_args: Any) -> None:
        self._callbacks.on_open_settings()

    def _handle_add_tool(self, tool: str) -> None:
        if hasattr(self.popover, "popdown"):
            self.popover.popdown()
        elif hasattr(self.popover, "hide"):
            self.popover.hide()
        self._callbacks.on_add(tool)

    def _rebuild_add_items(self) -> None:
        self._clear(self._popover_box)
        self.add_item_buttons = {}
        for tool, label in self._add_tools:
            button = self._new_button(self.gtk, f"＋ Add {label} account")
            add_css_class(button, "flat")
            button.connect(
                "clicked", lambda _button, tool=tool: self._handle_add_tool(tool)
            )
            self.add_item_buttons[tool] = button
            self._pack(self._popover_box, button)

    @staticmethod
    def _new_box(gtk: Any, orientation: int, spacing: int = 0) -> Any:
        try:
            return gtk.Box(orientation=orientation, spacing=spacing)
        except TypeError:
            return gtk.Box(orientation, spacing)

    @staticmethod
    def _new_label(gtk: Any, text: str) -> Any:
        try:
            return gtk.Label(label=text)
        except TypeError:
            return gtk.Label(text)

    @staticmethod
    def _new_button(gtk: Any, label: str) -> Any:
        try:
            return gtk.Button(label=label)
        except TypeError:
            return gtk.Button(label)

    @staticmethod
    def _new_popover(gtk: Any, relative_to: Any) -> Any:
        popover_cls = getattr(gtk, "Popover", _FallbackPopover)
        try:
            popover = popover_cls()
        except Exception:
            popover = _FallbackPopover()
        if hasattr(popover, "set_relative_to"):
            try:
                popover.set_relative_to(relative_to)
            except Exception:
                pass
        return popover

    def _set_compact(self, widget: Any, *, align: str = "END") -> None:
        if hasattr(widget, "set_hexpand"):
            widget.set_hexpand(False)
        gtk_align = getattr(getattr(self.gtk, "Align", None), align, None)
        if gtk_align is not None and hasattr(widget, "set_halign"):
            widget.set_halign(gtk_align)
        center = getattr(getattr(self.gtk, "Align", None), "CENTER", None)
        if center is not None and hasattr(widget, "set_valign"):
            widget.set_valign(center)

    @staticmethod
    def _pack(parent: Any, child: Any, *, expand: bool = False) -> None:
        if hasattr(parent, "pack_start"):
            parent.pack_start(child, expand, expand, 0)
        else:
            parent.add(child)

    @staticmethod
    def _add_to(parent: Any, child: Any) -> None:
        if hasattr(parent, "add"):
            parent.add(child)

    @staticmethod
    def _clear(container: Any) -> None:
        get_children = getattr(container, "get_children", None)
        if callable(get_children) and hasattr(container, "remove"):
            for child in list(get_children()):
                container.remove(child)
            return
        children = getattr(container, "children", None)
        if isinstance(children, list):
            children.clear()
