# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

from __future__ import annotations

import base64
import email
import hashlib
import json
import os
import shutil
import subprocess
import tomllib
import venv
import zipfile
from dataclasses import dataclass
from pathlib import Path
from typing import Iterator

import pytest
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name

import scripts.check_wheel_contents as wheel_checker

ROOT = Path(__file__).resolve().parents[2]
pytestmark = [pytest.mark.integration, pytest.mark.timeout(900)]


@dataclass(frozen=True)
class BuiltWheels:
    version: str
    dist: Path
    root: Path
    core: Path


def _run(
    argv: list[str | Path],
    *,
    cwd: Path,
    env: dict[str, str] | None = None,
    timeout: int = 300,
) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [str(token) for token in argv],
        cwd=cwd,
        env=env,
        capture_output=True,
        text=True,
        check=False,
        timeout=timeout,
    )


def _root_version() -> str:
    data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
    return data["project"]["version"]


def _lock_versions() -> dict[str, str]:
    data = tomllib.loads((ROOT / "uv.lock").read_text(encoding="utf-8"))
    return {
        canonicalize_name(package["name"]): package["version"]
        for package in data["package"]
    }


def _record_hash(content: bytes) -> str:
    digest = hashlib.sha256(content).digest()
    encoded = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
    return f"sha256={encoded}"


def _write_wheel(
    wheel_dir: Path,
    distribution: str,
    version: str,
    *,
    scripts: dict[str, bytes] | None = None,
    entry_points: str | None = None,
) -> Path:
    wheel_dir.mkdir(parents=True, exist_ok=True)
    normalized = distribution.replace("-", "_")
    dist_info = f"{normalized}-{version}.dist-info"
    wheel_path = wheel_dir / f"{normalized}-{version}-py3-none-any.whl"
    members = {
        f"{dist_info}/METADATA": (
            f"Metadata-Version: 2.1\nName: {distribution}\nVersion: {version}\n"
        ).encode(),
        f"{dist_info}/WHEEL": (
            "Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n"
        ).encode(),
    }
    if entry_points is not None:
        members[f"{dist_info}/entry_points.txt"] = entry_points.encode("utf-8")
    for name, content in (scripts or {}).items():
        members[f"{normalized}-{version}.data/scripts/{name}"] = content
    rows = [
        f"{name},{_record_hash(content)},{len(content)}"
        for name, content in members.items()
    ]
    rows.append(f"{dist_info}/RECORD,,")
    with zipfile.ZipFile(wheel_path, "w") as wheel:
        for name, content in members.items():
            info = zipfile.ZipInfo(name)
            info.create_system = 3
            info.external_attr = (
                0o755 << 16 if ".data/scripts/" in name else 0o644 << 16
            )
            wheel.writestr(info, content)
        wheel.writestr(f"{dist_info}/RECORD", "\n".join(rows).encode("utf-8"))
    return wheel_path


def _runtime_requirements(root_wheel: Path) -> tuple[Requirement, ...]:
    with zipfile.ZipFile(root_wheel) as wheel:
        metadata_name = next(
            name for name in wheel.namelist() if name.endswith("METADATA")
        )
        message = email.message_from_bytes(wheel.read(metadata_name))
    requirements = []
    for line in message.get_all("Requires-Dist") or ():
        requirement = Requirement(line)
        name = canonicalize_name(requirement.name)
        if name == "solstone-core":
            continue
        if requirement.marker is not None and not requirement.marker.evaluate(
            {"extra": ""}
        ):
            continue
        requirements.append(requirement)
    return tuple(requirements)


def _write_dependency_stub_wheels(root_wheel: Path, wheel_dir: Path) -> None:
    versions = _lock_versions()
    for requirement in _runtime_requirements(root_wheel):
        name = canonicalize_name(requirement.name)
        version = versions[name]
        assert requirement.specifier.contains(version, prereleases=True)
        _write_wheel(wheel_dir, name, version)


