#!/usr/bin/env python3
"""Management commands."""

import os
import shutil
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Iterator

PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split()

exe = ""
prefix = ""


def shell(cmd: str) -> None:
    """Run a shell command."""
    subprocess.run(cmd, shell=True, check=True)  # noqa: S602


@contextmanager
def environ(**kwargs: str) -> Iterator[None]:
    """Temporarily set environment variables."""
    original = dict(os.environ)
    os.environ.update(kwargs)
    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(original)


def uv_install() -> None:
    """Install dependencies using uv."""
    uv_opts = ""
    if "UV_RESOLUTION" in os.environ:
        uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}"
    cmd = f"uv pip compile {uv_opts} pyproject.toml devdeps.txt | uv pip install -r -"
    shell(cmd)
    if "CI" not in os.environ:
        shell("uv pip install --no-deps -e .")
    else:
        shell("uv pip install --no-deps .")


def setup() -> None:
    """Setup the project."""
    if not shutil.which("uv"):
        raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv")

    print("Installing dependencies (default environment)")  # noqa: T201
    default_venv = Path(".venv")
    if not default_venv.exists():
        shell("uv venv --python python")
    uv_install()

    if PYTHON_VERSIONS:
        for version in PYTHON_VERSIONS:
            print(f"\nInstalling dependencies (python{version})")  # noqa: T201
            venv_path = Path(f".venvs/{version}")
            if not venv_path.exists():
                shell(f"uv venv --python {version} {venv_path}")
            with environ(VIRTUAL_ENV=str(venv_path.resolve())):
                uv_install()


def activate(path: str) -> None:
    """Activate a virtual environment."""
    global exe, prefix  # noqa: PLW0603

    if (bin := Path(path, "bin")).exists():
        activate_script = bin / "activate_this.py"
    elif (scripts := Path(path, "Scripts")).exists():
        activate_script = scripts / "activate_this.py"
        exe = ".exe"
        prefix = f"{path}/Scripts/"
    else:
        raise ValueError(f"make: activate: Cannot find activation script in {path}")

    if not activate_script.exists():
        raise ValueError(f"make: activate: Cannot find activation script in {path}")

    exec(activate_script.read_text(), {"__file__": str(activate_script)})  # noqa: S102


def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None:
    """Run a command in a virtual environment."""
    kwargs = {"check": True, **kwargs}
    if version == "default":
        activate(".venv")
        subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs)  # noqa: S603, PLW1510
    else:
        activate(f".venvs/{version}")
        os.environ["MULTIRUN"] = "1"
        subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs)  # noqa: S603, PLW1510


def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
    """Run a command for all configured Python versions."""
    if PYTHON_VERSIONS:
        for version in PYTHON_VERSIONS:
            run(version, cmd, *args, **kwargs)
    else:
        run("default", cmd, *args, **kwargs)


def allrun(cmd: str, *args: str, **kwargs: Any) -> None:
    """Run a command in all virtual environments."""
    run("default", cmd, *args, **kwargs)
    if PYTHON_VERSIONS:
        multirun(cmd, *args, **kwargs)


def clean() -> None:
    """Delete build artifacts and cache files."""
    paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"]
    for path in paths_to_clean:
        shell(f"rm -rf {path}")

    cache_dirs = [".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"]
    for dirpath in Path(".").rglob("*"):
        if any(dirpath.match(pattern) for pattern in cache_dirs) and not (dirpath.match(".venv") or dirpath.match(".venvs")):
            shutil.rmtree(path, ignore_errors=True)


def vscode() -> None:
    """Configure VSCode to work on this project."""
    Path(".vscode").mkdir(parents=True, exist_ok=True)
    shell("cp -v config/vscode/* .vscode")


def main() -> int:
    """Main entry point."""
    args = list(sys.argv[1:])
    if not args or args[0] == "help":
        if len(args) > 1:
            run("default", "duty", "--help", args[1])
        else:
            print("Available commands")  # noqa: T201
            print("  help                  Print this help. Add task name to print help.")  # noqa: T201
            print("  setup                 Setup all virtual environments (install dependencies).")  # noqa: T201
            print("  run                   Run a command in the default virtual environment.")  # noqa: T201
            print("  multirun              Run a command for all configured Python versions.")  # noqa: T201
            print("  allrun                Run a command in all virtual environments.")  # noqa: T201
            print("  3.x                   Run a command in the virtual environment for Python 3.x.")  # noqa: T201
            print("  clean                 Delete build artifacts and cache files.")  # noqa: T201
            print("  vscode                Configure VSCode to work on this project.")  # noqa: T201
            try:
                run("default", "python", "-V", capture_output=True)
            except (subprocess.CalledProcessError, ValueError):
                pass
            else:
                print("\nAvailable tasks")  # noqa: T201
                run("default", "duty", "--list")
        return 0

    while args:
        cmd = args.pop(0)

        if cmd == "run":
            run("default", *args)
            return 0

        if cmd == "multirun":
            multirun(*args)
            return 0

        if cmd == "allrun":
            allrun(*args)
            return 0

        if cmd.startswith("3."):
            run(cmd, *args)
            return 0

        opts = []
        while args and (args[0].startswith("-") or "=" in args[0]):
            opts.append(args.pop(0))

        if cmd == "clean":
            clean()
        elif cmd == "setup":
            setup()
        elif cmd == "vscode":
            vscode()
        elif cmd == "check":
            multirun("duty", "check-quality", "check-types", "check-docs")
            run("default", "duty", "check-api")
        elif cmd in {"check-quality", "check-docs", "check-types", "test"}:
            multirun("duty", cmd, *opts)
        else:
            run("default", "duty", cmd, *opts)

    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception:  # noqa: BLE001
        sys.exit(1)
