"""Tests for ievo.core.updater — installer detection, PyPI check, upgrade."""

from __future__ import annotations

import subprocess
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

from ievo.core.updater import (
    Installer,
    detect_installer,
    fetch_latest_version,
    get_current_version,
    run_upgrade,
)

# ── detect_installer ──────────────────────────────────────────


def test_detect_installer_uvx_cache_path() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = ["/Users/x/.cache/uv/tool-run/ievo/bin/ievo"]
        assert detect_installer() == Installer.UVX


def test_detect_installer_uvx_in_path() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = ["/usr/local/bin/uvx"]
        assert detect_installer() == Installer.UVX


def test_detect_installer_uv_tool() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = ["/Users/x/.local/share/uv/tools/ievo/bin/ievo"]
        assert detect_installer() == Installer.UV_TOOL


def test_detect_installer_pip_fallback() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = ["/usr/local/bin/ievo"]
        assert detect_installer() == Installer.PIP


def test_detect_installer_empty_argv() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = [""]
        assert detect_installer() == Installer.PIP


def test_detect_installer_no_argv() -> None:
    with patch("ievo.core.updater.sys") as mock_sys:
        mock_sys.argv = []
        assert detect_installer() == Installer.PIP


# ── fetch_latest_version ──────────────────────────────────────


@pytest.mark.anyio
async def test_fetch_latest_version_success() -> None:
    mock_response = MagicMock()
    mock_response.json.return_value = {"info": {"version": "0.2.0"}}
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)
    mock_client.get = AsyncMock(return_value=mock_response)

    with patch("httpx.AsyncClient", return_value=mock_client):
        result = await fetch_latest_version()

    assert result == "0.2.0"


@pytest.mark.anyio
async def test_fetch_latest_version_network_error() -> None:
    mock_client = AsyncMock()
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)
    mock_client.get = AsyncMock(side_effect=httpx.ConnectError("Connection failed"))

    with (
        patch("httpx.AsyncClient", return_value=mock_client),
        pytest.raises(httpx.ConnectError),
    ):
        await fetch_latest_version()


@pytest.mark.anyio
async def test_fetch_latest_version_http_error() -> None:
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
        "Server error", request=MagicMock(), response=mock_response
    )

    mock_client = AsyncMock()
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)
    mock_client.get = AsyncMock(return_value=mock_response)

    with (
        patch("httpx.AsyncClient", return_value=mock_client),
        pytest.raises(httpx.HTTPStatusError),
    ):
        await fetch_latest_version()


# ── run_upgrade ───────────────────────────────────────────────


def test_run_upgrade_uv_tool() -> None:
    with patch("ievo.core.updater.subprocess.run") as mock_run:
        mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        run_upgrade(Installer.UV_TOOL)

    mock_run.assert_called_once_with(
        ["uv", "tool", "upgrade", "ievo"],
        check=True,
        capture_output=True,
        text=True,
    )


def test_run_upgrade_pip() -> None:
    with patch("ievo.core.updater.subprocess.run") as mock_run:
        mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        run_upgrade(Installer.PIP)

    mock_run.assert_called_once_with(
        ["pip", "install", "--upgrade", "ievo"],
        check=True,
        capture_output=True,
        text=True,
    )


def test_run_upgrade_uvx_raises() -> None:
    with pytest.raises(ValueError, match="UVX does not support upgrade"):
        run_upgrade(Installer.UVX)


def test_run_upgrade_binary_not_found() -> None:
    with (
        patch(
            "ievo.core.updater.subprocess.run",
            side_effect=FileNotFoundError("uv not found"),
        ),
        pytest.raises(FileNotFoundError),
    ):
        run_upgrade(Installer.UV_TOOL)


# ── get_current_version ───────────────────────────────────────


def test_get_current_version_returns_string() -> None:
    result = get_current_version()
    assert isinstance(result, str)
    assert len(result) > 0


def test_get_current_version_matches_module() -> None:
    from ievo import __version__

    assert get_current_version() == __version__


# ── Integration: detect_installer with real env ──────────────


def test_detect_installer_real_env_returns_valid_installer() -> None:
    """Integration: real sys.argv returns a valid Installer member."""
    result = detect_installer()
    assert isinstance(result, Installer)
    assert result in (Installer.UVX, Installer.UV_TOOL, Installer.PIP)


def test_run_upgrade_uv_tool_returns_completed_process() -> None:
    """Integration: verify run_upgrade returns CompletedProcess (not just mock assertion)."""
    fake_result = subprocess.CompletedProcess(
        args=["uv", "tool", "upgrade", "ievo"],
        returncode=0,
        stdout="upgraded ievo",
        stderr="",
    )
    with patch("ievo.core.updater.subprocess.run", return_value=fake_result):
        result = run_upgrade(Installer.UV_TOOL)

    assert isinstance(result, subprocess.CompletedProcess)
    assert result.returncode == 0
    assert "upgraded" in result.stdout


def test_run_upgrade_pip_returns_completed_process() -> None:
    """Integration: verify run_upgrade returns CompletedProcess with stdout."""
    fake_result = subprocess.CompletedProcess(
        args=["pip", "install", "--upgrade", "ievo"],
        returncode=0,
        stdout="Successfully installed ievo-0.2.0",
        stderr="",
    )
    with patch("ievo.core.updater.subprocess.run", return_value=fake_result):
        result = run_upgrade(Installer.PIP)

    assert isinstance(result, subprocess.CompletedProcess)
    assert result.returncode == 0
    assert "ievo" in result.stdout