@pytest.fixture(scope="module")
def built_wheels(tmp_path_factory: pytest.TempPathFactory) -> Iterator[BuiltWheels]:
    if shutil.which("uv") is None:
        pytest.skip("uv is not installed")
    if shutil.which("cargo") is None:
        pytest.skip("cargo is not installed")

    dist = tmp_path_factory.mktemp("root-launcher-wheels")
    env = os.environ.copy()
    env.pop("MATURIN_PEP517_ARGS", None)
    for argv in (
        [
            "uv",
            "build",
            "--offline",
            "--package",
            "solstone-core",
            "--wheel",
            "--out-dir",
            dist,
        ],
        ["uv", "build", "--offline", "--wheel", "--out-dir", dist],
    ):
        result = _run(argv, cwd=ROOT, env=env, timeout=900)
        assert result.returncode == 0, result.stderr or result.stdout

    version = _root_version()
    root_wheels = sorted(dist.glob(f"solstone-{version}-py3-none-any.whl"))
    core_wheels = sorted(dist.glob(f"solstone_core-{version}-*.whl"))
    assert len(root_wheels) == 1
    assert len(core_wheels) == 1
    _write_dependency_stub_wheels(root_wheels[0], dist)
    yield BuiltWheels(
        version=version, dist=dist, root=root_wheels[0], core=core_wheels[0]
    )


def _isolated_uv_env(tmp_path: Path) -> tuple[dict[str, str], Path, Path, Path]:
    home = tmp_path / "home"
    tool_dir = tmp_path / "uv-tools"
    tool_bin = tmp_path / "uv-bin"
    cache = tmp_path / "uv-cache"
    unrelated = tmp_path / "unrelated"
    for path in (home, tool_dir, tool_bin, cache, unrelated):
        path.mkdir(parents=True)
    env = os.environ.copy()
    env.update(
        {
            "HOME": str(home),
            "XDG_DATA_HOME": str(tmp_path / "xdg-data"),
            "UV_TOOL_DIR": str(tool_dir),
            "UV_TOOL_BIN_DIR": str(tool_bin),
            "UV_CACHE_DIR": str(cache),
            "UV_PYTHON_DOWNLOADS": "never",
        }
    )
    env.pop("PYTHONPATH", None)
    return env, tool_bin, tool_dir, unrelated


def _uv_tool_install(
    *,
    root_wheel: Path,
    core_wheel: Path,
    env: dict[str, str],
    cwd: Path,
    force: bool = False,
) -> subprocess.CompletedProcess[str]:
    argv: list[str | Path] = [
        "uv",
        "tool",
        "install",
        "--offline",
        "--no-index",
        "--find-links",
        root_wheel.parent,
        "--with",
        core_wheel,
    ]
    if force:
        argv.append("--force")
    argv.append(root_wheel)
    return _run(argv, cwd=cwd, env=env, timeout=900)


def _tool_bin_names(tool_bin: Path) -> set[str]:
    return {path.name for path in tool_bin.iterdir() if not path.name.startswith(".")}


def _tool_env_bin(tool_bin: Path, name: str = "sol") -> Path:
    return (tool_bin / name).resolve().parent


def _install_python_spy(env_bin: Path, log: Path) -> None:
    for name in ("python3", "python"):
        interpreter = env_bin / name
        real = env_bin / f"{name}.real"
        interpreter.rename(real)
        wrapper = (
            "#!/bin/sh\n"
            f"printf '%s %s\\n' {name!r} \"$*\" >> {str(log)!r}\n"
            f'exec {str(real)!r} "$@"\n'
        )
        interpreter.write_text(wrapper, encoding="utf-8")
        interpreter.chmod(0o755)


def _script_owners(python: Path, names: tuple[str, ...]) -> dict[str, list[str]]:
    script = """
import csv
import importlib.metadata as metadata
import json
import os
from pathlib import Path, PurePosixPath

bin_dir = Path(os.environ["BIN_DIR"])
targets = set(json.loads(os.environ["SCRIPT_NAMES"]))
scripts = {str((bin_dir / name).resolve()): name for name in targets}
owners = {name: [] for name in targets}
for dist in metadata.distributions():
    dist_name = dist.metadata.get("Name", "")
    for file in dist.files or []:
        basename = PurePosixPath(str(file).replace("\\\\", "/")).name
        if basename.endswith(".exe"):
            basename = basename[:-4]
        if basename not in targets:
            continue
        try:
            located = str(dist.locate_file(file).resolve())
        except OSError:
            continue
        if located in scripts:
            owners[scripts[located]].append(dist_name)
print(json.dumps({key: sorted(value) for key, value in owners.items()}, sort_keys=True))
"""
    result = _run(
        [python, "-c", script],
        cwd=python.parent,
        env={
            **os.environ,
            "BIN_DIR": str(python.parent),
            "SCRIPT_NAMES": json.dumps(list(names)),
        },
    )
    assert result.returncode == 0, result.stderr or result.stdout
    return json.loads(result.stdout)


