from __future__ import annotations

import json
import os
from pathlib import Path
from types import SimpleNamespace

import pytest

from oasyce import update_manager


def test_enable_managed_install_persists_auto_update(monkeypatch, tmp_path):
    monkeypatch.setenv("HOME", str(tmp_path))

    state = update_manager.enable_managed_install(auto_update=True)

    assert state["auto_update"] is True
    persisted = update_manager.read_managed_install_state()
    assert persisted["auto_update"] is True
    assert persisted["installed_via_bootstrap"] is True


def test_maybe_auto_update_skips_when_disabled(monkeypatch, tmp_path):
    monkeypatch.setenv("HOME", str(tmp_path))

    called = {"upgrade": False}
    monkeypatch.setattr(
        update_manager, "upgrade_managed_packages", lambda: called.update({"upgrade": True})
    )

    result = update_manager.maybe_auto_update_for_cli(
        module_name="oasyce.cli",
        argv=["info"],
        command_name="info",
    )

    assert result is False
    assert called["upgrade"] is False


def test_maybe_auto_update_reexecs_after_success(monkeypatch, tmp_path):
    monkeypatch.setenv("HOME", str(tmp_path))
    update_manager.enable_managed_install(auto_update=True)
    update_manager.write_managed_install_state(last_auto_update_check=0)

    monkeypatch.setattr(
        update_manager,
        "check_package_updates",
        lambda package_names=("oasyce", "odv"): [
            {
                "name": "oasyce",
                "current": "2.3.0",
                "latest": "2.4.0",
                "installed": True,
                "up_to_date": False,
            },
            {
                "name": "odv",
                "current": "0.2.1",
                "latest": "0.2.1",
                "installed": True,
                "up_to_date": True,
            },
        ],
    )
    monkeypatch.setattr(
        update_manager,
        "upgrade_managed_packages",
        lambda **_: SimpleNamespace(returncode=0, stderr=""),
    )

    seen = {}

    def fake_execvpe(executable, args, env):
        seen["executable"] = executable
        seen["args"] = args
        seen["env"] = env
        raise RuntimeError("reexec")

    monkeypatch.setattr(os, "execvpe", fake_execvpe)

    with pytest.raises(RuntimeError, match="reexec"):
        update_manager.maybe_auto_update_for_cli(
            module_name="oasyce.cli",
            argv=["quote", "ASSET_1"],
            command_name="quote",
        )

    assert seen["args"][0] == seen["executable"]
    assert seen["args"][1:4] == ["-m", "oasyce.cli", "quote"]
    assert seen["env"]["OASYCE_SKIP_AUTO_UPDATE"] == "1"


def test_package_install_context_prefers_editable_local_pyproject_version(monkeypatch, tmp_path):
    project_root = tmp_path / "oasyce-net"
    project_root.mkdir()
    (project_root / "pyproject.toml").write_text('[project]\nname = "oasyce"\nversion = "2.3.5"\n')
    dist_path = tmp_path / "site-packages" / "oasyce-2.3.0.dist-info"
    dist_path.parent.mkdir(parents=True)
    dist_path.mkdir()

    direct_url = json.dumps(
        {
            "dir_info": {"editable": True},
            "url": project_root.as_uri(),
        }
    )
    fake_dist = SimpleNamespace(
        version="2.3.0",
        _path=dist_path,
        read_text=lambda name: direct_url if name == "direct_url.json" else None,
    )

    monkeypatch.setattr(update_manager, "_distribution_for", lambda name: fake_dist)
    monkeypatch.setattr(update_manager, "_running_in_pipx", lambda: False)
    monkeypatch.setattr(update_manager, "_running_in_virtualenv", lambda: False)
    monkeypatch.setattr(update_manager, "is_externally_managed_environment", lambda: False)

    context = update_manager.package_install_context("oasyce")

    assert context["install_kind"] == "editable-local"
    assert context["version"] == "2.3.5"
    assert context["project_root"] == project_root


def test_package_install_context_treats_local_egg_info_as_editable(monkeypatch, tmp_path):
    project_root = tmp_path / "oasyce-net"
    project_root.mkdir()
    (project_root / "pyproject.toml").write_text('[project]\nname = "oasyce"\nversion = "2.3.5"\n')
    dist_path = project_root / "oasyce.egg-info"
    dist_path.mkdir()

    fake_dist = SimpleNamespace(
        version="2.3.0",
        _path=dist_path,
        read_text=lambda name: None,
    )

    monkeypatch.setattr(update_manager, "_distribution_for", lambda name: fake_dist)
    monkeypatch.setattr(update_manager, "_running_in_pipx", lambda: False)
    monkeypatch.setattr(update_manager, "_running_in_virtualenv", lambda: False)
    monkeypatch.setattr(update_manager, "is_externally_managed_environment", lambda: True)

    context = update_manager.package_install_context("oasyce")

    assert context["install_kind"] == "editable-local"
    assert context["version"] == "2.3.5"
    assert context["project_root"] == project_root


