"""Unit tests for piptool functionality."""

from __future__ import annotations

import subprocess
from unittest.mock import Mock, patch

import pytest

from pytola.dev.piptool.cli import (
    PipToolsConfig,
    _exec_command,
    check_uv_callable,
    pip_check,
    pip_download,
    pip_freeze,
    pip_install,
    pip_list,
    pip_show,
    pip_uninstall,
)


class TestPipToolsConfig:
    """Tests for PipToolsConfig class."""

    def test_config_initialization(self) -> None:
        """Test configuration initialization with defaults."""
        config = PipToolsConfig()
        assert config.NAME == "pip_tools"
        assert config.DEFAULT_PACKAGES_DIR == "packages"
        assert isinstance(config.TRUSTED_PIP_URL, list)
        assert len(config.TRUSTED_PIP_URL) > 0

    def test_config_custom_values(self) -> None:
        """Test configuration with custom values."""
        config = PipToolsConfig(
            name="custom_tools",
            trusted_pip_url=["--trusted-host", "custom.host"],
            default_packages_dir="custom_packages",
        )
        assert config.NAME == "custom_tools"
        assert config.DEFAULT_PACKAGES_DIR == "custom_packages"
        assert "--trusted-host" in config.TRUSTED_PIP_URL

    def test_packages_path_property(self) -> None:
        """Test packages_path cached property."""
        config = PipToolsConfig(default_packages_dir="test_packages")
        assert str(config.packages_path) == "test_packages"


class TestExecCommand:
    """Tests for _exec_command function."""

    def test_successful_command_execution(self) -> None:
        """Test successful command execution."""
        with patch("subprocess.run") as mock_run:
            mock_result = Mock()
            mock_result.stdout = "success output"
            mock_result.stderr = ""
            mock_run.return_value = mock_result

            result = _exec_command(["echo", "test"], capture_output=True)

            assert result == mock_result
            mock_run.assert_called_once_with(
                ["echo", "test"],
                cwd=None,
                capture_output=True,
                text=True,
                check=True,
                timeout=None,
            )

    def test_command_failure(self) -> None:
        """Test command execution failure."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.CalledProcessError(1, ["failed"])

            with pytest.raises(subprocess.CalledProcessError):
                _exec_command(["failed"])

    def test_command_timeout(self) -> None:
        """Test command timeout handling."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.TimeoutExpired(["timeout"], 5)

            with pytest.raises(subprocess.TimeoutExpired):
                _exec_command(["timeout"], timeout=5)

    def test_command_not_found(self) -> None:
        """Test command not found error."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = FileNotFoundError()

            with pytest.raises(FileNotFoundError):
                _exec_command(["nonexistent"])


class TestUVCallable:
    """Tests for check_uv_callable function."""

    def test_uv_available(self) -> None:
        """Test when uv is available."""
        with patch("subprocess.run") as mock_run:
            mock_result = Mock()
            mock_result.returncode = 0
            mock_run.return_value = mock_result

            result = check_uv_callable()

            assert result is True

    def test_uv_not_available(self) -> None:
        """Test when uv is not available."""
        with patch("subprocess.run") as mock_run:
            mock_result = Mock()
            mock_result.returncode = 1
            mock_run.return_value = mock_result

            result = check_uv_callable()

            assert result is False

    def test_uv_timeout(self) -> None:
        """Test when uv command times out."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.TimeoutExpired(["uv", "--version"], 5)

            result = check_uv_callable()

            assert result is False

    def test_uv_not_installed(self) -> None:
        """Test when uv is not installed."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = FileNotFoundError()

            result = check_uv_callable()

            assert result is False


class TestPipCommands:
    """Tests for pip command functions."""

    def test_pip_install_success(self) -> None:
        """Test successful pip install."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            pip_install(["requests", "numpy"])
            mock_exec.assert_called_once()

    def test_pip_install_no_packages(self) -> None:
        """Test pip install with no packages."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            # 直接测试函数行为 - 当提供空列表时, 不应该调用 _exec_command
            pip_install([])
            mock_exec.assert_not_called()

            # 也测试 None 情况
            pip_install(None)
            mock_exec.assert_not_called()

    def test_pip_uninstall_success(self) -> None:
        """Test successful pip uninstall."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            pip_uninstall(["requests"])
            mock_exec.assert_called_once()

    def test_pip_download_success(self) -> None:
        """Test successful pip download."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            with patch("pathlib.Path.mkdir"):
                pip_download(["requests"])
                mock_exec.assert_called_once()

    def test_pip_freeze_success(self) -> None:
        """Test successful pip freeze."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            with patch("pytola.dev.piptool.cli.check_uv_callable", return_value=False):
                mock_result = Mock()
                mock_result.stdout = "requests==2.28.0\nnumpy==1.24.0"
                mock_exec.return_value = mock_result

                with patch("pathlib.Path.open"):
                    pip_freeze()
                    mock_exec.assert_called_once()

    def test_pip_list_success(self) -> None:
        """Test successful pip list."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            mock_result = Mock()
            mock_result.stdout = "requests==2.28.0\nnumpy==1.24.0"
            mock_exec.return_value = mock_result

            pip_list()
            mock_exec.assert_called_once()

    def test_pip_show_success(self) -> None:
        """Test successful pip show."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            mock_result = Mock()
            mock_result.stdout = "Name: requests\nVersion: 2.28.0"
            mock_exec.return_value = mock_result

            pip_show("requests")
            mock_exec.assert_called_once()

    def test_pip_check_success(self) -> None:
        """Test successful pip check."""
        with patch("pytola.dev.piptool.cli._exec_command") as mock_exec:
            pip_check()
            mock_exec.assert_called_once()


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