def _assert_new_owners(python: Path) -> None:
    expected = {
        **{name: ["solstone"] for name in wheel_checker.ROOT_LAUNCHER_NAMES},
        **{name: ["solstone-core"] for name in wheel_checker.CORE_SCRIPT_NAMES},
    }
    assert (
        _script_owners(
            python, wheel_checker.ROOT_LAUNCHER_NAMES + wheel_checker.CORE_SCRIPT_NAMES
        )
        == expected
    )


def _create_venv(path: Path) -> Path:
    venv.EnvBuilder(with_pip=True, symlinks=False).create(path)
    return path / "bin" / "python"


def _pip_install(
    python: Path,
    wheels: tuple[Path, ...],
    *,
    force: bool = False,
) -> subprocess.CompletedProcess[str]:
    argv: list[str | Path] = [
        python,
        "-m",
        "pip",
        "install",
        "--no-index",
        "--no-deps",
    ]
    if force:
        argv.append("--force-reinstall")
    argv.extend(wheels)
    return _run(argv, cwd=python.parent)


def _old_layout_wheels(tmp_path: Path) -> tuple[Path, Path]:
    old_version = "1.0.14"
    wheel_dir = tmp_path / "old-wheels"
    core_scripts = {
        "sol": b"#!/bin/sh\necho old sol\n",
        "solstone": b"#!/bin/sh\necho old solstone\n",
        "solstone-core": b"#!/bin/sh\necho old solstone-core\n",
    }
    old_core = _write_wheel(
        wheel_dir,
        "solstone-core",
        old_version,
        scripts=core_scripts,
    )
    old_root = _write_wheel(
        wheel_dir,
        "solstone",
        old_version,
        entry_points=(
            "[console_scripts]\n"
            "solstone-python-compat = solstone.think.sol_compat_cli:main\n"
        ),
    )
    return old_root, old_core


def test_cold_uv_tool_install_exports_root_launchers_only(
    tmp_path: Path,
    built_wheels: BuiltWheels,
) -> None:
    env, tool_bin, _tool_dir, unrelated = _isolated_uv_env(tmp_path)
    assert _tool_bin_names(tool_bin) == set()

    install = _uv_tool_install(
        root_wheel=built_wheels.root,
        core_wheel=built_wheels.core,
        env=env,
        cwd=unrelated,
    )

    assert install.returncode == 0, install.stderr or install.stdout
    assert _tool_bin_names(tool_bin) == set(wheel_checker.ROOT_LAUNCHER_NAMES)
    assert not (tool_bin / "solstone-core").exists()
    assert not (tool_bin / "solstone-python-compat").exists()

    env_bin = _tool_env_bin(tool_bin)
    assert (env_bin / "solstone-core").exists()
    assert (tool_bin / "sol").resolve().parent == env_bin
    assert (tool_bin / "solstone").resolve().parent == env_bin

    log = tmp_path / "python-spy.log"
    _install_python_spy(env_bin, log)
    native = _run([tool_bin / "sol", "--version"], cwd=unrelated, env=env)
    assert native.returncode == 0, native.stderr
    assert native.stdout == f"sol (solstone) {built_wheels.version}\n"
    native_solstone = _run(
        [tool_bin / "solstone", "--version"],
        cwd=unrelated,
        env=env,
    )
    assert native_solstone.returncode == 0, native_solstone.stderr
    assert native_solstone.stdout == f"sol (solstone) {built_wheels.version}\n"
    assert not log.exists()

    journal = tmp_path / "journal"
    journal.mkdir()
    compat_env = {**env, "SOLSTONE_JOURNAL": str(journal)}
    compat = _run(
        [tool_bin / "sol", "call", "journal", "--help"],
        cwd=unrelated,
        env=compat_env,
    )
    assert compat.returncode != 78, compat.stderr
    assert log.exists(), compat.stderr
    spy = log.read_text(encoding="utf-8")
    assert "-P -m solstone.think.sol_compat_cli" in spy
    assert "__solstone_native_argv0=sol call journal --help" in spy


def test_isolated_pip_venv_install_exposes_new_layout(
    tmp_path: Path,
    built_wheels: BuiltWheels,
) -> None:
    python = _create_venv(tmp_path / "venv")
    install = _pip_install(python, (built_wheels.core, built_wheels.root))
    assert install.returncode == 0, install.stderr or install.stdout

    bin_dir = python.parent
    for name in wheel_checker.ROOT_LAUNCHER_NAMES + wheel_checker.CORE_SCRIPT_NAMES:
        result = _run([bin_dir / name, "--version"], cwd=tmp_path)
        assert result.returncode == 0, result.stderr
    _assert_new_owners(python)
    assert not (bin_dir / "solstone-python-compat").exists()


