from __future__ import annotations

from typing import Any, Callable

from ui.style import add_css_class, apply_app_css, apply_theme_class
from ui.tab_bar import TabBar
from ui.theme import ResolvedTheme
from ui.view_model import DashboardCallbacks, DashboardVM

_ALERTS_TAB_ID = "alerts"
_ALERTS_ADD_LABEL = "＋ Add account"


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.children: list[object] = []
        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_visible(self, visible: bool) -> None:
        self.visible = visible

    def get_visible(self) -> bool:
        return self.visible

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

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

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


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 _FallbackGtk:
    class Orientation:
        VERTICAL = 1
        HORIZONTAL = 2

    Label = _FallbackLabel
    Box = _FallbackBox
    Button = _FallbackButton


class Dashboard:
    def __init__(
        self,
        callbacks: DashboardCallbacks,
        gtk_module: Any | None = None,
        alerts_tab_factory: Callable[..., Any] | None = None,
        provider_tab_factory: Callable[..., Any] | None = None,
        footer_factory: Callable[..., Any] | None = None,
    ) -> None:
        self.gtk = gtk_module if gtk_module is not None else _load_gtk_modules()
        self._callbacks = callbacks
        if alerts_tab_factory is None:
            from ui.alerts_tab import AlertsTab

            alerts_tab_factory = AlertsTab
        if provider_tab_factory is None:
            from ui.provider_tab import ProviderTab

            provider_tab_factory = ProviderTab
        if footer_factory is None:
            from ui.footer import Footer

            footer_factory = Footer
        self._alerts_tab_factory = alerts_tab_factory
        self._provider_tab_factory = provider_tab_factory
        self._footer_factory = footer_factory
        apply_app_css(gtk_module=gtk_module)
        vertical = getattr(getattr(self.gtk, "Orientation", None), "VERTICAL", 0)
        self.widget = self._new_box(self.gtk, vertical, spacing=0)
        add_css_class(self.widget, "dashboard")
        self._theme: ResolvedTheme = "light"
        apply_theme_class(self.widget, self._theme)
        self._vm: DashboardVM | None = None
        self._active: str = _ALERTS_TAB_ID
        self.tab_bar: TabBar | None = None
        self.alerts_tab: Any | None = None
        self.provider_tabs: dict[str, Any] = {}
        self.footer: Any | None = None
        self._stack: Any | None = None
        self._pages_container: Any | None = None
        self._pages: dict[str, Any] = {}

    def set_theme(self, resolved: ResolvedTheme) -> None:
        if resolved not in ("light", "dark"):
            resolved = "light"
        self._theme = resolved
        apply_theme_class(self.widget, resolved)

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

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

    def update(self, vm: DashboardVM) -> None:
        if self.tab_bar is None:
            self._build(vm)
        else:
            self._refresh(vm)
        self._vm = vm

    def show_tab(self, tab_id: str) -> None:
        if self.tab_bar is None or tab_id not in self._pages:
            return
        self._active = tab_id
        self.tab_bar.set_active(tab_id)
        self._show_page(tab_id)
        if self._vm is not None and self.footer is not None:
            add_label, add_tools = self._footer_content(self._vm, tab_id)
            self.footer.update(
                add_label,
                add_tools,
                self._vm.refresh_text,
                busy_tools=self._vm.busy_add_tools,
            )
        self._callbacks.on_tab_changed(tab_id)

    def _build(self, vm: DashboardVM) -> None:
        self._active = vm.active_tab
        self.tab_bar = TabBar(
            vm.tabs, vm.active_tab, self._handle_tab_selected, gtk_module=self.gtk
        )
        stack_cls = getattr(self.gtk, "Stack", None)
        if stack_cls is not None:
            self._stack = stack_cls()
            self._pages_container = self._stack
        else:
            vertical = getattr(getattr(self.gtk, "Orientation", None), "VERTICAL", 0)
            self._pages_container = self._new_box(self.gtk, vertical, spacing=8)
        self.alerts_tab = self._alerts_tab_factory(vm.alerts, self._callbacks)
        self._add_page(_ALERTS_TAB_ID, self.alerts_tab.widget)
        for provider_vm in vm.providers:
            tab = self._provider_tab_factory(provider_vm, self._callbacks)
            self.provider_tabs[provider_vm.tool] = tab
            self._add_page(provider_vm.tool, tab.widget)
        add_label, add_tools = self._footer_content(vm, self._active)
        self.footer = self._footer_factory(
            add_label, add_tools, vm.refresh_text, self._callbacks
        )
        if vm.busy_add_tools:
            self.footer.update(
                add_label, add_tools, vm.refresh_text, busy_tools=vm.busy_add_tools
            )
        self._pack(self.widget, self.tab_bar.widget)
        self._pack(self.widget, self._pages_container)
        self._pack(self.widget, self.footer.widget)
        self._show_page(self._active)

    def _refresh(self, vm: DashboardVM) -> None:
        assert self.tab_bar is not None
        assert self.alerts_tab is not None
        assert self.footer is not None
        self.tab_bar.update(vm.tabs)
        self.alerts_tab.update(vm.alerts)
        tools = tuple(provider_vm.tool for provider_vm in vm.providers)
        for provider_vm in vm.providers:
            existing = self.provider_tabs.get(provider_vm.tool)
            if existing is not None:
                existing.update(provider_vm)
            else:
                tab = self._provider_tab_factory(provider_vm, self._callbacks)
                self.provider_tabs[provider_vm.tool] = tab
                self._add_page(provider_vm.tool, tab.widget)
        for tool in tuple(self.provider_tabs):
            if tool not in tools:
                self.provider_tabs.pop(tool)
                self._remove_page(tool)
        if self._active in self._pages:
            self._show_page(self._active)
        add_label, add_tools = self._footer_content(vm, self._active)
        self.footer.update(
            add_label, add_tools, vm.refresh_text, busy_tools=vm.busy_add_tools
        )

    def _handle_tab_selected(self, tab_id: str) -> None:
        self.show_tab(tab_id)

    def _footer_content(
        self, vm: DashboardVM, tab_id: str
    ) -> tuple[str, tuple[tuple[str, str], ...]]:
        for provider_vm in vm.providers:
            if provider_vm.tool == tab_id:
                labels = dict(vm.add_tools)
                return (
                    provider_vm.add_label,
                    ((tab_id, labels.get(tab_id, tab_id)),),
                )
        return _ALERTS_ADD_LABEL, vm.add_tools

    def _add_page(self, tab_id: str, widget: Any) -> None:
        if self._stack is not None:
            self._stack.add_named(widget, tab_id)
        else:
            self._pack(self._pages_container, widget)
        show_all = getattr(widget, "show_all", None)
        if callable(show_all):
            show_all()
        self._pages[tab_id] = widget

    def _remove_page(self, tab_id: str) -> None:
        widget = self._pages.pop(tab_id, None)
        if widget is None:
            return
        parent = self._pages_container
        remove = getattr(parent, "remove", None)
        if callable(remove):
            try:
                remove(widget)
                return
            except Exception:
                pass
        children = getattr(parent, "children", None)
        if isinstance(children, list) and widget in children:
            children.remove(widget)

    def _show_page(self, tab_id: str) -> None:
        if self._stack is not None:
            self._stack.set_visible_child_name(tab_id)
            return
        for page_id, widget in self._pages.items():
            visible = page_id == tab_id
            set_visible = getattr(widget, "set_visible", None)
            if callable(set_visible):
                set_visible(visible)
            else:
                widget.visible = visible
