import os
import shlex
import shutil
import tempfile
from datetime import date
from pathlib import Path

import vdf

from faugus.path_manager import PathManager, APP_DIR, DESKTOP_DIR, ICONS_DIR, SHORTCUT_ICONS_DIR, COVERS_DIR, BANNERS_DIR, GAMES_JSON, CONFIG_FILE_DIR, FILECHOOSER_FOLDERS_FILE, FAUGUS_LAUNCHER_DIR, FAUGUS_LAUNCHER_SHARE_DIR, FAUGUS_LAUNCHER_STATE_DIR

_LEGACY_ICON_BASES = (
    PathManager.user_config('faugus-launcher/icons'),
    PathManager.user_config('faugus-launcher/icons-nolauncher'),
)
_NEW_ICON_BASES = (ICONS_DIR, SHORTCUT_ICONS_DIR)


def _rewrite_icon_path(value):
    if not value:
        return None
    for old_base, new_base in zip(_LEGACY_ICON_BASES, _NEW_ICON_BASES):
        if value == old_base:
            return new_base
        if value.startswith(old_base + os.sep):
            return new_base + value[len(old_base):]
    return None


def _rewrite_desktop_lines(lines):
    changed = False
    new_lines = list(lines)
    for i, line in enumerate(new_lines):
        if line.startswith('Icon='):
            new_value = _rewrite_icon_path(line[len('Icon='):].rstrip('\n'))
            if new_value is not None:
                new_lines[i] = f'Icon={new_value}\n'
                changed = True
    return changed, new_lines


