"""XDG applications service"""

import json
import os
import re
import shlex
import subprocess
from gettext import gettext as _

from gi.repository import Gio

from lutris import settings
from lutris.database.games import get_games_where
from lutris.services.base import BaseService
from lutris.services.service_game import ServiceGame
from lutris.services.service_media import ServiceMedia
from lutris.util import system
from lutris.util.log import logger
from lutris.util.strings import slugify


def get_appid(app):
    """Get the appid for the game"""
    try:
        return os.path.splitext(app.get_id())[0]
    except UnicodeDecodeError:
        logger.exception(
            "Failed to read ID for app %s (non UTF-8 encoding). Reverting to executable name.",
            app,
        )
        return app.get_executable()


class XDGMedia(ServiceMedia):
    service = "xdg"
    source = "local"
    size = (64, 64)
    dest_path = os.path.join(settings.CACHE_DIR, "xdg/icons")
    file_patterns = ["%s.png"]


class XDGService(BaseService):
    id = "xdg"
    name = _("Local")
    icon = "linux"
    runner = "linux"
    online = False
    local = True
    medias = {"icon": XDGMedia}

    ignored_games = ("lutris",)
    ignored_executables = ("lutris", "steam")
    ignored_categories = ("Emulator", "Development", "Utility")

    @classmethod
    def iter_xdg_games(cls):
        """Iterates through XDG games only"""
        for app in Gio.AppInfo.get_all():
            if cls._is_importable(app):
                yield app

    @property
    def lutris_games(self):
        """Iterates through Lutris games imported from XDG"""
        for game in get_games_where(runner=XDGGame.runner, installer_slug=XDGGame.installer_slug, installed=1):
            yield game

    @classmethod
    def _is_importable(cls, app):
        """Returns whether a XDG game is importable to Lutris"""
        appid = get_appid(app)
        executable = app.get_executable() or ""
        if any(
            [
                app.get_nodisplay() or app.get_is_hidden(),  # App is hidden
                not executable,  # Check app has an executable
                appid.startswith("net.lutris"),  # Skip lutris created shortcuts
                appid.lower() in map(str.lower, cls.ignored_games),  # game blacklisted
                executable.lower() in cls.ignored_executables,  # exe blacklisted
            ]
        ):
            return False

        # must be in Game category
        categories = app.get_categories() or ""
        categories = list(filter(None, categories.lower().split(";")))
        if "game" not in categories:
            return False

        # contains a blacklisted category
        ignored_categories = set(c.casefold() for c in cls.ignored_categories)
        if any(c for c in categories if c in ignored_categories):
            return False
        return True

    def match_games(self):
        """XDG games aren't on the lutris website"""
        return

    def load(self):
        """Return the list of games stored in the XDG menu."""
        xdg_games = [XDGGame.new_from_xdg_app(app) for app in self.iter_xdg_games()]
        for game in xdg_games:
            game.save()
        return xdg_games

    def generate_installer(self, db_game):
        details = json.loads(db_game["details"])
        return {
            "name": db_game["name"],
            "version": "XDG",
            "slug": db_game["slug"],
            "game_slug": self.get_installed_slug(db_game),
            "runner": self.get_installed_runner_name(db_game),
            "script": {
                "game": {
                    "exe": details["exe"],
                    "args": details["args"],
                    "working_dir": details["path"],
                },
                "system": {"disable_runtime": True},
            },
        }

    def get_installed_runner_name(self, db_game):
        return self.runner

    def get_game_directory(self, installer):
        """Pull install location from installer"""
        return os.path.dirname(installer["script"]["game"]["exe"])


class XDGGame(ServiceGame):
    """XDG game (Linux game with a desktop launcher)"""

    service = "xdg"
    runner = "linux"
    installer_slug = "desktopapp"

    @staticmethod
    def get_app_icon(xdg_app):
        """Return the name of the icon for an XDG app if one if set"""
        icon = xdg_app.get_icon()
        if not icon:
            return ""
        return icon.to_string()

    @classmethod
    def new_from_xdg_app(cls, xdg_app):
        """Create a service game from a XDG entry"""
        service_game = cls()
        service_game.name = xdg_app.get_display_name()
        service_game.icon = cls.get_app_icon(xdg_app)
        service_game.appid = get_appid(xdg_app)
        service_game.slug = cls.get_slug(xdg_app)
        exe, args = cls.get_command_args(xdg_app)
        path = cls.get_desktop_entry_path(xdg_app)
        service_game.details = json.dumps(
            {
                "exe": exe,
                "args": args,
                "path": path,
            }
        )
        return service_game

    @staticmethod
    def get_desktop_entry_path(xdg_app):
        """Retrieve the Path variable from the .desktop file"""

        # I expect we'll only see DesktopAppInfos here, but just in case
        # we get something else, we'll make sure.
        if hasattr(xdg_app, "get_string"):
            return xdg_app.get_string("Path")
        return None

    @staticmethod
    def get_command_args(app):
        """Return a tuple with absolute command path and an argument string"""
        command = shlex.split(app.get_commandline())
        # remove %U etc. and change %% to % in arguments
        args = list(map(lambda arg: re.sub("%[^%]", "", arg).replace("%%", "%"), command[1:]))
        exe = command[0]
        if not exe.startswith("/"):
            exe = system.find_required_executable(exe)
        return exe, subprocess.list2cmdline(args)

    @staticmethod
    def get_slug(xdg_app):
        """Get the slug from the game name"""
        return slugify(xdg_app.get_display_name()) or slugify(get_appid(xdg_app))
