from __future__ import annotations

from typing import Any, Callable

from ui.style import add_css_class
from ui.view_model import AlertsTabVM, AlertVM, DashboardCallbacks, SummaryLineVM


def _load_gtk_modules() -> tuple[Any, bool]:
    try:
        import gi

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

        return Gtk, True
    except Exception:
        return _FallbackGtk, False


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[object] = []
        self.end_children: list[object] = []

    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 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 set_line_wrap(self, _wrap: bool) -> None:
        return

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

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

    def pack_end(self, child: object, *_args: object) -> None:
        self.children.append(child)
        self.end_children.insert(0, child)

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

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


class _FallbackLabel(_FallbackWidget):
    pass


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

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

    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

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

    Label = _FallbackLabel
    Box = _FallbackBox
    Button = _FallbackButton

    @staticmethod
    def Label_new(text: str = "") -> _FallbackLabel:
        return _FallbackLabel(text=text)

    @staticmethod
    def Box_new(_orientation: int | None = None, spacing: int = 0) -> _FallbackBox:
        return _FallbackBox(_orientation=_orientation, _spacing=spacing)


class _BannerRow:
    def __init__(
        self,
        widget: Any,
        icon_label: Any,
        text_box: Any,
        title_label: Any,
        detail_label: Any,
        action_button: Any | None,
        close_button: Any,
        alert: AlertVM,
    ) -> None:
        self.widget = widget
        self.icon_label = icon_label
        self.text_box = text_box
        self.title_label = title_label
        self.detail_label = detail_label
        self.action_button = action_button
        self.close_button = close_button
        self.alert = alert


class _SummaryRow:
    def __init__(self, widget: Any, left_label: Any, right_label: Any) -> None:
        self.widget = widget
        self.left_label = left_label
        self.right_label = right_label