def _fix_desktop_file(path):
    try:
        with open(path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    except OSError:
        return

    changed, new_lines = _rewrite_desktop_lines(lines)

    if changed:
        try:
            with open(path, 'w', encoding='utf-8') as f:
                f.writelines(new_lines)
        except OSError:
            pass


def _fix_desktop_shortcuts():
    for directory in (APP_DIR, DESKTOP_DIR):
        d = Path(directory)
        if not d.is_dir():
            continue
        for entry in d.glob('*.desktop'):
            _fix_desktop_file(entry)


def _fix_steam_shortcuts():
    from faugus.steam_setup import get_all_shortcut_paths

    for path in get_all_shortcut_paths():
        if not os.path.exists(path):
            continue
        try:
            with open(path, 'rb') as f:
                shortcuts = vdf.binary_load(f)
        except (SyntaxError, OSError):
            continue

        if "shortcuts" not in shortcuts:
            continue

        changed = False
        for game_info in shortcuts["shortcuts"].values():
            if not isinstance(game_info, dict):
                continue
            new_value = _rewrite_icon_path(game_info.get("icon"))
            if new_value is not None:
                game_info["icon"] = new_value
                changed = True

        if changed:
            try:
                with open(path, 'wb') as f:
                    vdf.binary_dump(shortcuts, f)
            except OSError:
                pass


def _migrate_artwork_directory(old_dir, new_dir):
    old_path = Path(old_dir)
    new_path = Path(new_dir)
    if not old_path.is_dir() or old_path == new_path:
        return False
    if not new_path.exists():
        shutil.move(str(old_path), str(new_path))
        return True
    moved = False
    for entry in old_path.iterdir():
        target = new_path / entry.name
        if not target.exists():
            shutil.move(str(entry), str(target))
            moved = True
    try:
        old_path.rmdir()
    except OSError:
        pass
    return moved


def _migrate_artwork_directories():
    legacy_banners = PathManager.user_config('faugus-launcher/banners')
    legacy_heroes_config = PathManager.user_config('faugus-launcher/heroes')
    legacy_heroes_data = PathManager.user_data('faugus-launcher/heroes')
    migrated = False
    migrated |= _migrate_artwork_directory(legacy_banners, COVERS_DIR)
    migrated |= _migrate_artwork_directory(legacy_heroes_config, BANNERS_DIR)
    migrated |= _migrate_artwork_directory(legacy_heroes_data, BANNERS_DIR)
    return migrated


def _migrate_games_json_fields():
    from faugus.utils import load_json_file, save_json_file

    games = load_json_file(GAMES_JSON, None)
    if games is None:
        return

    field_renames = {
        "banner": "cover",
        "disable_hidraw": "sdl_enabled",
        "prevent_sleep": "no_sleep",
        "pre_launch_command": "pre_launch",
        "post_launch_command": "post_launch",
        "addapp_checkbox": "addapp_enabled",
    }

    changed = False
    for game in games:
        if not isinstance(game, dict):
            continue
        for old_field, new_field in field_renames.items():
            if old_field in game:
                game[new_field] = game.pop(old_field)
                changed = True

    if changed:
        save_json_file(games, GAMES_JSON)


def _migrate_config_json_values():
    from faugus.utils import load_json_file, save_json_file

    config = load_json_file(CONFIG_FILE_DIR, None)
    if config is None:
        return

    changed = False

    mode_renames = {"Blocks": "Grid", "Banners": "Covers"}
    mode = config.get('interface-mode')
    if mode in mode_renames:
        config['interface-mode'] = mode_renames[mode]
        changed = True

    key_renames = {
        "hero-enabled": "banner-enabled",
        "banner-size": "cover-size",
        "disable-hidraw": "sdl-enabled",
        "prevent-sleep": "no-sleep-enabled",
        "close-onlaunch": "auto-close-on-launch",
        "show-labels": "labels-enabled",
        "enable-logging": "logging-enabled",
        "start-boot": "autostart-enabled",
        "start-minimized": "minimized-startup-enabled",
        "show-categories": "categories-and-sort-enabled",
        "enable-wow64": "wow64-enabled",
        "window-behavior": "startup-window-size",
    }
    for old_key, new_key in key_renames.items():
        if old_key in config:
            config[new_key] = config.pop(old_key)
            changed = True

    inverted_key_renames = {"splash-disable": "splash-window-enabled", "disable-updates": "automatic-updates"}
    for old_key, new_key in inverted_key_renames.items():
        if old_key in config:
            old_value = config.pop(old_key)
            config[new_key] = 'False' if old_value == 'True' else 'True'
            changed = True

    if changed:
        save_json_file(config, CONFIG_FILE_DIR)


def _migrate_filechooser_folder_keys():
    from faugus.utils import load_json_file, save_json_file

    folders = load_json_file(FILECHOOSER_FOLDERS_FILE, None)
    if folders is None:
        return

    key_renames = {"pre_launch_command": "pre_launch", "post_launch_command": "post_launch"}

    changed = False
    for old_key, new_key in key_renames.items():
        if old_key in folders:
            folders[new_key] = folders.pop(old_key)
            changed = True

    if changed:
        save_json_file(folders, FILECHOOSER_FOLDERS_FILE)


def _collect_desktop_backups():
    entries = []
    for directory, subdir in ((DESKTOP_DIR, "desktop-shortcuts"), (APP_DIR, "appmenu-shortcuts")):
        d = Path(directory)
        if not d.is_dir():
            continue
        for entry in d.glob('*.desktop'):
            entries.append((entry, subdir, entry.name))
    return entries


def _collect_steam_shortcut_backups():
    from faugus.steam_setup import get_all_shortcut_paths

    entries = []
    for path in get_all_shortcut_paths():
        path = Path(path)
        if path.exists():
            account_id = path.parent.parent.name
            entries.append((path, "steam-shortcuts", f"{account_id}-shortcuts.vdf"))
    return entries


def _restore_action_dir(original, backup_rel):
    quoted_dir = shlex.quote(original)
    quoted_src = shlex.quote('/' + backup_rel)
    return (
        f'DIR={quoted_dir}\n'
        f'if [ -L "$DIR" ]; then DIR=$(readlink -f "$DIR"); fi\n'
        f'if [ -d "$DIR" ]; then find "$DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {{}} +; else mkdir -p "$DIR"; fi\n'
        f'cp -a "$SCRIPT_DIR"{quoted_src}/. "$DIR/"\n'
        f'printf \'Restored %s\\n\' {quoted_dir}'
    )


def _restore_action_file(original, backup_rel):
    quoted_dst = shlex.quote(original)
    quoted_dst_dir = shlex.quote(os.path.dirname(original))
    quoted_src = shlex.quote('/' + backup_rel)
    return (
        f'mkdir -p {quoted_dst_dir}\n'
        f'cp -a "$SCRIPT_DIR"{quoted_src} {quoted_dst}\n'
        f'printf \'Restored %s\\n\' {quoted_dst}'
    )


def _clear_action(original):
    quoted_dir = shlex.quote(original)
    return (
        f'DIR={quoted_dir}\n'
        f'if [ -L "$DIR" ]; then DIR=$(readlink -f "$DIR"); fi\n'
        f'if [ -d "$DIR" ]; then find "$DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {{}} +; else mkdir -p "$DIR"; fi\n'
        f'printf \'Cleared %s\\n\' {quoted_dir}'
    )


def _build_restore_script(dir_snapshots, file_snapshots, clear_only_dirs):
    preview = []
    actions = []

    for entry in dir_snapshots:
        preview.append(f'printf \'  %s\\n\' {shlex.quote(entry["original"] + "/")}')
        actions.append(_restore_action_dir(entry["original"], entry["backup"]))

    for entry in file_snapshots:
        preview.append(f'printf \'  %s\\n\' {shlex.quote(entry["original"])}')
        actions.append(_restore_action_file(entry["original"], entry["backup"]))

    for original in clear_only_dirs:
        preview.append(f'printf \'  %s (cleared)\\n\' {shlex.quote(original + "/")}')
        actions.append(_clear_action(original))

    preview_block = "\n".join(preview)
    actions_block = "\n\n".join(actions)

    return f'''#!/bin/sh
set -e

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)

echo "This restores Faugus Launcher's data to exactly how it was"
echo "before the migration ran, replacing the current contents of:"
{preview_block}

printf 'Proceed? [y/N] '
read ANSWER
case "$ANSWER" in
    y|Y) ;;
    *) echo "Cancelled."; exit 0 ;;
esac

{actions_block}

echo "Done. Restart Faugus Launcher for the changes to take effect."
'''


def _prompt_backup_destination():
    import gi
    gi.require_version('Gtk', '4.0')
    from gi.repository import Gtk, GLib

    from faugus.utils import (
        on_entry_changed, load_red_entry_css, load_frame_css, hide_dialog_action_area,
        new_file_chooser, set_file_chooser_start_folder, destroy_and_release,
        build_bottom_button_box, expand_path,
    )

    result = {"path": None}
    loop = GLib.MainLoop()

    dialog = Gtk.Dialog(title="Faugus")
    hide_dialog_action_area(dialog)
    dialog.set_modal(True)
    dialog.set_resizable(False)

    load_red_entry_css()
    load_frame_css()

    root_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
    dialog.get_content_area().append(root_box)

    frame = Gtk.Frame()
    frame.set_margin_start(10)
    frame.set_margin_end(10)
    frame.set_margin_top(10)
    frame.set_margin_bottom(10)

    main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
    main_box.set_margin_top(10)
    main_box.set_margin_bottom(10)
    main_box.set_margin_start(10)
    main_box.set_margin_end(10)
    frame.set_child(main_box)
    root_box.append(frame)

    label_title = Gtk.Label()
    label_title.set_markup("<big><b>Welcome to Faugus 2.0</b></big>")
    label_title.set_halign(Gtk.Align.START)
    main_box.append(label_title)

    label_body = Gtk.Label(
        label=(
            "A backup of your data is required to ensure\n"
            "no data loss during the migration process.\n\n"
            "Choose a location for the backup to be performed."
        )
    )
    label_body.set_halign(Gtk.Align.CENTER)
    label_body.set_wrap(True)
    main_box.append(label_body)

    default_dir = PathManager.user_home('Faugus Backup')

    box_dest = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
    entry_dest = Gtk.Entry()
    entry_dest.connect("changed", on_entry_changed)
    entry_dest.set_text(default_dir)
    entry_dest.set_hexpand(True)
    button_browse = Gtk.Button()
    button_browse.set_child(Gtk.Image.new_from_icon_name("system-search-symbolic"))
    button_browse.set_size_request(50, -1)
    box_dest.append(entry_dest)
    box_dest.append(button_browse)
    main_box.append(box_dest)

    def on_browse_clicked(widget):
        filechooser = new_file_chooser(
            dialog, "Select the backup destination", Gtk.FileChooserAction.SELECT_FOLDER,
            accept_label="Open", cancel_label="Cancel",
        )
        set_file_chooser_start_folder(filechooser, "backup_destination", expand_path(entry_dest.get_text()) or None)

        def on_response(fc, response):
            if response == Gtk.ResponseType.ACCEPT:
                entry_dest.set_text(fc.get_file().get_path())
            destroy_and_release(fc)

        filechooser.connect("response", on_response)
        filechooser.present()

    button_browse.connect("clicked", on_browse_clicked)

    def finish(path):
        result["path"] = path
        destroy_and_release(dialog)
        loop.quit()

    def on_ok_clicked(widget):
        dest = expand_path(entry_dest.get_text().strip())
        if not dest:
            entry_dest.add_css_class("entry")
            return
        try:
            os.makedirs(dest, exist_ok=True)
        except OSError:
            entry_dest.add_css_class("entry")
            return
        finish(dest)

    def on_cancel_clicked(widget):
        finish(None)

    def on_close_request(widget):
        finish(None)
        return True

    button_cancel = Gtk.Button(label="Cancel")
    button_cancel.set_hexpand(True)
    button_cancel.connect("clicked", on_cancel_clicked)

    button_ok = Gtk.Button(label="Ok")
    button_ok.set_hexpand(True)
    button_ok.connect("clicked", on_ok_clicked)

    root_box.append(build_bottom_button_box(button_cancel, button_ok))
    dialog.connect("close-request", on_close_request)

    dialog.present()
    loop.run()

    return result["path"]


def _has_existing_faugus_config():
    config_dir = os.path.realpath(FAUGUS_LAUNCHER_DIR) if os.path.islink(FAUGUS_LAUNCHER_DIR) else FAUGUS_LAUNCHER_DIR
    return os.path.isfile(os.path.join(config_dir, 'config.ini'))


def _backup_before_migration():
    marker = Path(FAUGUS_LAUNCHER_STATE_DIR) / "migration-backup-done"
    if marker.exists():
        return

    if not _has_existing_faugus_config():
        return

    chosen_dir = _prompt_backup_destination()
    if not chosen_dir:
        import sys
        sys.exit(0)

    backup_root = Path(chosen_dir)
    zip_path = backup_root / f"faugus-migration-backup-{date.today().isoformat()}.zip"

    staging_dir = Path(tempfile.mkdtemp(prefix="faugus-migration-backup-"))

    dir_snapshots = []
    clear_only_dirs = [str(FAUGUS_LAUNCHER_STATE_DIR)]
    for label, src in (
        ("config", FAUGUS_LAUNCHER_DIR),
        ("data", FAUGUS_LAUNCHER_SHARE_DIR),
    ):
        real_src = os.path.realpath(src) if os.path.islink(src) else src
        if os.path.isdir(real_src):
            shutil.copytree(real_src, staging_dir / label, symlinks=True)
            dir_snapshots.append({"backup": label, "original": str(src)})
        else:
            clear_only_dirs.append(str(src))

    file_snapshots = []
    for src_path, subdir, basename in _collect_desktop_backups() + _collect_steam_shortcut_backups():
        target_dir = staging_dir / subdir
        target_dir.mkdir(parents=True, exist_ok=True)
        backup_path = target_dir / basename
        shutil.copy2(src_path, backup_path)
        file_snapshots.append({"backup": str(backup_path.relative_to(staging_dir)), "original": str(src_path)})

    script_text = _build_restore_script(dir_snapshots, file_snapshots, clear_only_dirs)
    restore_script = staging_dir / "restore.sh"
    restore_script.write_text(script_text, encoding="utf-8")
    restore_script.chmod(0o755)

    backup_root.mkdir(parents=True, exist_ok=True)
    shutil.make_archive(str(zip_path.with_suffix("")), "zip", root_dir=str(staging_dir))
    shutil.rmtree(staging_dir)

    marker.parent.mkdir(parents=True, exist_ok=True)
    marker.touch()


def fix_legacy_shortcut_icons():
    artwork_migrated = False

    try:
        _fix_desktop_shortcuts()
    except Exception:
        pass
    try:
        _fix_steam_shortcuts()
    except Exception:
        pass
    try:
        from faugus.utils import update_games_json
        update_games_json()
    except Exception:
        pass
    try:
        artwork_migrated = _migrate_artwork_directories()
    except Exception:
        pass
    try:
        _migrate_games_json_fields()
    except Exception:
        pass
    try:
        _migrate_config_json_values()
    except Exception:
        pass
    try:
        _migrate_filechooser_folder_keys()
    except Exception:
        pass

    return artwork_migrated
