"""Tests for the FastMCP CLI."""

import json
from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from fastmcp.cli.cli import app, _parse_env_var


@pytest.fixture
def mock_config(tmp_path):
    """Create a mock Claude config file."""
    config = {"mcpServers": {}}
    config_file = tmp_path / "claude_desktop_config.json"
    config_file.write_text(json.dumps(config))
    return config_file


@pytest.fixture
def server_file(tmp_path):
    """Create a server file."""
    server_file = tmp_path / "server.py"
    server_file.write_text(
        """from fastmcp import FastMCP
mcp = FastMCP("test")
"""
    )
    return server_file


@pytest.fixture
def mock_env_file(tmp_path):
    """Create a mock .env file."""
    env_file = tmp_path / ".env"
    env_file.write_text("FOO=bar\nBAZ=123")
    return env_file


def test_parse_env_var():
    """Test parsing environment variables."""
    assert _parse_env_var("FOO=bar") == ("FOO", "bar")
    assert _parse_env_var("FOO=") == ("FOO", "")
    assert _parse_env_var("FOO=bar baz") == ("FOO", "bar baz")
    assert _parse_env_var("FOO = bar ") == ("FOO", "bar")

    with pytest.raises(SystemExit):
        _parse_env_var("invalid")


@pytest.mark.parametrize(
    "args,expected_env",
    [
        # Basic env var
        (
            ["--env-var", "FOO=bar"],
            {"FOO": "bar"},
        ),
        # Multiple env vars
        (
            ["--env-var", "FOO=bar", "--env-var", "BAZ=123"],
            {"FOO": "bar", "BAZ": "123"},
        ),
        # Env var with spaces
        (
            ["--env-var", "FOO=bar baz"],
            {"FOO": "bar baz"},
        ),
    ],
)
def test_install_with_env_vars(mock_config, server_file, args, expected_env):
    """Test installing with environment variables."""
    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        result = runner.invoke(
            app,
            ["install", str(server_file)] + args,
        )

        assert result.exit_code == 0

        # Read the config file and check env vars
        config = json.loads(mock_config.read_text())
        assert "mcpServers" in config
        assert len(config["mcpServers"]) == 1
        server = next(iter(config["mcpServers"].values()))
        assert server["env"] == expected_env


def test_install_with_env_file(mock_config, server_file, mock_env_file):
    """Test installing with environment variables from a file."""
    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        result = runner.invoke(
            app,
            ["install", str(server_file), "--env-file", str(mock_env_file)],
        )

        assert result.exit_code == 0

        # Read the config file and check env vars
        config = json.loads(mock_config.read_text())
        assert "mcpServers" in config
        assert len(config["mcpServers"]) == 1
        server = next(iter(config["mcpServers"].values()))
        assert server["env"] == {"FOO": "bar", "BAZ": "123"}


def test_install_preserves_existing_env_vars(mock_config, server_file):
    """Test that installing preserves existing environment variables."""
    # Set up initial config with env vars
    config = {
        "mcpServers": {
            "test": {
                "command": "uv",
                "args": [
                    "run",
                    "--with",
                    "fastmcp",
                    "fastmcp",
                    "run",
                    str(server_file),
                ],
                "env": {"FOO": "bar", "BAZ": "123"},
            }
        }
    }
    mock_config.write_text(json.dumps(config))

    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        # Install with a new env var
        result = runner.invoke(
            app,
            ["install", str(server_file), "--env-var", "NEW=value"],
        )

        assert result.exit_code == 0

        # Read the config file and check env vars are preserved
        config = json.loads(mock_config.read_text())
        server = next(iter(config["mcpServers"].values()))
        assert server["env"] == {"FOO": "bar", "BAZ": "123", "NEW": "value"}


def test_install_updates_existing_env_vars(mock_config, server_file):
    """Test that installing updates existing environment variables."""
    # Set up initial config with env vars
    config = {
        "mcpServers": {
            "test": {
                "command": "uv",
                "args": [
                    "run",
                    "--with",
                    "fastmcp",
                    "fastmcp",
                    "run",
                    str(server_file),
                ],
                "env": {"FOO": "bar", "BAZ": "123"},
            }
        }
    }
    mock_config.write_text(json.dumps(config))

    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        # Update an existing env var
        result = runner.invoke(
            app,
            ["install", str(server_file), "--env-var", "FOO=newvalue"],
        )

        assert result.exit_code == 0

        # Read the config file and check env var was updated
        config = json.loads(mock_config.read_text())
        server = next(iter(config["mcpServers"].values()))
        assert server["env"] == {"FOO": "newvalue", "BAZ": "123"}


def test_server_dependencies(mock_config, server_file):
    """Test that server dependencies are correctly handled."""
    # Create a server file with dependencies
    server_file = server_file.parent / "server_with_deps.py"
    server_file.write_text(
        """from fastmcp import FastMCP
mcp = FastMCP("test", dependencies=["pandas", "numpy"])
"""
    )

    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        result = runner.invoke(app, ["install", str(server_file)])

        assert result.exit_code == 0

        # Read the config file and check dependencies were added as --with args
        config = json.loads(mock_config.read_text())
        server = next(iter(config["mcpServers"].values()))
        assert "--with" in server["args"]
        assert "pandas" in server["args"]
        assert "numpy" in server["args"]


def test_server_dependencies_empty(mock_config, server_file):
    """Test that server with no dependencies works correctly."""
    runner = CliRunner()

    with patch("fastmcp.cli.claude.get_claude_config_path") as mock_config_path:
        mock_config_path.return_value = mock_config.parent

        result = runner.invoke(app, ["install", str(server_file)])

        assert result.exit_code == 0

        # Read the config file and check only fastmcp is in --with args
        config = json.loads(mock_config.read_text())
        server = next(iter(config["mcpServers"].values()))
        assert server["args"].count("--with") == 1
        assert "fastmcp" in server["args"]


def test_dev_with_dependencies(mock_config, server_file):
    """Test that dev command handles dependencies correctly."""
    # Create a server file with dependencies
    server_file = server_file.parent / "server_with_deps.py"
    server_file.write_text(
        """from fastmcp import FastMCP
mcp = FastMCP("test", dependencies=["pandas", "numpy"])
"""
    )

    runner = CliRunner()

    with patch("subprocess.run") as mock_run:
        mock_run.return_value.returncode = 0  # Set successful return code
        result = runner.invoke(app, ["dev", str(server_file)])
        assert result.exit_code == 0

        # Check that dependencies were passed to subprocess.run
        mock_run.assert_called_once()
        args = mock_run.call_args[0][0]
        assert "npx" in args
        assert "@modelcontextprotocol/inspector" in args
        assert "uv" in args
        assert "run" in args
        assert "--with" in args
        assert "pandas" in args
        assert "numpy" in args
        assert "fastmcp" in args


def test_run_with_dependencies(mock_config, server_file):
    """Test that run command does not handle dependencies."""
    # Create a server file with dependencies
    server_file = server_file.parent / "server_with_deps.py"
    server_file.write_text(
        """from fastmcp import FastMCP
mcp = FastMCP("test", dependencies=["pandas", "numpy"])

if __name__ == "__main__":
    mcp.run()
"""
    )

    runner = CliRunner()

    with patch("subprocess.run") as mock_run:
        result = runner.invoke(app, ["run", str(server_file)])
        assert result.exit_code == 0

        # Run command should not call subprocess.run
        mock_run.assert_not_called()
