from __future__ import annotations

import logging
from typing import Any

from gi.repository import Gtk

from ulauncher.ui.helpers.hotkey_controller import HotkeyController
from ulauncher.ui.helpers.theme import get_themes
from ulauncher.ui.preferences.views import BaseView, styled
from ulauncher.utils.environment import IS_X11
from ulauncher.utils.eventbus import EventBus
from ulauncher.utils.settings import Settings
from ulauncher.utils.systemd_controller import SystemdController

logger = logging.getLogger(__name__)
events = EventBus()


class PreferencesView(BaseView):
    """General preferences page"""

    def __init__(self) -> None:
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.settings: Settings = Settings.load()
        self.autostart_pref: SystemdController = SystemdController("ulauncher")

        scrolled = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER)
        scrolled.set_propagate_natural_width(True)
        scrolled.set_propagate_natural_height(True)
        self.pack_start(scrolled, True, True, 0)

        # Create main container - centers on wide screens, fills on narrow screens
        prefs_view = styled(
            Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=30, spacing=24),
            "preferences-content",
        )
        prefs_view.set_halign(Gtk.Align.CENTER)
        prefs_view.set_valign(Gtk.Align.START)
        prefs_view.set_size_request(600, -1)  # min-width of 600px
        scrolled.add(prefs_view)

        # Add sections
        self._add_general_section(prefs_view)
        self._add_applications_section(prefs_view)
        self._add_advanced_section(prefs_view)

    def _add_section_header(self, parent: Gtk.Box, title: str) -> None:
        """Add a section header"""
        label = Gtk.Label(
            label=title,
            halign=Gtk.Align.START,
            margin_top=10,
            margin_bottom=2,
        )
        styled(label, "preferences-section-title")
        parent.pack_start(label, False, False, 0)

    def _create_section_container(self, parent: Gtk.Box, title: str) -> Gtk.Box:
        """Create a stylized section card"""
        self._add_section_header(parent, title)
        section_box = styled(
            Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0, margin_top=0),
            "preferences-section-card",
        )
        parent.pack_start(section_box, False, False, 0)
        return section_box

    def _add_setting_row(
        self,
        parent: Gtk.Box,
        label_text: str,
        widget: Gtk.Widget,
        description: str,
        full_width: bool = False,
        is_warning: bool = False,
    ) -> None:
        """Add a settings row with label and widget

        Args:
            parent: The parent container
            label_text: The setting label
            widget: The control widget
            description: Description text
            full_width: If True, widget takes full width below label (for long inputs like Entry)
            is_warning: If True, style the description as a warning using GTK's 'warning' class
        """
        row_box = styled(Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=24), "preferences-setting-row")
        row_box.set_hexpand(True)

        # Left side - label and description
        label_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
        if not full_width:
            label_container.set_hexpand(True)
        else:
            label_container.set_size_request(360, -1)

        label = styled(Gtk.Label(label=label_text, halign=Gtk.Align.START), "preferences-setting-title")
        label.set_xalign(0.0)
        label_container.pack_start(label, False, False, 0)

        desc_label = styled(
            Gtk.Label(
                label=description,
                halign=Gtk.Align.START,
                wrap=True,
                max_width_chars=70,
                margin_top=2,
                use_markup=True,
            ),
            "preferences-setting-description",
        )
        desc_label.set_xalign(0.0)
        if is_warning:
            desc_label.get_style_context().add_class("warning-label")
        label_container.pack_start(desc_label, False, False, 0)

        if full_width:
            # For long inputs: stack vertically
            container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
            container.pack_start(label_container, False, False, 0)
            widget.set_halign(Gtk.Align.FILL)
            widget.set_hexpand(True)
            container.pack_start(widget, False, False, 0)
            row_box.pack_start(container, True, True, 0)
        else:
            # For buttons/switches/combos: place on right side
            row_box.pack_start(label_container, True, True, 0)
            widget.set_halign(Gtk.Align.END)
            widget.set_valign(Gtk.Align.START)
            row_box.pack_start(widget, False, False, 0)

        parent.pack_start(row_box, False, False, 0)

    def _add_general_section(self, parent: Gtk.Box) -> None:
        """Add general settings section"""
        general_box = self._create_section_container(parent, "General")
        run_in_bg_footer = "\n<b>Recommended:</b> Enabling this will make Ulauncher open noticeably faster."

        # Run in background (via systemd autostart, or keep-alive fallback)
        autostart_status = self.autostart_pref.status()
        if autostart_status.can_start:
            autostart_switch = Gtk.Switch(active=autostart_status.is_enabled)
            autostart_switch.connect("notify::active", self._on_autostart_toggled)
            desc = "Start Ulauncher automatically with your desktop session so it's ready when you need it."
            self._add_setting_row(general_box, "Run in background", autostart_switch, f"{desc}{run_in_bg_footer}")
        else:
            keep_alive_switch = Gtk.Switch(active=self.settings.keep_alive)
            keep_alive_switch.connect("notify::active", self._on_keep_alive_toggled)
            desc = "Keep Ulauncher running in the background after first use so it stays ready"
            self._add_setting_row(general_box, "Run in background", keep_alive_switch, f"{desc}{run_in_bg_footer}")

        self._add_tray_icon_row(general_box)

        # Hotkey
        if HotkeyController.is_supported():
            hotkey_button = Gtk.Button.new_with_label("Set hotkey")
            hotkey_button.connect("clicked", self._on_hotkey_clicked)
            hotkey_desc = "Choose the global keyboard shortcut that opens Ulauncher."
            self._add_setting_row(general_box, "Hotkey", hotkey_button, hotkey_desc)
        else:
            warning_text = (
                "Ulauncher doesn't support setting global shortcuts for your desktop environment. "
                "Bind this command in your DE settings: gapplication launch io.ulauncher.Ulauncher"
            )
            unavailable_label = Gtk.Label(label="Not available", sensitive=False)
            self._add_setting_row(general_box, "Hotkey", unavailable_label, warning_text, is_warning=True)

        # Color theme
        theme_combo = Gtk.ComboBoxText()
        themes = get_themes()
        for theme in themes:
            theme_combo.append(theme, theme)
        theme_combo.set_active_id(self.settings.theme_name)
        theme_combo.connect("changed", self._on_theme_changed)
        theme_desc = "Switch between installed themes. Changes apply immediately when you relaunch the UI."
        self._add_setting_row(general_box, "Color theme", theme_combo, theme_desc)

        # Screen to show on
        screen_combo = Gtk.ComboBoxText()
        screen_combo.append("mouse-pointer-monitor", "The screen with the mouse pointer")
        screen_combo.append("default-monitor", "The default screen")
        screen_combo.set_active_id(self.settings.render_on_screen)
        screen_combo.connect("changed", self._on_screen_changed)
        screen_desc = "Decide which monitor presents Ulauncher when you press the hotkey."
        self._add_setting_row(general_box, "Screen to show on", screen_combo, screen_desc)

        # Auto resume
        auto_resume_switch = Gtk.Switch(active=self.settings.auto_resume)
        auto_resume_switch.connect("notify::active", self._on_auto_resume_toggled)
        auto_resume_desc = "If you close Ulauncher without running the query, restore it on the next session."
        self._add_setting_row(general_box, "Auto-resume unfinished sessions", auto_resume_switch, auto_resume_desc)

        # Close on focus out
        close_focus_switch = Gtk.Switch(active=self.settings.close_on_focus_out)
        close_focus_switch.connect("notify::active", self._on_close_focus_toggled)
        focus_desc = "Hide the Ulauncher window automatically as soon as another app grabs focus."
        self._add_setting_row(general_box, "Close Ulauncher when losing focus", close_focus_switch, focus_desc)

        # Grab mouse pointer
        grab_mouse_switch = Gtk.Switch(active=self.settings.grab_mouse_pointer)
        grab_mouse_switch.connect("notify::active", self._on_grab_mouse_toggled)
        grab_desc = "Capture the pointer to prevent focus-follows-mouse setups from stealing the launcher focus."
        self._add_setting_row(general_box, "Grab mouse pointer focus", grab_mouse_switch, grab_desc)

    def _add_applications_section(self, parent: Gtk.Box) -> None:
        """Add applications settings section"""
        applications_box = self._create_section_container(parent, "Applications")

        # Enable application mode
        app_mode_switch = Gtk.Switch(active=self.settings.enable_application_mode)
        app_mode_switch.connect("notify::active", self._on_app_mode_toggled)
        desc = "Include desktop applications alongside shortcuts and extensions in search results."
        self._add_setting_row(applications_box, "Include applications in search", app_mode_switch, desc)

        # Raise if started
        raise_switch = Gtk.Switch(active=self.settings.raise_if_started, sensitive=IS_X11)
        raise_switch.connect("notify::active", self._on_raise_toggled)
        desc = "Focus an already running application instead of launching a duplicate instance. Works only on X11."

        self._add_setting_row(applications_box, "Switch to application if already running", raise_switch, desc)

        # Window width
        width_adjustment = Gtk.Adjustment(value=self.settings.base_width, lower=540, upper=2000, step_increment=10)
        width_spin = Gtk.SpinButton(adjustment=width_adjustment)
        width_spin.connect("value-changed", self._on_width_changed)
        desc = "Set the launcher width between 540 and 2000 pixels to match your workspace."
        self._add_setting_row(applications_box, "Window width", width_spin, desc)

        # Top apps
        recent_adjustment = Gtk.Adjustment(value=self.settings.max_recent_apps, lower=0, upper=20, step_increment=1)
        recent_spin = Gtk.SpinButton(adjustment=recent_adjustment)
        recent_spin.connect("value-changed", self._on_recent_apps_changed)
        desc = "Control how many frequently used applications remain pinned near the top of the results."
        self._add_setting_row(applications_box, "Number of frequent apps to show", recent_spin, desc)

    def _add_advanced_section(self, parent: Gtk.Box) -> None:
        """Add advanced settings section"""
        advanced_box = self._create_section_container(parent, "Advanced")

        # Desktop filters
        filters_switch = Gtk.Switch(active=self.settings.disable_desktop_filters)
        filters_switch.connect("notify::active", self._on_filters_toggled)
        desc = "Show applications that are hidden for your desktop environment by ignoring desktop filters."
        self._add_setting_row(advanced_box, "Include foreign desktop apps", filters_switch, desc)

        # Window shadow
        shadow_adjustment = Gtk.Adjustment(value=self.settings.window_shadow, lower=0, upper=25, step_increment=1)
        shadow_spin = Gtk.SpinButton(adjustment=shadow_adjustment)
        shadow_spin.connect("value-changed", self._on_shadow_changed)
        desc = (
            "The window shadow size. Set to 0 to disable. "
            "Shadows are also disabled if we detect your window manager cannot support them."
        )
        self._add_setting_row(advanced_box, "Window shadow size", shadow_spin, desc)

        # GTK Layer Shell
        layer_switch = Gtk.Switch(active=self.settings.layer_shell, sensitive=not IS_X11)
        layer_switch.connect("notify::active", self._on_layer_toggled)
        desc = (
            "Use Layer Shell for positioning on Wayland (when supported). "
            "Recommended unless your desktop handles Wayland positioning separately (Hyprland)"
        )
        self._add_setting_row(advanced_box, "Enable Layer Shell", layer_switch, desc)

        # Jump keys
        jump_entry = Gtk.Entry(text=self.settings.jump_keys, width_chars=50)
        jump_entry.connect("changed", self._on_jump_keys_changed)
        desc = "Configure the characters used for jumping directly to a result with modifier shortcuts."
        self._add_setting_row(advanced_box, "Jump keys", jump_entry, desc, full_width=True)

        # Terminal command
        terminal_entry = Gtk.Entry(text=self.settings.terminal_command, width_chars=50)
        terminal_entry.connect("changed", self._on_terminal_changed)
        desc = (
            "Override the terminal binary for desktop entries that request a terminal. Leave blank to use the default."
        )
        self._add_setting_row(advanced_box, "Terminal command", terminal_entry, desc, full_width=True)

    def _add_tray_icon_row(self, parent: Gtk.Box) -> None:
        # Placed next to "Run in background" because the tray icon is only effective while persistent.
        self._tray_switch = Gtk.Switch(active=self.settings.show_tray_icon, sensitive=self.settings.is_persistent())
        self._tray_switch.connect("notify::active", self._on_tray_toggled)
        desc = (
            "Display a tray icon for quick actions. Only available while Ulauncher is set to "
            "run in the background. Also requires AppIndicator3 or XApp on X11."
        )
        self._add_setting_row(parent, "Show tray icon", self._tray_switch, desc)

    # Event handlers
    def _on_autostart_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        is_enabled = switch.get_active()
        # Skip if already in sync - notably when set_active() below re-fires this handler.
        if is_enabled == self.autostart_pref.status().is_enabled:
            return
        try:
            self.autostart_pref.toggle(is_enabled)
        except OSError:
            logger.exception("Failed to toggle autostart")
            switch.set_active(not is_enabled)
            return
        self._tray_switch.set_sensitive(is_enabled)
        events.emit("app:toggle_hold", is_enabled)

    def _on_hotkey_clicked(self, _: Gtk.Button) -> None:
        HotkeyController.show_dialog()

    def _on_theme_changed(self, combo: Gtk.ComboBoxText) -> None:
        theme_name = combo.get_active_text()
        if theme_name:
            self.settings.save({"theme_name": theme_name})

    def _on_screen_changed(self, combo: Gtk.ComboBoxText) -> None:
        screen = combo.get_active_id()
        if screen:
            self.settings.save({"render_on_screen": screen})

    def _on_auto_resume_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"auto_resume": switch.get_active()})

    def _on_close_focus_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"close_on_focus_out": switch.get_active()})

    def _on_grab_mouse_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"grab_mouse_pointer": switch.get_active()})

    def _on_app_mode_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"enable_application_mode": switch.get_active()})

    def _on_raise_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"raise_if_started": switch.get_active()})

    def _on_width_changed(self, spin: Gtk.SpinButton) -> None:
        min_width = 540
        max_width = 2000
        width = spin.get_value_as_int()
        if min_width <= width <= max_width:
            self.settings.save({"base_width": width})

    def _on_recent_apps_changed(self, spin: Gtk.SpinButton) -> None:
        count = spin.get_value_as_int()
        self.settings.save({"max_recent_apps": count})

    def _on_shadow_changed(self, spin: Gtk.SpinButton) -> None:
        self.settings.save({"window_shadow": spin.get_value_as_int()})

    def _on_layer_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"layer_shell": switch.get_active()})

    def _on_tray_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        is_enabled = switch.get_active()
        self.settings.save({"show_tray_icon": is_enabled})
        events.emit("app:toggle_tray_icon", is_enabled)

    def _on_filters_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        self.settings.save({"disable_desktop_filters": switch.get_active()})

    def _on_keep_alive_toggled(self, switch: Gtk.Switch, _: Any) -> None:
        is_enabled = switch.get_active()
        self.settings.save({"keep_alive": is_enabled})
        self._tray_switch.set_sensitive(is_enabled)
        events.emit("app:toggle_hold", is_enabled)

    def _on_jump_keys_changed(self, entry: Gtk.Entry) -> None:
        self.settings.save({"jump_keys": entry.get_text()})

    def _on_terminal_changed(self, entry: Gtk.Entry) -> None:
        self.settings.save({"terminal_command": entry.get_text()})
