import os
import threading
import logging
from typing import Optional

import gi

import sadb.database
import stillCenter.constants as constants
from stillCenter.SamInterface import sam_interface
from stillCenter.i18n import _

import stillCenter.UrlImage as UrlImage
from sam.quick import get_queue_dicts

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

logger = logging.getLogger(__name__)


def clear_css_classes(widget):
    for css_class in widget.get_css_classes():
        widget.remove_css_class(css_class)


def set_row_if_not_none(row, value):
    if value is not None and value != "":
        row.set_visible(True)
        row.set_subtitle(value)
    else:
        row.set_visible(False)


def still_rating_to_display(rating: sadb.StillRating) -> tuple[str, Optional[str]]:
    match rating:
        case sadb.StillRating.WARNING:
            return _("Warning"), "warning"
        case sadb.StillRating.BRONZE:
            return _("Bronze"), "bronze"
        case sadb.StillRating.SILVER:
            return _("Silver"), "silver"
        case sadb.StillRating.GOLD:
            return _("Gold"), "gold"
        case sadb.StillRating.GOLD_PLUS:
            return _("Gold+"), "gold-plus"
        case sadb.StillRating.SWAI:
            return _("Powered by SWAI"), "swai"
        case _:
            return _("Unknown"), None


class AppPage:
    button_style_applied = False
    installed = False
    update_available = False
    current_queue_action = None
    app = None

    try:
        db = sadb.database.get_readable_db()

    # This is a workaround to avoid importing sqlite3
    except Exception as e:  # pylint: disable=broad-exception-caught
        if e.__class__.__name__ == "OperationalError":
            sadb.database.update_db()
            sadb.database.update_installed()
            db = sadb.database.get_readable_db()
        else:
            raise  # Re-raise if it's not an OperationalError

    def __init__(self, stillCenter):
        self.stillCenter = stillCenter
        self.db = None
        self.page_builder = Gtk.Builder()
        self.page_builder.set_translation_domain(constants.GETTEXT_DOMAIN)
        self.page_builder.add_from_file(os.path.join(constants.UI_DIR, "AppPage.ui"))
        self.nav_page = self.page_builder.get_object("nav_page")
        self.icon = self.page_builder.get_object("icon")
        self.name = self.page_builder.get_object("name_label")
        self.author = self.page_builder.get_object("author_label")
        self.rating_button = self.page_builder.get_object("rating_button")
        self.rating_label = self.page_builder.get_object("rating_label")
        self.remove_button = self.page_builder.get_object("remove_button")
        self.install_button = self.page_builder.get_object("install_button")
        self.update_button = self.page_builder.get_object("update_button")
        self.screenshot_overlay = self.page_builder.get_object("screenshot_overlay")
        self.screenshot_carousel = self.page_builder.get_object("screenshot_carousel")
        self.next_screenshot = self.page_builder.get_object("next_screenshot")
        self.back_screenshot = self.page_builder.get_object("back_screenshot")
        self.next_ss_revealer = self.page_builder.get_object("next_ss_revealer")
        self.back_ss_revealer = self.page_builder.get_object("back_ss_revealer")
        self.summary = self.page_builder.get_object("summary_label")
        self.description = self.page_builder.get_object("description")
        self.homepage_row = self.page_builder.get_object("homepage_row")
        self.donate_row = self.page_builder.get_object("donate_row")
        self.manual_link_row = self.page_builder.get_object("manual_link_row")
        self.manual_instructions_row = self.page_builder.get_object("manual_instructions_row")
        self.docs_link_row = self.page_builder.get_object("docs_link_row")
        self.source_row = self.page_builder.get_object("source_row")
        self.category_row = self.page_builder.get_object("category_row")
        self.keyword_row = self.page_builder.get_object("keyword_row")
        self.install_button_spinner = self.page_builder.get_object("install_button_spinner")
        self.refresh_installed = False

        sam_interface.connect_to_signal("queue_changed", self.queue_changed)
        sam_interface.connect_to_signal("progress_changed", self.percent_changed)
        self.rating_button.connect("clicked", self.rating_button_clicked)
        self.install_button.connect("clicked", lambda button: self.install_button_clicked())
        self.update_button.connect("clicked", lambda button: self.run_action("update"))
        self.remove_button.connect("clicked", lambda button: self.run_action("remove"))
        self.screenshot_carousel.connect("page-changed", self.carousel_changed)
        self.next_screenshot.connect("clicked", self.ss_btn_clicked, 1)
        self.back_screenshot.connect("clicked", self.ss_btn_clicked, -1)
        self.homepage_row.connect("activated", lambda _row: self.open_url(self.app.homepage))
        self.donate_row.connect("activated", lambda _row: self.open_url(self.app.donate_url))
        self.manual_link_row.connect("activated", lambda _row: self.open_url(self.app.manual_url))
        self.docs_link_row.connect("activated", lambda _row: self.open_url(self.app.docs_link))

        # Add stillControl CSS on first initialization
        if AppPage.button_style_applied is False:
            css = Gtk.CssProvider()
            css.load_from_file(Gio.File.new_for_path(os.path.join(constants.UI_DIR, "stillControl.css")))
            Gtk.StyleContext.add_provider_for_display(
                self.rating_button.get_display(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
            )
            AppPage.button_style_applied = True

    def ensure_db(self):
        if self.db is None:
            self.db = sadb.database.get_readable_db()
        return self.db

    def queue_changed(self):
        self.refresh_installed = True
        self.update_buttons()
        self.refresh_installed = False

    def id_in_installed_database(self, app_id):
        self.ensure_db()
        self.db.c.execute("SELECT id FROM installed WHERE id = ?", (app_id,))
        result = self.db.c.fetchone()
        return result is not None

    def show_app(self, app_id):
        self.ensure_db()
        # Get an installed app if it exists, or try to get a remote app
        if self.id_in_installed_database(app_id):
            self.app = self.db.get_installed_app_by_id(app_id)
            if self.app is not None:
                self.installed = True
                self.update_available = self.app.update_available
            else:
                self.app = None
        else:
            self.installed = False
            self.app = self.db.get_app(app_id)

        if self.app is None:
            return

        # clear screenshot boxes
        child = self.screenshot_carousel.get_first_child()
        while child is not None:
            self.screenshot_carousel.remove(child)
            child = self.screenshot_carousel.get_first_child()

        # Set icon, name, author
        self.icon.set_image_url(self.app.app_id, self.app.icon_url)
        display_name = self.app.appstream_name or self.app.name or self.app.app_id
        self.name.set_label(display_name)
        self.author.set_visible(True)
        self.author.set_label(self.app.author or _("Unknown Author"))

        # Set rating text and rating css
        rating_text, rating_css = still_rating_to_display(self.app.still_rating)
        self.rating_label.set_label(rating_text)
        clear_css_classes(self.rating_button)
        if rating_css is not None:
            self.rating_button.add_css_class(rating_css)
            self.rating_button.add_css_class(f"{rating_css}-button")

        # summary and description
        summary = self.app.summary or display_name
        self.summary.set_label(summary)
        self.description.set_label(self.app.description or summary)

        # Set info rows
        set_row_if_not_none(self.homepage_row, self.app.homepage)
        self.homepage_row.set_activatable(self.app.homepage is not None and self.app.homepage != "")
        set_row_if_not_none(self.donate_row, self.app.donate_url)
        self.donate_row.set_activatable(self.app.donate_url is not None and self.app.donate_url != "")
        set_row_if_not_none(self.manual_link_row, self.app.manual_url)
        self.manual_link_row.set_activatable(self.app.manual_url is not None and self.app.manual_url != "")
        self.manual_link_row.set_title(self.manual_link_title())
        set_row_if_not_none(self.manual_instructions_row, self.app.manual_instructions)
        set_row_if_not_none(self.docs_link_row, self.app.docs_link)
        self.docs_link_row.set_activatable(self.app.docs_link is not None and self.app.docs_link != "")
        self.source_row.set_title(_("Primary Source: {source}").format(source=self.app.primary_src))
        self.source_row.set_subtitle(self.app.src_pkg_name)
        set_row_if_not_none(self.category_row, ", ".join(self.app.categories))
        set_row_if_not_none(self.keyword_row, ", ".join(self.app.keywords))

        # Add screenshots
        for screenshot in self.app.screenshot_urls:
            if screenshot != "":
                image = UrlImage.UrlImage()
                image.screenshot = True
                image.set_vexpand(True)
                image.set_hexpand(True)
                image.set_valign(Gtk.Align.FILL)
                image.set_halign(Gtk.Align.FILL)
                image.image.set_vexpand(True)
                image.image.set_hexpand(True)
                image.image.set_valign(Gtk.Align.FILL)
                image.image.set_halign(Gtk.Align.FILL)
                image.set_image_url(self.app.app_id, screenshot)

                click = Gtk.GestureClick()
                click.connect("released", self.open_screenshot_dialog, self.app.app_id, screenshot)
                image.add_controller(click)

                self.screenshot_carousel.append(image)
            else:
                self.app.screenshot_urls.remove(screenshot)
        self.screenshot_overlay.set_visible(len(self.app.screenshot_urls) > 0)
        self.next_ss_revealer.set_reveal_child(len(self.app.screenshot_urls) > 1)
        self.carousel_changed(self.screenshot_carousel, 0)

        # Show the page
        main_view = self.stillCenter.builder.get_object("main_view")
        main_view.push(self.nav_page)

        self.update_buttons()

    def manual_link_title(self) -> str:
        flags = [flag.lower() for flag in self.app.manual_flags or []]
        if "appimage" in flags:
            return _("AppImage")
        return _("Manual Install Link")

    def manual_install_button_label(self) -> str:
        flags = [flag.lower() for flag in self.app.manual_flags or []]
        if "appimage" in flags:
            return _("AppImages Available")
        return _("Manual Installation")

    def has_manual_install_option(self) -> bool:
        return (
            self.app is not None
            and (
                (self.app.manual_url is not None and self.app.manual_url != "")
                or (self.app.manual_instructions is not None and self.app.manual_instructions != "")
            )
        )

    def open_url(self, url: str):
        if url is None or url == "":
            return
        Gtk.show_uri(self.nav_page, url, Gdk.CURRENT_TIME)

    def install_button_clicked(self):
        if self.has_manual_install_option():
            flags = [flag.lower() for flag in self.app.manual_flags or []]
            if "appimage" in flags and self.app.manual_url is not None and self.app.manual_url != "":
                self.open_url(self.app.manual_url)
                return

            dialog = ManualInstallDialog(
                self.manual_install_button_label(),
                self.app.manual_instructions,
                self.app.manual_url,
                self.open_url,
            )
            dialog.present(self.install_button)
            return
        self.run_action("install")


    def carousel_changed(self, carousel: Adw.Carousel, index: int):
        if index > 0:
            self.back_ss_revealer.set_reveal_child(True)
        else:
            self.back_ss_revealer.set_reveal_child(False)

        if index + 1 >= carousel.get_n_pages():
            self.next_ss_revealer.set_reveal_child(False)
        else:
            self.next_ss_revealer.set_reveal_child(True)


    def ss_btn_clicked(self, _button: Gtk.Button, changed_by: int):
        screenshots = self.screenshot_carousel.observe_children()
        index = round(self.screenshot_carousel.get_position())
        if 0 <= index + changed_by < screenshots.get_n_items():
            self.screenshot_carousel.scroll_to(screenshots.get_item(index + changed_by), True)

    def open_screenshot_dialog(self, _gesture, _n_press, _x, _y, app_id: str, screenshot_url: str):
        dialog = ScreenshotDialog(app_id, screenshot_url)
        dialog.present(self.screenshot_overlay)


    def rating_button_clicked(self, _button):
        rating_text, rating_css = still_rating_to_display(self.app.still_rating)
        if self.app.still_rating == sadb.StillRating.UNKNOWN:
            dialog = TextDialog(
                _("stillRating: <b>Unknown</b>"),
                _("stillHQ has not rated this app. Therefore no rating notes are available.")
            )
        else:
            dialog = TextDialog(
                _("stillRating: <b>{rating}</b>").format(rating=rating_text),
                self.app.still_rating_notes
            )
            dialog.headerbar.add_css_class(rating_css)
            dialog.box.add_css_class(rating_css)
        dialog.present(self.rating_button)

    def percent_changed(self, percent):
        if self.current_queue_action is not None:
            match self.current_queue_action:
                case "install":
                    self.install_button.set_label(_("Installing {percent}%").format(percent=percent))
                case "remove":
                    self.install_button.set_label(_("Removing {percent}%").format(percent=percent))
                case "update":
                    self.install_button.set_label(_("Updating {percent}%").format(percent=percent))

    def update_buttons(self):
        if self.app is None:
            return
        self.current_queue_action = None
        queue = get_queue_dicts()
        if not queue:
            queue = []  # Prevent function from breaking if queue is empty
        if len(queue) != 0:
            if self.app.src_pkg_name in [action["package_id"] for action in queue]:
                self.install_button.set_label(_("Waiting"))
                self.install_button.set_sensitive(False)
                self.update_button.set_visible(False)
                self.remove_button.set_visible(False)
                self.install_button_spinner.set_visible(False)
                # Filter queue to get items for this app's manager
                # Each manager processes its queue independently
                manager_queue = [a for a in queue if a["manager_id"] == self.app.primary_src]
                if manager_queue and self.app.src_pkg_name == manager_queue[0]["package_id"]:
                    self.current_queue_action = manager_queue[0]["task"].lower()
                    self.percent_changed(manager_queue[0]["progress"])
            else:
                self.update_buttons_not_in_queue()
        else:
            self.update_buttons_not_in_queue()

    def update_buttons_not_in_queue(self):
        if self.app is None:
            return

        if self.refresh_installed:
            # Show loading state immediately
            self.show_loading_state()
        
            # Update the installed database asynchronously
            thread = threading.Thread(target=self.async_update_installed)
            thread.start()
        else:
            self.finish_button_update()

    def show_loading_state(self):
        """Show loading state while updating database"""
        self.install_button.set_label(_("Install"))
        self.install_button.set_sensitive(False)
        self.install_button.remove_css_class("suggested-action")
        self.update_button.set_visible(False)
        self.remove_button.set_visible(False)
        self.install_button_spinner.set_visible(True)

    def async_update_installed(self):
        """Update installed database in background thread"""
        try:
            # Update the installed database to ensure we have the latest information
            logger.info("Refreshing installed database from AppPage")
            sadb.database.update_installed()
            
            # Schedule UI update on main thread
            GLib.idle_add(self.finish_button_update)
        except Exception:
            logger.exception("Error updating installed database")
            # Still try to update UI even if database update fails
            GLib.idle_add(self.finish_button_update)

    def finish_button_update(self):
        """Finish updating buttons after database update (runs on main thread)"""
        if self.app is None:
            return
            
        # Hide spinner
        self.install_button_spinner.set_visible(False)
        
        database = sadb.database.get_readable_db()
        database.c.execute("SELECT id, update_available FROM installed WHERE src_pkg_name = ?",
                           (self.app.src_pkg_name,))  # temp
        app = database.c.fetchone()

        if app is not None:
            self.installed = True
            if app[1] == 1:
                self.update_available = True
            else:
                self.update_available = False
        else:
            self.installed = False
            self.update_available = False

        if self.installed:
            self.install_button.set_sensitive(False)
            self.install_button.set_label(_("Installed"))
            self.install_button.remove_css_class("suggested-action")
            self.update_button.set_visible(self.update_available)
            self.remove_button.set_visible(True)
            # self.try_btn.set_visible(False)
        else:
            if self.has_manual_install_option():
                self.install_button.set_label(self.manual_install_button_label())
            else:
                self.install_button.set_label(_("Install"))
            self.install_button.add_css_class("suggested-action")
            self.install_button.set_sensitive(True)
            self.update_button.set_visible(False)
            self.remove_button.set_visible(False)
            # self.try_btn.set_visible(self.app.demo_url is not None and self.app.demo_url != "")


    def run_action(self, action):
        display_name = self.app.appstream_name or self.app.name
        if not self.stillCenter.sam_access_allowed:
            if action == "install":
                self.stillCenter.show_install_permission_denied_dialog(display_name)
            else:
                self.stillCenter.show_manage_permission_denied_dialog(action)
            return

        sam_interface.add_dict_to_queue(
            {
                "package_id": self.app.src_pkg_name,
                "app_name": display_name,
                "task": action,
                "manager_id": self.app.primary_src,
                "background": "False"
            }
        )


class ManualInstallDialog(Adw.Dialog):
    def __init__(self, title, instructions, url, open_url_callback):
        super().__init__()
        self.set_title(title)
        self.set_content_width(520)
        self.set_content_height(280)
        self.set_presentation_mode(Adw.DialogPresentationMode.FLOATING)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)

        label = Gtk.Label(xalign=0)
        label.set_wrap(True)
        label.set_valign(Gtk.Align.START)
        label.set_vexpand(True)
        label.set_label(instructions or _("Open the link to continue installation."))
        box.append(label)

        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        button_box.set_halign(Gtk.Align.END)

        close_button = Gtk.Button(label=_("Close"))
        close_button.connect("clicked", lambda _btn: self.close())
        button_box.append(close_button)

        if url is not None and url != "":
            link_button = Gtk.Button(label=_("Open Link"))
            link_button.add_css_class("suggested-action")
            link_button.connect("clicked", lambda _btn: (open_url_callback(url), self.close()))
            button_box.append(link_button)

        box.append(button_box)
        self.set_child(box)


