"""Shared machinery for the packaging checks.

The three `slow` tests need a real artifact: a wheel built from this project, installed into
a throwaway venv with nothing else in it. Building that twice would be silly, so both live in
**session-scoped fixtures** — the scope from the pytest page, used the way it is meant to be
used: expensive, read-only setup shared by every test that asks for it.

Two rules make the result mean something:

* the wheel is built from a *copy* of the project in a temp dir, so `build/` and `.egg-info/`
  never appear in your working tree;
* every subprocess runs with `PYTHONPATH` stripped and a cwd outside the project, so an
  "installed" import cannot silently be satisfied by `src/` sitting on the path.
"""

from __future__ import annotations

import importlib.util
import os
import shutil
import subprocess
import sys
import venv
from pathlib import Path

import pytest

PROJECT = Path(__file__).resolve().parent.parent

# Anything that is a build artifact, a cache or an environment: never copied, never shipped.
JUNK = shutil.ignore_patterns(
    ".venv", "venv", "__pycache__", "build", "dist", "*.egg-info",
    ".pytest_cache", ".mypy_cache", ".ruff_cache", ".git",
)


def tool_cmd(name: str) -> list[str] | None:
    """How to invoke `name` here: `python -m name` if importable, else the binary, else None."""
    if importlib.util.find_spec(name) is not None:
        return [sys.executable, "-m", name]
    found = shutil.which(name)
    return [found] if found else None


def run(cmd: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
    """Run a command with `src/` removed from the import path (see the module docstring)."""
    env = dict(os.environ)
    env.pop("PYTHONPATH", None)
    return subprocess.run(cmd, capture_output=True, text=True, env=env, cwd=cwd)


def build_wheel(project: Path, outdir: Path) -> Path:
    """Build `project`'s wheel — `python -m build --wheel`, which is what the README says.

    Falls back to driving the same build backend through `pip wheel --no-build-isolation`
    when `build` is not installed (a sandbox with no package index), so the packaging checks
    still run there rather than quietly skipping.
    """
    if importlib.util.find_spec("build") is not None:
        cmd = [sys.executable, "-m", "build", "--wheel", "--outdir", str(outdir), str(project)]
    elif importlib.util.find_spec("setuptools") is not None:
        cmd = [sys.executable, "-m", "pip", "wheel", str(project),
               "--no-deps", "--no-build-isolation", "--no-index", "--wheel-dir", str(outdir)]
    else:
        pytest.skip("neither `build` nor `setuptools` is importable — `uv sync` installs build")
    result = run(cmd)
    assert result.returncode == 0, f"the wheel did not build:\n{result.stdout}\n{result.stderr}"
    wheels = sorted(outdir.glob("*.whl"))
    assert wheels, f"the build produced no .whl in {outdir}:\n{result.stdout}\n{result.stderr}"
    return wheels[0]


@pytest.fixture(scope="session")
def wheel(tmp_path_factory: pytest.TempPathFactory) -> Path:
    """The project's wheel, built once for the whole run."""
    work = tmp_path_factory.mktemp("wheel")
    project = work / "project"
    shutil.copytree(PROJECT, project, ignore=JUNK)
    return build_wheel(project, work / "dist")


@pytest.fixture(scope="session")
def installed_python(tmp_path_factory: pytest.TempPathFactory, wheel: Path) -> Path:
    """A throwaway venv with that wheel — and nothing else — installed. Built once."""
    env_dir = tmp_path_factory.mktemp("env") / "venv"
    venv.EnvBuilder(with_pip=True, clear=True).create(env_dir)
    bin_dir = env_dir / ("Scripts" if os.name == "nt" else "bin")
    python = bin_dir / ("python.exe" if os.name == "nt" else "python")
    result = run([str(python), "-m", "pip", "install", "--no-index", "--no-deps", str(wheel)])
    assert result.returncode == 0, f"pip could not install it:\n{result.stdout}\n{result.stderr}"
    return python
