from __future__ import annotations

from typing import Any, Callable

from ui.settings_store import Settings
from ui.theme import THEME_PREFERENCES, normalize_theme_preference

_RESPONSE_SAVE = -5
_RESPONSE_CANCEL = -6

_THEME_LABELS = {
    "system": "System",
    "light": "Light",
    "dark": "Dark",
}


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

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

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

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

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


class _FallbackLabel(_FallbackWidget):
    pass


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


class _FallbackSwitch(_FallbackWidget):
    def __init__(self) -> None:
        super().__init__(text="")
        self.active = False

    def set_active(self, active: bool) -> None:
        self.active = bool(active)

    def get_active(self) -> bool:
        return self.active


class _FallbackSpinButton(_FallbackWidget):
    def __init__(self, minimum: float, maximum: float, step: float) -> None:
        super().__init__(text="")
        self.minimum = float(minimum)
        self.maximum = float(maximum)
        self.step = float(step)
        self.value = float(minimum)

    @classmethod
    def new_with_range(
        cls, minimum: float, maximum: float, step: float
    ) -> _FallbackSpinButton:
        return cls(minimum, maximum, step)

    def set_value(self, value: float) -> None:
        self.value = max(self.minimum, min(self.maximum, float(value)))

    def get_value(self) -> float:
        return self.value

    def get_value_as_int(self) -> int:
        return int(self.value)


class _FallbackComboBoxText(_FallbackWidget):
    def __init__(self) -> None:
        super().__init__(text="")
        self._ids: list[str] = []
        self._labels: list[str] = []
        self._active_id: str | None = None

    def append(self, id: str, text: str) -> None:
        self._ids.append(id)
        self._labels.append(text)

    def set_active_id(self, id: str) -> None:
        if id in self._ids:
            self._active_id = id

    def get_active_id(self) -> str | None:
        return self._active_id


class _FallbackDialog(_FallbackWidget):
    def __init__(self, title: str = "") -> None:
        super().__init__(text=title)
        self.title = title
        self.transient_for: Any | None = None
        self.modal = False
        self.destroyed = False
        self.buttons: list[tuple[str, int]] = []
        self._content = _FallbackBox()

    def set_title(self, title: str) -> None:
        self.title = title

    def set_transient_for(self, window: Any) -> None:
        self.transient_for = window

    def set_modal(self, modal: bool) -> None:
        self.modal = bool(modal)

    def add_button(self, label: str, response: int) -> None:
        self.buttons.append((label, response))

    def get_content_area(self) -> _FallbackBox:
        return self._content

    def run(self) -> int:
        return _RESPONSE_CANCEL

    def destroy(self) -> None:
        self.visible = False
        self.destroyed = True


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

    class ResponseType:
        OK = _RESPONSE_SAVE
        CANCEL = _RESPONSE_CANCEL

    Label = _FallbackLabel
    Box = _FallbackBox
    Switch = _FallbackSwitch
    SpinButton = _FallbackSpinButton
    ComboBoxText = _FallbackComboBoxText
    Dialog = _FallbackDialog


