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

from __future__ import annotations

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()


def shell(cmd: str, capture_output: bool = False, **kwargs: Any) -> str | None:
    """Run a shell command."""
    if capture_output:
        return subprocess.check_output(cmd, shell=True, text=True, **kwargs)  # noqa: S602
    subprocess.run(cmd, shell=True, check=True, stderr=subprocess.STDOUT, **kwargs)  # noqa: S602
    return None


@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(venv: Path) -> None:
    """Install dependencies using uv."""
    with environ(UV_PROJECT_ENVIRONMENT=str(venv)):
        shell("uv sync")
 

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(default_venv)

    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(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
                uv_install(venv_path)


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":
        with environ(UV_PROJECT_ENVIRONMENT=".venv"):
            subprocess.run(["uv", "run", cmd, *args], **kwargs)  # noqa: S603, PLW1510
    else:
        with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
            subprocess.run(["uv", "run", cmd, *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 dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs:
            shutil.rmtree(dirpath, 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
            if os.path.exists(".venv"):
                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 subprocess.CalledProcessError as process:
        if process.output:
            print(process.output, file=sys.stderr)  # noqa: T201
        sys.exit(process.returncode)
