# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

from __future__ import annotations

import contextlib
import os
import subprocess
import sys
from pathlib import Path
from typing import Any

from anki.utils import pointVersion
from aqt import mw
from aqt.qt import QAction
from aqt.utils import askUser, is_mac, is_win, showInfo


def launcher_executable() -> str | None:
    """Return the path to the Anki launcher executable."""
    return os.getenv("ANKI_LAUNCHER")


def uv_binary() -> str | None:
    """Return the path to the uv binary."""
    return os.environ.get("ANKI_LAUNCHER_UV")


def launcher_root() -> str | None:
    """Return the path to the launcher root directory (AnkiProgramFiles)."""
    return os.environ.get("UV_PROJECT")


def venv_binary(cmd: str) -> str | None:
    """Return the path to a binary in the launcher's venv."""
    root = launcher_root()
    if not root:
        return None

    root_path = Path(root)
    if is_win:
        binary_path = root_path / ".venv" / "Scripts" / cmd
    else:
        binary_path = root_path / ".venv" / "bin" / cmd

    return str(binary_path)


def add_python_requirements(reqs: list[str]) -> tuple[bool, str]:
    """Add Python requirements to the launcher venv using uv add.

    Returns (success, output)"""

    binary = uv_binary()
    if not binary:
        return (False, "Not in packaged build.")

    uv_cmd = [binary, "add"] + reqs
    result = subprocess.run(uv_cmd, capture_output=True, text=True, check=False)

    if result.returncode == 0:
        root = launcher_root()
        if root:
            sync_marker = Path(root) / ".sync_complete"
            sync_marker.touch()
        return (True, result.stdout)
    else:
        return (False, result.stderr)


def trigger_launcher_run() -> None:
    """Create a trigger file to request launcher UI on next run."""
    try:
        root = launcher_root()
        if not root:
            return

        trigger_path = Path(root) / ".want-launcher"
        trigger_path.touch()
    except Exception as e:
        print(e)


def update_and_restart() -> None:
    """Update and restart Anki using the launcher."""
    launcher = launcher_executable()
    assert launcher

    trigger_launcher_run()

    with contextlib.suppress(ResourceWarning):
        env = os.environ.copy()
        env["ANKI_LAUNCHER_WANT_TERMINAL"] = "1"
        creationflags = 0
        if sys.platform == "win32":
            creationflags = (
                subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
            )
        # On Windows, changing the handles breaks ANSI display
        io = None if sys.platform == "win32" else subprocess.DEVNULL

        subprocess.Popen(
            [launcher],
            start_new_session=True,
            stdin=io,
            stdout=io,
            stderr=io,
            env=env,
            creationflags=creationflags,
        )

    mw.app.quit()


def confirm_then_upgrade():
    if not askUser("Change to a different Anki version?"):
        return
    update_and_restart()


# return modified command array that points to bundled command, and return
# required environment
def _packagedCmd(cmd: list[str]) -> tuple[Any, dict[str, str]]:
    cmd = cmd[:]
    env = os.environ.copy()
    # keep LD_LIBRARY_PATH when in snap environment
    if "LD_LIBRARY_PATH" in env and "SNAP" not in env:
        del env["LD_LIBRARY_PATH"]

    # Try to find binary in anki-audio package for Windows/Mac
    if is_win or is_mac:
        try:
            import anki_audio

            audio_pkg_path = Path(anki_audio.__file__).parent
            if is_win:
                packaged_path = audio_pkg_path / (cmd[0] + ".exe")
            else:  # is_mac
                packaged_path = audio_pkg_path / cmd[0]

            if packaged_path.exists():
                cmd[0] = str(packaged_path)
                return cmd, env
        except ImportError:
            # anki-audio not available, fall back to old behavior
            pass

    packaged_path = Path(sys.prefix) / cmd[0]
    if packaged_path.exists():
        cmd[0] = str(packaged_path)

    return cmd, env


def on_addon_config():
    showInfo(
        "This add-on is automatically added when installing older Anki versions, so that they work with the launcher. You can remove it if you wish."
    )


def setup():
    mw.addonManager.setConfigAction(__name__, on_addon_config)

    if pointVersion() >= 250600:
        return
    if not launcher_executable():
        return

    # Add action to tools menu
    action = QAction("Upgrade/Downgrade", mw)
    action.triggered.connect(confirm_then_upgrade)
    mw.form.menuTools.addAction(action)

    # Monkey-patch audio tools to use anki-audio
    if is_win or is_mac:
        import aqt
        import aqt.sound

        aqt.sound._packagedCmd = _packagedCmd

    # Inject launcher functions into launcher module
    import aqt.package

    aqt.package.launcher_executable = launcher_executable
    aqt.package.update_and_restart = update_and_restart
    aqt.package.trigger_launcher_run = trigger_launcher_run
    aqt.package.uv_binary = uv_binary
    aqt.package.launcher_root = launcher_root
    aqt.package.venv_binary = venv_binary
    aqt.package.add_python_requirements = add_python_requirements


setup()