class SettingsDialog:
    def __init__(
        self,
        settings: Settings,
        on_save: Callable[[Settings], None],
        transient_for: Any | None = None,
        gtk_module: Any | None = None,
    ) -> None:
        self._settings = settings
        self._on_save = on_save
        self.gtk = gtk_module if gtk_module is not None else _load_gtk_modules()
        self.saved: Settings | None = None

        response_type = getattr(self.gtk, "ResponseType", None)
        self._response_save = getattr(response_type, "OK", _RESPONSE_SAVE)
        self._response_cancel = getattr(response_type, "CANCEL", _RESPONSE_CANCEL)

        self.dialog = self._new_dialog(self.gtk, "Settings", transient_for)
        if hasattr(self.dialog, "add_button"):
            self.dialog.add_button("Cancel", self._response_cancel)
            self.dialog.add_button("Save", self._response_save)

        self.auto_close_switch = self._new_switch(self.gtk, settings.auto_close)
        self.refresh_spin = self._new_spin(
            self.gtk, 30, 3600, 30, settings.refresh_interval_seconds
        )
        self.threshold_spin = self._new_spin(
            self.gtk, 1, 50, 1, settings.warning_threshold_pct
        )
        self.theme_combo = self._new_theme_combo(self.gtk, settings.theme)

        self._build_content()

    def open(self) -> None:
        dialog = self.dialog
        if hasattr(dialog, "show_all"):
            dialog.show_all()
        run = getattr(dialog, "run", None)
        if not callable(run):
            return
        response = run()
        if response == self._response_save:
            self._handle_save()
        if hasattr(dialog, "destroy"):
            dialog.destroy()

    def simulate_save(self, settings: Settings) -> None:
        self.auto_close_switch.set_active(settings.auto_close)
        self.refresh_spin.set_value(settings.refresh_interval_seconds)
        self.threshold_spin.set_value(settings.warning_threshold_pct)
        self.theme_combo.set_active_id(normalize_theme_preference(settings.theme))
        self._handle_save()

    def _handle_save(self) -> None:
        new_settings = Settings(
            auto_close=bool(self.auto_close_switch.get_active()),
            refresh_interval_seconds=int(self.refresh_spin.get_value()),
            warning_threshold_pct=int(self.threshold_spin.get_value()),
            last_tab=self._settings.last_tab,
            theme=normalize_theme_preference(self.theme_combo.get_active_id()),
        )
        self.saved = new_settings
        self._on_save(new_settings)

    def _build_content(self) -> None:
        orientation = getattr(self.gtk, "Orientation", None)
        horizontal = getattr(orientation, "HORIZONTAL", 0)
        rows = (
            ("Theme", self.theme_combo),
            ("Auto-close popup", self.auto_close_switch),
            ("Refresh interval (seconds)", self.refresh_spin),
            ("Warning threshold (% remaining, 5h)", self.threshold_spin),
        )
        content = None
        get_content_area = getattr(self.dialog, "get_content_area", None)
        if callable(get_content_area):
            content = get_content_area()
        for label_text, control in rows:
            row = self._new_box(self.gtk, horizontal, spacing=12)
            self._pack(row, self._new_label(self.gtk, label_text))
            self._pack(row, control)
            if content is not None:
                self._pack(content, row)

    @staticmethod
    def _new_dialog(gtk: Any, title: str, transient_for: Any | None) -> Any:
        try:
            dialog = gtk.Dialog(title=title)
        except TypeError:
            dialog = gtk.Dialog(title)
        if transient_for is not None and hasattr(dialog, "set_transient_for"):
            try:
                dialog.set_transient_for(transient_for)
            except Exception:
                pass
        if hasattr(dialog, "set_modal"):
            dialog.set_modal(True)
        return dialog

    @staticmethod
    def _new_switch(gtk: Any, active: bool) -> Any:
        switch = gtk.Switch()
        switch.set_active(active)
        return switch

    @staticmethod
    def _new_theme_combo(gtk: Any, theme: str) -> Any:
        combo_cls = getattr(gtk, "ComboBoxText", _FallbackComboBoxText)
        combo = combo_cls()
        for theme_id in THEME_PREFERENCES:
            combo.append(theme_id, _THEME_LABELS[theme_id])
        combo.set_active_id(normalize_theme_preference(theme))
        return combo

    @staticmethod
    def _new_spin(
        gtk: Any, minimum: int, maximum: int, step: int, value: int
    ) -> Any:
        spin_cls = getattr(gtk, "SpinButton", _FallbackSpinButton)
        new_with_range = getattr(spin_cls, "new_with_range", None)
        if callable(new_with_range):
            spin = new_with_range(minimum, maximum, step)
        else:
            spin = spin_cls(minimum, maximum, step)
        spin.set_value(value)
        return spin

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