from typing import List
import os

import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gio, GObject

import sadb.database

STORE = {}
INSTALLED_STORE = {}

COLUMN_POINTS = {
    "name": 10,
    "src_pkg_name": 10,
    "author": 10,
    "summary": 5,
    "description": 3,
    "keywords": 5
}

class AppItem(GObject.Object):
    __gtype_name__ = "AppItem"

    def __init__(self, app_id, name, author, icon):
        super().__init__()
        self._app_id = app_id
        self._name = name
        self._author = author
        self._author_visible = self._author is not None
        self._icon = icon
        self._installed = False
        self._update = False

    @classmethod
    def new_installed(cls, app_id, name, author, icon, installed, update):
        app = cls(app_id, name, author, icon)
        app._installed = installed
        app._update = update
        return app

    @GObject.Property(type=str)
    def app_id(self):
        return self._app_id

    @app_id.setter
    def app_id(self, value):
        self._app_id = value

    @GObject.Property(type=str)
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @GObject.Property(type=str)
    def author(self):
        return self._author

    @author.setter
    def author(self, value):
        self._author = value
        self.author_visible = self._author is not None

    @GObject.Property(type=bool, default=True)
    def author_visible(self):
        return self._author_visible

    @author_visible.setter
    def author_visible(self, value):
        self._author_visible = value

    @GObject.Property(type=str)
    def icon(self):
        return self._icon

    @icon.setter
    def icon(self, value):
        self._icon = value

    @GObject.Property(type=bool, default=False)
    def installed(self):
        return self._installed

    @installed.setter
    def installed(self, value):
        self._installed = value

    @GObject.Property(type=bool, default=False)
    def update(self):
        return self._update

    @update.setter
    def update(self, value):
        self._update = value


def refresh_app_store():
    global STORE
    STORE = {"all": Gio.ListStore()}
    
    # Clear search cache when app store is refreshed
    if hasattr(search_algorithm, '_cache'):
        search_algorithm._cache.clear()

    db = sadb.database.get_readable_db()

    db.c.execute("SELECT id, name, author, icon_url, categories, keywords FROM apps")
    apps_columns = db.c.fetchall()


    for app in apps_columns:
        categories_raw = app[4] or ""
        keywords_raw = app[5] or ""
        categories = [category.strip() for category in categories_raw.split(",") if category.strip()]
        keywords = [keyword.strip() for keyword in keywords_raw.split(",") if keyword.strip()]

        app_item = AppItem(app[0], app[1], app[2], app[3])

        for category in categories:
            add_to_category(category, app_item)

            # Used to separate audio and video tools that may have categories such as
            # "Audio", "AudioVideo", "Editor", "Player"
            if "Audio" in categories:
                add_to_category("Audio" + category, app_item)
            if "Video" in categories:
                add_to_category("Video" + category, app_item)

        for keyword in keywords:
            add_to_category("keyword-" + keyword, app_item)
        STORE["all"].append(app_item)


def add_to_category(category, app_item):#
    if category not in STORE.keys():
        STORE[category] = Gio.ListStore()
    STORE[category].append(app_item)


def has_swai_desktop_file(app_id: str) -> bool:
    desktop_filename = f"{app_id}.desktop"
    desktop_paths = (
        os.path.join("/usr/share/applications", desktop_filename),
        os.path.join("/usr/local/share/applications", desktop_filename),
    )
    return any(os.path.exists(path) for path in desktop_paths)


def refresh_installed_store():
    global INSTALLED_STORE
    INSTALLED_STORE = {"installed": Gio.ListStore(), "update": Gio.ListStore(), "no_update": Gio.ListStore()}

    db = sadb.database.get_readable_db()
    db.c.execute("SELECT id, name, author, icon_url, categories, keywords, update_available FROM installed")
    apps_columns = db.c.fetchall()

    for app in apps_columns:
        app_id = app[0]
        # SWAI entries require a desktop file. Skip invalid entries so they
        # don't show up as installed when their desktop file is missing.
        if app_id.startswith("swai.") and not has_swai_desktop_file(app_id):
            continue

        app_item = AppItem.new_installed(app[0], app[1], app[2], app[3], True, app[6])

        INSTALLED_STORE["installed"].append(app_item)
        if app[6]:
            INSTALLED_STORE["update"].append(app_item)
        else:
            INSTALLED_STORE["no_update"].append(app_item)


def search_algorithm(query: str) -> Gio.ListStore:
    db = sadb.database.get_readable_db()
    list_store = Gio.ListStore()
    seen_ids = set()  # Track seen app IDs to avoid duplicates

    sql = """
        SELECT apps.id, apps.name, apps.author, apps.icon_url FROM apps_fts
        JOIN apps ON apps.rowid = apps_fts.rowid  
        WHERE apps_fts MATCH ?
        ORDER BY rank
    """
    
    # Strategy 1: Try exact match first
    results = list(db.c.execute(sql, (query,)))
    
    # Strategy 2: If no exact results, try prefix matching
    if not results:
        prefix_query = f"{query}*"
        results = list(db.c.execute(sql, (prefix_query,)))
    
    # Add results to list store, avoiding duplicates
    for row in results:
        if row[0] not in seen_ids:
            seen_ids.add(row[0])
            app_item = AppItem(row[0], row[1], row[2], row[3])
            list_store.append(app_item)

    return list_store