class AlertsTab:
    def __init__(
        self,
        vm: AlertsTabVM,
        callbacks: DashboardCallbacks,
        gtk_module: Any | None = None,
    ) -> None:
        self._callbacks = callbacks
        self.gtk, _ = _load_gtk_modules()
        if gtk_module is not None:
            self.gtk = gtk_module
        self.banners: list[_BannerRow] = []
        self.summary_rows: list[_SummaryRow] = []
        self._banner_keys: tuple[tuple[str, str, str, str | None], ...] = ()

        vertical = getattr(getattr(self.gtk, "Orientation", None), "VERTICAL", 0)
        self.widget = self._new_box(self.gtk, vertical, spacing=12)
        add_css_class(self.widget, "dashboard-content")
        add_css_class(self.widget, "alerts-tab")
        self.banners_box = self._new_box(self.gtk, vertical, spacing=8)
        self.healthy_box = self._new_box(self.gtk, vertical, spacing=4)
        add_css_class(self.healthy_box, "healthy-summary")
        self.empty_box = self._new_box(self.gtk, vertical, spacing=4)
        add_css_class(self.empty_box, "empty-state")
        self.empty_heading_label = self._new_label(self.gtk, "")
        self.empty_detail_label = self._new_label(self.gtk, "")
        self._pack(self.empty_box, self.empty_heading_label)
        self._pack(self.empty_box, self.empty_detail_label)
        self._show(self.empty_heading_label)
        self._show(self.empty_detail_label)
        for child in (self.banners_box, self.healthy_box, self.empty_box):
            if hasattr(child, "set_no_show_all"):
                child.set_no_show_all(True)
            self._pack(self.widget, child)

        self.update(vm)

    @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_new(orientation, spacing)

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

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

    @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 _pack_end(parent: Any, child: Any) -> None:
        if hasattr(parent, "pack_end"):
            parent.pack_end(child, False, False, 0)
        else:
            parent.add(child)

    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 _clear(container: Any) -> None:
        if hasattr(container, "get_children") and hasattr(container, "remove"):
            for child in list(container.get_children()):
                container.remove(child)

    @staticmethod
    def _show(widget: Any) -> None:
        if hasattr(widget, "show_all"):
            widget.show_all()

    @staticmethod
    def _set_visible(widget: Any, visible: bool) -> None:
        if hasattr(widget, "set_visible"):
            widget.set_visible(visible)
        if visible:
            AlertsTab._show(widget)

    def update(self, vm: AlertsTabVM) -> None:
        keys = tuple((a.kind, a.tool, a.slug, a.action) for a in vm.alerts)
        if keys != self._banner_keys:
            self._rebuild_banners(vm.alerts)
            self._banner_keys = keys
        else:
            for row, alert in zip(self.banners, vm.alerts):
                row.alert = alert
                row.title_label.set_text(alert.title_text)
                row.detail_label.set_text(alert.detail_text)
                if row.action_button is not None:
                    self._set_button_label(row.action_button, alert.action_label)
        if len(vm.healthy_lines) != len(self.summary_rows):
            self._rebuild_summary(vm.healthy_lines)
        else:
            for summary_row, line in zip(self.summary_rows, vm.healthy_lines):
                summary_row.left_label.set_text(line.text_left)
                summary_row.right_label.set_text(line.text_right)
        self.empty_heading_label.set_text(vm.empty_heading)
        self.empty_detail_label.set_text(vm.empty_detail)
        self._set_visible(self.banners_box, bool(vm.alerts))
        self._set_visible(self.healthy_box, bool(vm.healthy_lines))
        self._set_visible(self.empty_box, bool(vm.empty_heading))

    def _rebuild_banners(self, alerts: tuple[AlertVM, ...]) -> None:
        self._clear(self.banners_box)
        self.banners = []
        for alert in alerts:
            row = self._build_banner(alert)
            self.banners.append(row)
            self._pack(self.banners_box, row.widget)
            self._show(row.widget)

    def _build_banner(self, alert: AlertVM) -> _BannerRow:
        vertical = getattr(getattr(self.gtk, "Orientation", None), "VERTICAL", 0)
        horizontal = getattr(getattr(self.gtk, "Orientation", None), "HORIZONTAL", 0)
        box = self._new_box(self.gtk, horizontal, spacing=10)
        add_css_class(box, "alert-banner")
        add_css_class(box, "error" if alert.kind == "broken" else "warn")
        icon_label = self._new_label(self.gtk, "✕" if alert.kind == "broken" else "⚠")
        add_css_class(icon_label, "alert-icon")
        self._set_compact(icon_label, align="START")
        text_box = self._new_box(self.gtk, vertical, spacing=2)
        if hasattr(text_box, "set_hexpand"):
            text_box.set_hexpand(True)
        title_label = self._new_label(self.gtk, alert.title_text)
        add_css_class(title_label, "alert-title")
        detail_label = self._new_label(self.gtk, alert.detail_text)
        add_css_class(detail_label, "alert-detail")
        self._pack(text_box, title_label)
        self._pack(text_box, detail_label)
        self._pack(box, icon_label)
        self._pack(box, text_box, expand=True)
        close_button = self._new_button(self.gtk, "×")
        add_css_class(close_button, "alert-close")
        self._set_compact(close_button)
        action_button: Any | None = None
        row = _BannerRow(
            box, icon_label, text_box, title_label, detail_label, None, close_button, alert
        )
        close_button.connect(
            "clicked", lambda _button, row=row: self._dismiss(row)
        )
        if alert.action is not None:
            action_button = self._new_button(self.gtk, alert.action_label)
            if alert.action == "switch":
                add_css_class(action_button, "suggested-action")
            add_css_class(action_button, "alert-action")
            self._set_compact(action_button)
            action_button.connect(
                "clicked", lambda _button, row=row: self._dispatch(row)
            )
            row.action_button = action_button
            self._pack_end(box, action_button)
        self._pack_end(box, close_button)
        return row

    def _dismiss(self, row: _BannerRow) -> None:
        alert = row.alert
        self._callbacks.on_dismiss_alert(alert.kind, alert.tool, alert.slug)

    def _dispatch(self, row: _BannerRow) -> None:
        alert = row.alert
        if alert.action == "switch" and alert.action_slug is not None:
            self._callbacks.on_set_default(alert.tool, alert.action_slug)
        elif alert.action == "repair":
            self._callbacks.on_repair(alert.tool, alert.slug)

    def _rebuild_summary(self, lines: tuple[SummaryLineVM, ...]) -> None:
        self._clear(self.healthy_box)
        self.summary_rows = []
        horizontal = getattr(getattr(self.gtk, "Orientation", None), "HORIZONTAL", 0)
        for line in lines:
            line_box = self._new_box(self.gtk, horizontal, spacing=6)
            add_css_class(line_box, "summary-line")
            left_label = self._new_label(self.gtk, line.text_left)
            right_label = self._new_label(self.gtk, line.text_right)
            if hasattr(left_label, "set_hexpand"):
                left_label.set_hexpand(True)
            if hasattr(right_label, "set_xalign"):
                right_label.set_xalign(1.0)
            self._set_compact(right_label)
            self._pack(line_box, left_label, expand=True)
            self._pack_end(line_box, right_label)
            self.summary_rows.append(_SummaryRow(line_box, left_label, right_label))
            self._pack(self.healthy_box, line_box)
            self._show(line_box)

    @staticmethod
    def _set_button_label(button: Any, label: str) -> None:
        if hasattr(button, "set_label"):
            button.set_label(label)
        else:
            button.set_text(label)
