"""Tests for ievo update command — self-update CLI."""

from __future__ import annotations

import subprocess
from unittest.mock import AsyncMock, patch

import pytest
import typer

from ievo.core.updater import Installer

# ── Command tests ─────────────────────────────────────────────


def test_update_already_up_to_date() -> None:
    """AC-1: current == latest -> success message, no subprocess."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.1.0",
        ),
        patch("ievo.commands.update.run_upgrade") as mock_upgrade,
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 0
    mock_upgrade.assert_not_called()


def test_update_uv_tool_upgrade() -> None:
    """AC-2: newer version + uv tool -> runs subprocess."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.UV_TOOL),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.2.0",
        ),
        patch("ievo.commands.update.run_upgrade") as mock_upgrade,
    ):
        update()

    mock_upgrade.assert_called_once_with(Installer.UV_TOOL)


def test_update_pip_upgrade() -> None:
    """AC-3: newer version + pip -> runs subprocess."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.2.0",
        ),
        patch("ievo.commands.update.run_upgrade") as mock_upgrade,
    ):
        update()

    mock_upgrade.assert_called_once_with(Installer.PIP)


def test_update_uvx_informational() -> None:
    """AC-4: uvx -> informational message, no subprocess, exit 0."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.UVX),
        patch("ievo.commands.update.run_upgrade") as mock_upgrade,
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 0
    mock_upgrade.assert_not_called()


def test_update_network_error() -> None:
    """AC-5: PyPI unreachable -> error message, exit 1."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            side_effect=Exception("Connection failed"),
        ),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 1


def test_update_no_deprecated_warning() -> None:
    """Negative: deprecated_command is NOT called."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.UVX),
        patch("ievo.utils.deprecation.warn") as mock_warn,
        pytest.raises(typer.Exit),
    ):
        update()

    mock_warn.assert_not_called()


def test_update_subprocess_failure() -> None:
    """Edge case: subprocess fails -> error, exit 1."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.2.0",
        ),
        patch(
            "ievo.commands.update.run_upgrade",
            side_effect=subprocess.CalledProcessError(1, "pip", stderr="fail"),
        ),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 1


def test_update_binary_not_found() -> None:
    """Edge case: installer binary missing -> error, exit 1."""
    from ievo.commands.update import update

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.UV_TOOL),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.2.0",
        ),
        patch(
            "ievo.commands.update.run_upgrade",
            side_effect=FileNotFoundError("uv not found"),
        ),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 1


# ── Integration: verify real console output content ──────────


def _capture_console() -> tuple:
    """Create a Rich Console that writes to a StringIO buffer."""
    from io import StringIO

    from rich.console import Console

    buf = StringIO()
    return Console(file=buf, force_terminal=False, no_color=True), buf


def test_update_up_to_date_output_content() -> None:
    """Integration: verify actual success message content."""
    from ievo.commands.update import update

    console, buf = _capture_console()

    with (
        patch("ievo.commands.update.get_current_version", return_value="1.0.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="1.0.0",
        ),
        patch("ievo.utils.console.console", console),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 0
    output = buf.getvalue()
    assert "1.0.0" in output
    assert "up to date" in output


def test_update_uvx_output_content() -> None:
    """Integration: verify uvx informational message content."""
    from ievo.commands.update import update

    console, buf = _capture_console()

    with (
        patch("ievo.commands.update.get_current_version", return_value="1.0.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.UVX),
        patch("ievo.utils.console.console", console),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 0
    output = buf.getvalue()
    assert "uvx" in output
    assert "uv cache clean" in output


def test_update_newer_version_output_content() -> None:
    """Integration: verify version info is printed before upgrade."""
    from ievo.commands.update import update

    console, buf = _capture_console()

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            return_value="0.2.0",
        ),
        patch("ievo.commands.update.run_upgrade"),
        patch("ievo.utils.console.console", console),
    ):
        update()

    output = buf.getvalue()
    assert "0.1.0" in output
    assert "0.2.0" in output
    assert "pip" in output


def test_update_network_error_output_content() -> None:
    """Integration: verify error message on network failure."""
    from ievo.commands.update import update

    console, buf = _capture_console()

    with (
        patch("ievo.commands.update.get_current_version", return_value="0.1.0"),
        patch("ievo.commands.update.detect_installer", return_value=Installer.PIP),
        patch(
            "ievo.commands.update.fetch_latest_version",
            new_callable=AsyncMock,
            side_effect=Exception("Connection failed"),
        ),
        patch("ievo.utils.console.console", console),
        pytest.raises(typer.Exit) as exc_info,
    ):
        update()

    assert exc_info.value.exit_code == 1
    output = buf.getvalue()
    assert "Could not check for updates" in output
