import stillCenter.constants as constants
import sadb.database
import threading
import os
import logging
import gi
from sam.quick import get_queue_dicts

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gtk, Adw, GLib, Gio, Gdk

# Import templates for xml builder
import stillCenter.AppStore as AppStore
import stillCenter.AppPage as AppPage
import stillCenter.FlowApps as FlowApps
import stillCenter.NetworkErrors as NetworkErrors
import stillCenter.AppGridView as AppGridView  # Needed for builder
import stillCenter.AppListView as AppListView  # Needed for builder
import stillCenter.CategoryPage as CategoryPage

from stillCenter.CategoryButtonBig import CategoryButtonBig
from stillCenter.SamInterface import sam_interface, get_sam_error
from stillCenter.LoadingPage import LoadingPage
from stillCenter.ErrorPage import ErrorPage

logger = logging.getLogger(__name__)


class StillCenter(Adw.Application):
    def __init__(self):
        super().__init__(application_id="io.stillhq.stillCenter")

        self.builder = Gtk.Builder()
        self.builder.add_from_file(os.path.join(constants.UI_DIR, "stillCenter.ui"))

        # Get objects from the builder
        self.window = self.builder.get_object("window")
        self.sidebar = self.builder.get_object("main_sidebar")
        self.sidebar_split = self.builder.get_object("sidebar_split_view")
        self.main_stack = self.builder.get_object("main_stack")
        self.main_view = self.builder.get_object("main_view")
        self.home_sections = self.builder.get_object("home_sections")
        self.categories_flowbox = self.builder.get_object("categories_flowbox")
        self.search_stack = self.builder.get_object("search_stack")
        self.search_app_list = self.builder.get_object("search_app_list")
        self.search_entry = self.builder.get_object("search_entry")
        self.search_placeholder = self.builder.get_object("search_placeholder")
        self.update_box = self.builder.get_object("update_box")
        self.update_all_button = self.builder.get_object("update_all_button")
        self.available_updates = self.builder.get_object("available_updates")
        self._search_timeout_id = None
        self.sam_access_allowed = True
        self._sam_access_warning_shown = False

        # Set Window Name
        self.window.set_title("stillCenter")

        # Setting up the sidebar
        self.sidebar_index = []
        main_stack_pages = self.main_stack.get_pages()
        for i in range(main_stack_pages.get_n_items()):
            stack_page = main_stack_pages.get_item(i)
            sidebar_row = Gtk.ListBoxRow()
            label = Gtk.Label(label=stack_page.get_title(), xalign=0)
            sidebar_row.set_child(label)
            self.sidebar_index.append(stack_page.get_name())
            self.sidebar.append(sidebar_row)
        self.sidebar.connect("row-selected", self.sidebar_selected)

        # Populating Categories
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Audio", "Audio", "audio-x-generic-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Development", "Development", "applications-engineering-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Education", "Education", "accessories-dictionary-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Game", "Games", "applications-games-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Graphics", "Graphics", "applications-graphics-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Network", "Network", "preferences-system-network-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Office", "Office", "x-office-document-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Utility", "Utilities", "applications-utilities-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "Video", "Video", "applications-multimedia-symbolic")
        )
        self.categories_flowbox.append(
            CategoryButtonBig(self, "System", "System", "preferences-other-symbolic")
        )

        # Check for SAM error and push error screen
        sam_error, sam_error_msg = get_sam_error()
        if sam_error:
            logger.error("SAM startup check failed: %s", sam_error_msg)
            errorpage = ErrorPage(
                self, "still App Manager Error", sam_error_msg + \
                "\n\nCheck if the SAM systemd service is running,"
                " or check the systemd status of SAM for possible errors."
            )
            errorpage.push()
            return

        self.sam_access_allowed = self.get_sam_access_allowed()

        # Setup search
        self.search_entry.connect("changed", self.search_changed)

        # Loading base pages
        self.app_page = AppPage.AppPage(self)
        self.category_page = CategoryPage.CategoryPage(self)

        # Set update button
        sam_interface.connect_to_signal("queue_changed", self.run_update_installed_page)
        self.update_all_button.connect("clicked", self.update_all)

        # Loading screen
        self.loading_screen = LoadingPage(self)

        self.loading_screen.push()
        thread = threading.Thread(target=self.load_apps)
        thread.start()

    def set_loading_status(self, title: str, description: str = ""):
        GLib.idle_add(self.loading_screen.set_status, title, description)

    def get_sam_access_allowed(self) -> bool:
        try:
            return bool(sam_interface.can_access())
        except Exception:
            logger.exception("Failed to query SAM access; assuming access is allowed")
            return True

    def show_browse_only_warning(self):
        if self._sam_access_warning_shown or self.sam_access_allowed:
            return

        dialog = AppPage.TextDialog(
            "Limited access",
            "You do not have permission to install, update, or remove apps with stillCenter.\n\n"
            "You can still browse the app catalog and view app details."
        )
        dialog.present(self.window)
        self._sam_access_warning_shown = True

    def show_install_permission_denied_dialog(self, app_name: str | None = None):
        message = "You do not have permission to install apps with stillCenter.\n\n"
        if app_name:
            message += f"Ask your administrator to install {app_name} for you."
        else:
            message += "Ask your administrator to install the app for you."

        dialog = AppPage.TextDialog("Install permission required", message)
        dialog.present(self.window)

    def show_manage_permission_denied_dialog(self, action_name: str):
        dialog = AppPage.TextDialog(
            "Administrator permission required",
            f"You do not have permission to {action_name} apps with stillCenter.\n\n"
            "Ask your administrator to manage this app for you."
        )
        dialog.present(self.window)

    def update_all(self, button):
        if not self.sam_access_allowed:
            self.show_manage_permission_denied_dialog("update")
            return
        logger.info("User requested update-all from stillCenter")
        thread = threading.Thread(target=lambda: sam_interface.update_all(False))
        thread.start()

    def run_update_installed_page(self):
        thread = threading.Thread(target=self.update_installed_page)
        thread.start()

    # Update installed page
    def update_installed_page(self):
        try:
            logger.info("Refreshing installed app database")
            sadb.database.update_installed()
        except FileNotFoundError as error:
            # Some sources (e.g. SWAI) may reference removed desktop files.
            # Skip those entries instead of crashing the background loader.
            logger.warning("Skipping unreadable desktop entry: %s", error)
        except Exception:
            logger.exception("Failed to refresh installed app database")

        AppStore.refresh_installed_store()

        GLib.idle_add(self.update_installed_gui)

    def update_installed_gui(self):
        # Set models of ListViews
        self.builder.get_object("installed").set_store(self, AppStore.INSTALLED_STORE["no_update"])
        self.builder.get_object("available_updates").set_store(self, AppStore.INSTALLED_STORE["update"])

        if AppStore.INSTALLED_STORE["update"].get_n_items() == 0:
            self.available_updates.set_visible(False)
            self.update_box.set_visible(False)
        else:
            self.available_updates.set_visible(True)
            self.update_box.set_visible(True)

        queue = get_queue_dicts()
        if not queue:
            queue = []  # Prevent function from breaking if queue is empty
        if len(queue) != 0:
            self.update_all_button.set_sensitive(False)
        else:
            self.update_all_button.set_sensitive(True)

    def load_apps(self):
        try:
            logger.info("Loading app repository")
            self.set_loading_status(
                "Downloading app data...",
                "Fetching the latest app catalog for stillCenter..."
            )
            sadb.database.update_db()

            self.set_loading_status(
                "Building app index...",
                "Preparing the searchable app list..."
            )
            AppStore.refresh_app_store()
            logger.info("App repository loaded successfully")
        except Exception as error:  # pylint: disable=broad-exception-caught
            logger.exception("Failed to load app repository")
            GLib.idle_add(self.show_repo_error, error)
            return

        self.set_loading_status(
            "Checking installed apps...",
            "Scanning what is already installed and what can be updated..."
        )

        self.update_installed_page()

        self.set_loading_status(
            "Loading homepage...",
            "Preparing featured sections and finishing startup..."
        )
        GLib.idle_add(lambda: self.populate_apps())
        GLib.idle_add(self.loading_screen.pop)

    def retry_initial_load(self, error_page=None):
        if error_page is not None:
            error_page.pop()

        logger.info("Retrying initial stillCenter load")
        self.loading_screen.set_status(
            "Retrying startup...",
            "Trying to connect again and finish loading stillCenter..."
        )
        self.loading_screen.push()
        thread = threading.Thread(target=self.load_apps)
        thread.start()

    def show_repo_error(self, error):
        logger.error("Showing repository error to user: %s", error)
        self.loading_screen.pop()
        title = "App repository could not be loaded"
        description = f"stillCenter could not load the app repository.\n\n{error}"

        if NetworkErrors.is_connection_error(error):
            title = NetworkErrors.get_connection_error_title(error)
            description = NetworkErrors.build_connection_error_description("the app repository", error)

        errorpage = ErrorPage(
            self,
            title,
            description
        )
        errorpage.set_icon("network-error-symbolic")
        errorpage.set_retry(self.retry_initial_load)
        errorpage.push()
        return GLib.SOURCE_REMOVE

    def populate_apps(self):
        self.search_placeholder.set_title(f"Search from {len(AppStore.STORE["all"])} Apps")
        FlowApps.populate_apps(self.home_sections, self)

    def sidebar_selected(self, _listbox, row):
        self.main_stack.set_visible_child_name(self.sidebar_index[row.get_index()])
        if self.sidebar_split.get_collapsed() and not self.sidebar_split.get_show_content():
            self.sidebar_split.set_show_content(True)

    def search_changed(self, entry):
        # Cancel any pending search
        if self._search_timeout_id:
            GLib.source_remove(self._search_timeout_id)
        
        # Wait 250ms before searching to wait for user to stop typing
        # Removed immediate search call to prevent duplicate searches
        self._search_timeout_id = GLib.timeout_add(250, self.do_search, entry.get_text())
        logger.debug("Scheduled search timeout id %s", self._search_timeout_id)


    def do_search(self, query: str):
        query = query.strip()
        if len(query) >= 2:  # Require at least 2 characters for search
            store = AppStore.search_algorithm(query)

            if len(store) == 0:
                self.search_stack.set_visible_child_name("no_results")
                return

            self.search_app_list.set_store(self, store)
            self.search_stack.set_visible_child_name("results")
        else:
            self.search_stack.set_visible_child_name("search_placeholder")

    def do_activate(self):
        self.window.set_application(self)
        self.window.present()
        self.show_browse_only_warning()
