"""The interpreter resolver derives the target project's Python version.

uv lets a ``.python-version`` file override ``requires-python`` during
interpreter selection — that mismatch is the live failure mode (a repo pinned
to 3.13 whose packages need 3.14). The resolver defends against it: it honors
``.python-version`` only when it actually satisfies ``requires-python``,
otherwise it resolves a concrete version from ``requires-python`` so provisioning
can pass it to uv explicitly.
"""

from __future__ import annotations

from pathlib import Path

from roboco.services.toolchain import ResolvedPython, resolve_target_python, satisfies


def _write(root: Path, *, pyproject: str | None = None, pin: str | None = None) -> None:
    if pyproject is not None:
        (root / "pyproject.toml").write_text(pyproject)
    if pin is not None:
        (root / ".python-version").write_text(pin)


def test_requires_python_only_resolves_lowest_satisfying(tmp_path: Path) -> None:
    _write(tmp_path, pyproject='[project]\nrequires-python = ">=3.14,<3.15"\n')
    assert resolve_target_python(tmp_path) == ResolvedPython("3.14", "requires_python")


def test_python_version_file_ignored_when_it_violates_requires_python(
    tmp_path: Path,
) -> None:
    # The guard-core-app bug: pin says 3.13 but the packages need 3.14.
    _write(
        tmp_path,
        pyproject='[project]\nrequires-python = ">=3.14,<3.15"\n',
        pin="3.13\n",
    )
    assert resolve_target_python(tmp_path) == ResolvedPython("3.14", "requires_python")


def test_python_version_file_honored_when_it_satisfies(tmp_path: Path) -> None:
    _write(
        tmp_path,
        pyproject='[project]\nrequires-python = ">=3.14,<3.15"\n',
        pin="3.14\n",
    )
    assert resolve_target_python(tmp_path) == ResolvedPython(
        "3.14", "python_version_file"
    )


def test_python_version_file_with_patch_and_satisfying(tmp_path: Path) -> None:
    _write(
        tmp_path,
        pyproject='[project]\nrequires-python = ">=3.12"\n',
        pin="3.13.2\n",
    )
    assert resolve_target_python(tmp_path) == ResolvedPython(
        "3.13", "python_version_file"
    )


def test_open_lower_bound_picks_that_minor(tmp_path: Path) -> None:
    _write(tmp_path, pyproject='[project]\nrequires-python = ">=3.12,<3.13"\n')
    assert resolve_target_python(tmp_path) == ResolvedPython("3.12", "requires_python")


def test_poetry_table_requires_python(tmp_path: Path) -> None:
    _write(
        tmp_path,
        pyproject='[tool.poetry.dependencies]\npython = ">=3.14,<3.15"\n',
    )
    assert resolve_target_python(tmp_path) == ResolvedPython("3.14", "requires_python")


def test_no_python_project_returns_none(tmp_path: Path) -> None:
    # No pyproject and no pin → nothing to resolve; caller leaves uv unchanged.
    assert resolve_target_python(tmp_path) is None


def test_pyproject_without_requires_python_and_no_pin_returns_none(
    tmp_path: Path,
) -> None:
    _write(tmp_path, pyproject='[project]\nname = "x"\nversion = "0"\n')
    assert resolve_target_python(tmp_path) is None


def test_pin_only_no_requires_python_is_honored(tmp_path: Path) -> None:
    # A pin with no requires-python constraint is trivially satisfying.
    _write(tmp_path, pyproject='[project]\nname = "x"\nversion = "0"\n', pin="3.13\n")
    assert resolve_target_python(tmp_path) == ResolvedPython(
        "3.13", "python_version_file"
    )


def test_satisfies_helper() -> None:
    assert satisfies("3.14", ">=3.14,<3.15") is True
    assert satisfies("3.13", ">=3.14,<3.15") is False
    assert satisfies("3.12", "") is True


def test_roboco_repo_floor_resolves_to_its_real_python() -> None:
    """RoboCo's own ``requires-python`` must resolve to 3.13, not lower.

    Its code imports ``tomllib`` (3.11+) and the stack runs on 3.13; a
    permissive floor like ``>=3.10`` made the resolver provision agent
    workspaces at 3.10, where the suite cannot even be collected. This guards
    the floor against silently regressing below the real requirement.
    """
    root = Path(__file__).resolve()
    while not (root / "pyproject.toml").is_file():
        if root.parent == root:
            raise AssertionError("repo root with pyproject.toml not found")
        root = root.parent
    resolved = resolve_target_python(root)
    assert resolved is not None
    assert resolved.version == "3.13"
