"""Tests for the run_with_uv function and related functionality."""

import subprocess
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from fastmcp.cli.run import run_with_uv


class TestRunWithUv:
    """Test the run_with_uv function."""

    @patch("subprocess.run")
    def test_run_with_uv_basic(self, mock_run):
        """Test basic run_with_uv execution."""
        mock_run.return_value = Mock(returncode=0)

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py")

        assert exc_info.value.code == 0

        # Check the command that was called
        mock_run.assert_called_once()
        cmd = mock_run.call_args[0][0]
        env = mock_run.call_args.kwargs.get("env", {})

        # With no environment config, the command should be returned unchanged
        expected = [
            "fastmcp",
            "run",
            "server.py",
        ]
        assert cmd == expected
        # Check that the environment marker is set
        assert env.get("FASTMCP_UV_SPAWNED") == "1"

    @patch("subprocess.run")
    def test_run_with_uv_python_version(self, mock_run):
        """Test run_with_uv with Python version."""
        mock_run.return_value = Mock(returncode=0)

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py", python_version="3.11")

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]
        expected = [
            "uv",
            "run",
            "--python",
            "3.11",
            "fastmcp",
            "run",
            "server.py",
        ]
        assert cmd == expected

    @patch("subprocess.run")
    def test_run_with_uv_project(self, mock_run):
        """Test run_with_uv with project directory."""
        mock_run.return_value = Mock(returncode=0)
        # Use an absolute path that works on all platforms
        project_path = Path.cwd() / "my" / "project"

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py", project=project_path)

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]
        # Check the basic structure
        assert cmd[:3] == ["uv", "run", "--project"]
        # Check that the project path is absolute
        assert Path(cmd[3]).is_absolute()
        # Check the rest of the command
        assert cmd[4:] == [
            "fastmcp",
            "run",
            "server.py",
        ]

    @patch("subprocess.run")
    def test_run_with_uv_with_packages(self, mock_run):
        """Test run_with_uv with additional packages."""
        mock_run.return_value = Mock(returncode=0)

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py", with_packages=["pandas", "numpy"])

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]
        expected = [
            "uv",
            "run",
            "--with",
            "pandas",  # original order preserved
            "--with",
            "numpy",  # original order preserved
            "fastmcp",
            "run",
            "server.py",
        ]
        assert cmd == expected

    @patch("subprocess.run")
    def test_run_with_uv_with_requirements(self, mock_run):
        """Test run_with_uv with requirements file."""
        mock_run.return_value = Mock(returncode=0)
        req_path = Path("requirements.txt")

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py", with_requirements=req_path)

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]
        expected = [
            "uv",
            "run",
            "--with-requirements",
            str(req_path.resolve()),  # auto-resolved to absolute path
            "fastmcp",
            "run",
            "server.py",
        ]
        assert cmd == expected

    @patch("subprocess.run")
    def test_run_with_uv_transport_options(self, mock_run):
        """Test run_with_uv with transport-related options."""
        mock_run.return_value = Mock(returncode=0)

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv(
                "server.py",
                transport="http",
                host="localhost",
                port=8080,
                path="/api",
                log_level="DEBUG",
                show_banner=False,
            )

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]
        # With no environment config, no uv run prefix
        expected = [
            "fastmcp",
            "run",
            "server.py",
            "--transport",
            "http",
            "--host",
            "localhost",
            "--port",
            "8080",
            "--path",
            "/api",
            "--log-level",
            "DEBUG",
            "--no-banner",
        ]
        assert cmd == expected

    @patch("subprocess.run")
    def test_run_with_uv_all_options(self, mock_run):
        """Test run_with_uv with all options combined."""
        mock_run.return_value = Mock(returncode=0)

        # Use an absolute path that works on all platforms
        project_path = Path.cwd() / "workspace"

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv(
                "server.py",
                python_version="3.10",
                project=project_path,
                with_packages=["pandas"],
                with_requirements=Path("reqs.txt"),
                transport="http",
                port=9000,
                show_banner=False,
            )

        assert exc_info.value.code == 0

        cmd = mock_run.call_args[0][0]

        # When project is specified, Python version is not included
        # Build expected command step by step
        expected_start = ["uv", "run", "--project"]

        # Check start and that project path is absolute
        assert cmd[:3] == expected_start
        assert Path(cmd[3]).is_absolute()

        # Find the index where packages and requirements start
        next_idx = 4
        assert cmd[next_idx : next_idx + 2] == ["--with", "pandas"]
        next_idx += 2
        assert cmd[next_idx : next_idx + 1] == ["--with-requirements"]
        next_idx += 1
        # Check requirements path is absolute
        assert Path(cmd[next_idx]).is_absolute()
        assert Path(cmd[next_idx]).name == "reqs.txt"
        next_idx += 1

        # Rest should be the fastmcp command with options
        assert cmd[next_idx:] == [
            "fastmcp",
            "run",
            "server.py",
            "--transport",
            "http",
            "--port",
            "9000",
            "--no-banner",
        ]

    @patch("subprocess.run")
    def test_run_with_uv_error_handling(self, mock_run):
        """Test run_with_uv error handling."""
        mock_run.side_effect = subprocess.CalledProcessError(1, ["uv", "run"])

        with pytest.raises(SystemExit) as exc_info:
            run_with_uv("server.py")

        assert exc_info.value.code == 1

    @patch("fastmcp.cli.run.logger")
    @patch("subprocess.run")
    def test_run_with_uv_logging(self, mock_run, mock_logger):
        """Test that run_with_uv logs the command."""
        mock_run.return_value = Mock(returncode=0)

        with pytest.raises(SystemExit):
            run_with_uv("server.py", python_version="3.11")

        # Check that debug logging was called with the command
        mock_logger.debug.assert_called()
        call_args = mock_logger.debug.call_args[0][0]
        assert "Running command:" in call_args
        assert "uv run --python 3.11" in call_args