@pytest.mark.parametrize("installer", ("pip", "uv"))
def test_upgrade_from_old_script_ownership_converges(
    tmp_path: Path,
    built_wheels: BuiltWheels,
    installer: str,
) -> None:
    old_root, old_core = _old_layout_wheels(tmp_path)
    if installer == "pip":
        python = _create_venv(tmp_path / "upgrade-venv")
        old_install = _pip_install(python, (old_core, old_root))
        assert old_install.returncode == 0, old_install.stderr or old_install.stdout
        bin_dir = python.parent
        assert (bin_dir / "solstone-python-compat").exists()
        upgrade = _pip_install(
            python, (built_wheels.core, built_wheels.root), force=True
        )
        assert upgrade.returncode == 0, upgrade.stderr or upgrade.stdout
        reinstall = _pip_install(
            python, (built_wheels.core, built_wheels.root), force=True
        )
        assert reinstall.returncode == 0, reinstall.stderr or reinstall.stdout
        assert not (bin_dir / "solstone-python-compat").exists()
        _assert_new_owners(python)
        return

    env, tool_bin, _tool_dir, unrelated = _isolated_uv_env(tmp_path)
    old_install = _uv_tool_install(
        root_wheel=old_root,
        core_wheel=old_core,
        env=env,
        cwd=unrelated,
    )
    assert old_install.returncode == 0, old_install.stderr or old_install.stdout
    assert _tool_bin_names(tool_bin) == {"solstone-python-compat"}
    upgrade = _uv_tool_install(
        root_wheel=built_wheels.root,
        core_wheel=built_wheels.core,
        env=env,
        cwd=unrelated,
        force=True,
    )
    assert upgrade.returncode == 0, upgrade.stderr or upgrade.stdout
    reinstall = _uv_tool_install(
        root_wheel=built_wheels.root,
        core_wheel=built_wheels.core,
        env=env,
        cwd=unrelated,
        force=True,
    )
    assert reinstall.returncode == 0, reinstall.stderr or reinstall.stdout
    assert _tool_bin_names(tool_bin) == set(wheel_checker.ROOT_LAUNCHER_NAMES)
    env_bin = _tool_env_bin(tool_bin)
    assert not (env_bin / "solstone-python-compat").exists()
    _assert_new_owners(env_bin / "python")


def test_partial_uninstall_ownership_boundaries(
    tmp_path: Path,
    built_wheels: BuiltWheels,
) -> None:
    root_only_python = _create_venv(tmp_path / "root-only")
    assert (
        _pip_install(
            root_only_python, (built_wheels.core, built_wheels.root)
        ).returncode
        == 0
    )
    root_only_bin = root_only_python.parent
    result = _run(
        [root_only_python, "-m", "pip", "uninstall", "-y", "solstone"], cwd=tmp_path
    )
    assert result.returncode == 0, result.stderr or result.stdout
    assert not any(
        (root_only_bin / name).exists() for name in wheel_checker.ROOT_LAUNCHER_NAMES
    )
    assert (root_only_bin / "solstone-core").exists()

    core_only_python = _create_venv(tmp_path / "core-only")
    assert (
        _pip_install(
            core_only_python, (built_wheels.core, built_wheels.root)
        ).returncode
        == 0
    )
    core_only_bin = core_only_python.parent
    result = _run(
        [core_only_python, "-m", "pip", "uninstall", "-y", "solstone-core"],
        cwd=tmp_path,
    )
    assert result.returncode == 0, result.stderr or result.stdout
    assert not (core_only_bin / "solstone-core").exists()
    for name in wheel_checker.ROOT_LAUNCHER_NAMES:
        launcher = _run([core_only_bin / name, "--version"], cwd=tmp_path)
        assert launcher.returncode == 78
        assert launcher.stdout == ""
        assert "native solstone-core sibling is missing" in launcher.stderr

    full_python = _create_venv(tmp_path / "full")
    assert (
        _pip_install(full_python, (built_wheels.core, built_wheels.root)).returncode
        == 0
    )
    full_bin = full_python.parent
    result = _run(
        [full_python, "-m", "pip", "uninstall", "-y", "solstone", "solstone-core"],
        cwd=tmp_path,
    )
    assert result.returncode == 0, result.stderr or result.stdout
    assert not any(
        (full_bin / name).exists()
        for name in wheel_checker.ROOT_LAUNCHER_NAMES + wheel_checker.CORE_SCRIPT_NAMES
    )