class TextDialog(Adw.Dialog):
    def __init__(self, title, text):
        super().__init__()
        self.set_title(title)
        self.set_content_width(550)
        self.set_content_height(500)
        self.set_presentation_mode(Adw.DialogPresentationMode.FLOATING)

        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        self.headerbar = Adw.HeaderBar()
        self.title = Gtk.Label()
        self.title.set_markup(title)
        self.headerbar.set_title_widget(self.title)
        self.headerbar.add_css_class("flat")
        self.box.append(self.headerbar)

        self.scroll = Gtk.ScrolledWindow()
        self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        self.text_label = Gtk.Label(label=text, xalign=0)
        self.text_label.set_margin_top(10)
        self.text_label.set_margin_bottom(10)
        self.text_label.set_margin_start(10)
        self.text_label.set_margin_end(10)
        self.text_label.set_vexpand(True)
        self.text_label.set_hexpand(True)
        self.text_label.set_valign(Gtk.Align.START)
        self.text_label.set_halign(Gtk.Align.FILL)
        self.text_label.set_wrap(True)
        self.scroll.set_child(self.text_label)

        self.box.append(self.scroll)
        self.set_child(self.box)


class ScreenshotDialog(Adw.Dialog):
    def __init__(self, app_id: str, screenshot_url: str):
        super().__init__()
        self.set_title(_("Screenshot"))
        self.set_content_width(1200)
        self.set_content_height(800)
        self.set_presentation_mode(Adw.DialogPresentationMode.FLOATING)

        image = UrlImage.UrlImage()
        image.screenshot = True
        image.set_hexpand(True)
        image.set_vexpand(True)
        image.image.set_content_fit(Gtk.ContentFit.CONTAIN)
        image.set_image_url(app_id, screenshot_url)

        overlay = Gtk.Overlay()
        overlay.set_child(image)

        close_button = Gtk.Button(icon_name="window-close-symbolic")
        close_button.set_halign(Gtk.Align.END)
        close_button.set_valign(Gtk.Align.START)
        close_button.set_margin_top(8)
        close_button.set_margin_end(8)
        close_button.set_has_frame(False)
        close_button.set_size_request(26, 26)
        close_button.add_css_class("circular")
        close_button.add_css_class("flat")
        close_button.add_css_class("screenshot-close-button")
        close_button.connect("clicked", lambda _btn: self.close())
        overlay.add_overlay(close_button)

        self.set_child(overlay)