def test_managed_runtime_report_maps_odv_to_datavault(monkeypatch):
    monkeypatch.setattr(
        update_manager,
        "runtime_environment_report",
        lambda: {"python_executable": "/tmp/python", "python_version": "3.12.0"},
    )
    monkeypatch.setattr(
        update_manager,
        "package_install_context",
        lambda name: {
            "name": name,
            "version": "0.2.2" if name == "odv" else "2.3.5",
            "install_kind": "user-site",
            "project_root": None,
        },
    )
    monkeypatch.setattr(
        update_manager.importlib_util,
        "find_spec",
        lambda name: (
            SimpleNamespace(origin=f"/tmp/{name}/__init__.py") if name == "datavault" else None
        ),
    )
    monkeypatch.setattr(
        update_manager.shutil,
        "which",
        lambda name: f"/tmp/bin/{name}",
    )

    report = update_manager.managed_runtime_report(["odv"])

    assert report["environment"]["python_executable"] == "/tmp/python"
    assert report["packages"][0]["name"] == "odv"
    assert report["packages"][0]["import_name"] == "datavault"
    assert report["packages"][0]["importable"] is True
    assert report["packages"][0]["import_path"] == "/tmp/datavault/__init__.py"
    assert report["packages"][0]["cli_name"] == "datavault"


def test_update_command_uses_user_installs_for_externally_managed_python(monkeypatch):
    packages = [
        {
            "name": "oasyce",
            "current": "2.3.5",
            "latest": "2.3.5",
            "installed": True,
            "up_to_date": True,
        },
        {
            "name": "odv",
            "current": "0.2.0",
            "latest": "0.2.2",
            "installed": True,
            "up_to_date": False,
        },
    ]

    monkeypatch.setattr(
        update_manager,
        "package_install_context",
        lambda name: {
            "name": name,
            "installed": True,
            "version": "0.0.0",
            "install_kind": "system-site" if name == "odv" else "editable-local",
            "editable": name == "oasyce",
            "project_root": Path("/tmp/oasyce-net") if name == "oasyce" else None,
            "dist_path": None,
        },
    )
    monkeypatch.setattr(update_manager, "_running_in_pipx", lambda: False)
    monkeypatch.setattr(update_manager, "_running_in_virtualenv", lambda: False)
    monkeypatch.setattr(update_manager, "is_externally_managed_environment", lambda: True)

    command = update_manager.update_command_for_agents(packages)

    assert "--user" in command
    assert "--break-system-packages" in command
    assert command.endswith("odv")


def test_upgrade_managed_packages_returns_manual_instructions_for_editable_installs(
    monkeypatch, tmp_path
):
    project_root = tmp_path / "oasyce-net"
    project_root.mkdir()
    packages = [
        {
            "name": "oasyce",
            "current": "2.3.0",
            "latest": "2.3.4",
            "installed": True,
            "up_to_date": False,
        }
    ]

    monkeypatch.setattr(
        update_manager,
        "package_install_context",
        lambda name: {
            "name": name,
            "installed": True,
            "version": "2.3.0",
            "install_kind": "editable-local",
            "editable": True,
            "project_root": project_root,
            "dist_path": None,
        },
    )

    result = update_manager.upgrade_managed_packages(packages)

    assert result.returncode == 1
    assert "editable local installs" in result.stderr
    assert "git pull --ff-only" in result.stderr


def test_upgrade_managed_packages_rewrites_pep668_failures(monkeypatch):
    packages = [
        {
            "name": "odv",
            "current": "0.2.0",
            "latest": "0.2.2",
            "installed": True,
            "up_to_date": False,
        }
    ]

    monkeypatch.setattr(
        update_manager,
        "package_install_context",
        lambda name: {
            "name": name,
            "installed": True,
            "version": "0.2.0",
            "install_kind": "system-site",
            "editable": False,
            "project_root": None,
            "dist_path": None,
        },
    )
    monkeypatch.setattr(update_manager, "_running_in_pipx", lambda: False)
    monkeypatch.setattr(update_manager, "_running_in_virtualenv", lambda: False)
    monkeypatch.setattr(update_manager, "is_externally_managed_environment", lambda: True)
    monkeypatch.setattr(
        update_manager.subprocess,
        "run",
        lambda *args, **kwargs: SimpleNamespace(
            returncode=1,
            stdout="",
            stderr="error: externally-managed-environment",
        ),
    )

    result = update_manager.upgrade_managed_packages(packages)

    assert result.returncode == 1
    assert "PEP 668" in result.stderr
    assert "Run this instead:" in result.stderr
