"""
Tests for the MCP server and tools.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from exceptions import ErrorCode


class TestInputValidation:
    """Tests for input validation functions."""

    def test_validate_id_valid(self):
        """Test valid IDs pass validation."""
        from server import _validate_id

        assert _validate_id("test-123", "project_id") is None
        assert _validate_id("my_project", "project_id") is None
        assert _validate_id("Agent-001", "agent_id") is None

    def test_validate_id_empty(self):
        """Test empty ID fails validation."""
        from server import _validate_id

        error = _validate_id("", "project_id")
        assert error is not None
        assert "required" in error.lower()

    def test_validate_id_too_long(self):
        """Test too-long ID fails validation."""
        from server import _validate_id

        error = _validate_id("a" * 200, "project_id")
        assert error is not None
        assert "1-128" in error

    def test_validate_id_invalid_chars(self):
        """Test invalid characters fail validation."""
        from server import _validate_id

        assert _validate_id("test@invalid", "project_id") is not None
        assert _validate_id("test!project", "project_id") is not None
        assert _validate_id("test project", "project_id") is not None

    def test_validate_branch_valid(self):
        """Test valid branch names."""
        from server import _validate_branch

        assert _validate_branch("main") is None
        assert _validate_branch("feature/new-thing") is None
        assert _validate_branch("release-1.0.0") is None
        assert _validate_branch("hotfix.urgent") is None

    def test_validate_branch_invalid(self):
        """Test invalid branch names."""
        from server import _validate_branch

        assert _validate_branch("") is not None
        assert _validate_branch("a" * 300) is not None

    def test_validate_url_valid(self):
        """Test valid repository URLs."""
        from server import _validate_url

        assert _validate_url("https://github.com/owner/repo.git") is None
        assert _validate_url("https://gitea.example.com/owner/repo") is None
        assert _validate_url("git@github.com:owner/repo.git") is None

    def test_validate_url_invalid(self):
        """Test invalid repository URLs."""
        from server import _validate_url

        assert _validate_url("") is not None
        assert _validate_url("not-a-url") is not None
        assert _validate_url("ftp://invalid.com/repo") is not None


class TestHealthCheck:
    """Tests for health check endpoint."""

    @pytest.mark.asyncio
    async def test_health_check_structure(self):
        """Test health check returns proper structure."""
        from server import health_check

        with (
            patch("server._gitea_provider", None),
            patch("server._workspace_manager", None),
        ):
            result = await health_check()

        assert "status" in result
        assert "service" in result
        assert "version" in result
        assert "timestamp" in result
        assert "dependencies" in result

    @pytest.mark.asyncio
    async def test_health_check_no_providers(self):
        """Test health check without providers configured."""
        from server import health_check

        with (
            patch("server._gitea_provider", None),
            patch("server._workspace_manager", None),
        ):
            result = await health_check()

        assert result["dependencies"]["gitea"] == "not configured"


class TestToolRegistry:
    """Tests for tool registration."""

    def test_tool_registry_populated(self):
        """Test that tools are registered."""
        from server import _tool_registry

        assert len(_tool_registry) > 0
        assert "clone_repository" in _tool_registry
        assert "git_status" in _tool_registry
        assert "create_branch" in _tool_registry
        assert "commit" in _tool_registry

    def test_tool_schema_structure(self):
        """Test tool schemas have proper structure."""
        from server import _tool_registry

        for name, info in _tool_registry.items():
            assert "func" in info
            assert "description" in info
            assert "schema" in info
            assert info["schema"]["type"] == "object"
            assert "properties" in info["schema"]


class TestCloneRepository:
    """Tests for clone_repository tool."""

    @pytest.mark.asyncio
    async def test_clone_invalid_project_id(self):
        """Test clone with invalid project ID."""
        from server import clone_repository

        # Access the underlying function via .fn
        result = await clone_repository.fn(
            project_id="invalid@id",
            agent_id="agent-1",
            repo_url="https://github.com/owner/repo.git",
        )

        assert result["success"] is False
        assert "project_id" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_clone_invalid_repo_url(self):
        """Test clone with invalid repo URL."""
        from server import clone_repository

        result = await clone_repository.fn(
            project_id="valid-project",
            agent_id="agent-1",
            repo_url="not-a-valid-url",
        )

        assert result["success"] is False
        assert "url" in result["error"].lower()


class TestGitStatus:
    """Tests for git_status tool."""

    @pytest.mark.asyncio
    async def test_status_workspace_not_found(self):
        """Test status when workspace doesn't exist."""
        from server import git_status

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await git_status.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False
        assert result["code"] == ErrorCode.WORKSPACE_NOT_FOUND.value


class TestBranchOperations:
    """Tests for branch operation tools."""

    @pytest.mark.asyncio
    async def test_create_branch_invalid_name(self):
        """Test creating branch with invalid name."""
        from server import create_branch

        result = await create_branch.fn(
            project_id="test-project",
            agent_id="agent-1",
            branch_name="",  # Invalid
        )

        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_list_branches_workspace_not_found(self):
        """Test listing branches when workspace doesn't exist."""
        from server import list_branches

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await list_branches.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_checkout_invalid_project(self):
        """Test checkout with invalid project ID."""
        from server import checkout

        result = await checkout.fn(
            project_id="inv@lid",
            agent_id="agent-1",
            ref="main",
        )

        assert result["success"] is False


class TestCommitOperations:
    """Tests for commit operation tools."""

    @pytest.mark.asyncio
    async def test_commit_invalid_project(self):
        """Test commit with invalid project ID."""
        from server import commit

        result = await commit.fn(
            project_id="inv@lid",
            agent_id="agent-1",
            message="Test commit",
        )

        assert result["success"] is False


class TestPushPullOperations:
    """Tests for push/pull operation tools."""

    @pytest.mark.asyncio
    async def test_push_workspace_not_found(self):
        """Test push when workspace doesn't exist."""
        from server import push

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await push.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_pull_workspace_not_found(self):
        """Test pull when workspace doesn't exist."""
        from server import pull

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await pull.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False


class TestDiffLogOperations:
    """Tests for diff and log operation tools."""

    @pytest.mark.asyncio
    async def test_diff_workspace_not_found(self):
        """Test diff when workspace doesn't exist."""
        from server import diff

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await diff.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_log_workspace_not_found(self):
        """Test log when workspace doesn't exist."""
        from server import log

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await log.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False


class TestPROperations:
    """Tests for pull request operation tools."""

    @pytest.mark.asyncio
    async def test_create_pr_no_repo_url(self):
        """Test create PR when workspace has no repo URL."""
        from models import WorkspaceInfo, WorkspaceState
        from server import create_pull_request

        mock_workspace = WorkspaceInfo(
            project_id="test-project",
            path="/tmp/test",
            state=WorkspaceState.READY,
            repo_url=None,  # No repo URL
        )

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=mock_workspace)

        with patch("server._workspace_manager", mock_manager):
            result = await create_pull_request.fn(
                project_id="test-project",
                agent_id="agent-1",
                title="Test PR",
                source_branch="feature",
                target_branch="main",
            )

        assert result["success"] is False
        assert "repository URL" in result["error"]

    @pytest.mark.asyncio
    async def test_list_prs_invalid_state(self):
        """Test list PRs with invalid state filter."""
        from models import WorkspaceInfo, WorkspaceState
        from server import list_pull_requests

        mock_workspace = WorkspaceInfo(
            project_id="test-project",
            path="/tmp/test",
            state=WorkspaceState.READY,
            repo_url="https://gitea.test.com/owner/repo.git",
        )

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=mock_workspace)

        mock_provider = AsyncMock()
        mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))

        with (
            patch("server._workspace_manager", mock_manager),
            patch("server._get_provider_for_url", return_value=mock_provider),
        ):
            result = await list_pull_requests.fn(
                project_id="test-project",
                agent_id="agent-1",
                state="invalid-state",
            )

        assert result["success"] is False
        assert "Invalid state" in result["error"]

    @pytest.mark.asyncio
    async def test_merge_pr_invalid_strategy(self):
        """Test merge PR with invalid strategy."""
        from models import WorkspaceInfo, WorkspaceState
        from server import merge_pull_request

        mock_workspace = WorkspaceInfo(
            project_id="test-project",
            path="/tmp/test",
            state=WorkspaceState.READY,
            repo_url="https://gitea.test.com/owner/repo.git",
        )

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=mock_workspace)

        mock_provider = AsyncMock()
        mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))

        with (
            patch("server._workspace_manager", mock_manager),
            patch("server._get_provider_for_url", return_value=mock_provider),
        ):
            result = await merge_pull_request.fn(
                project_id="test-project",
                agent_id="agent-1",
                pr_number=42,
                merge_strategy="invalid-strategy",
            )

        assert result["success"] is False
        assert "Invalid strategy" in result["error"]


class TestWorkspaceOperations:
    """Tests for workspace operation tools."""

    @pytest.mark.asyncio
    async def test_get_workspace_not_found(self):
        """Test get workspace when it doesn't exist."""
        from server import get_workspace

        mock_manager = AsyncMock()
        mock_manager.get_workspace = AsyncMock(return_value=None)

        with patch("server._workspace_manager", mock_manager):
            result = await get_workspace.fn(
                project_id="nonexistent",
                agent_id="agent-1",
            )

        assert result["success"] is False

    @pytest.mark.asyncio
    async def test_lock_workspace_success(self):
        """Test successful workspace locking."""
        from datetime import UTC, datetime, timedelta

        from models import WorkspaceInfo, WorkspaceState
        from server import lock_workspace

        mock_workspace = WorkspaceInfo(
            project_id="test-project",
            path="/tmp/test",
            state=WorkspaceState.LOCKED,
            lock_holder="agent-1",
            lock_expires=datetime.now(UTC) + timedelta(seconds=300),
        )

        mock_manager = AsyncMock()
        mock_manager.lock_workspace = AsyncMock(return_value=True)
        mock_manager.get_workspace = AsyncMock(return_value=mock_workspace)

        with patch("server._workspace_manager", mock_manager):
            result = await lock_workspace.fn(
                project_id="test-project",
                agent_id="agent-1",
                timeout=300,
            )

        assert result["success"] is True
        assert result["lock_holder"] == "agent-1"

    @pytest.mark.asyncio
    async def test_unlock_workspace_success(self):
        """Test successful workspace unlocking."""
        from server import unlock_workspace

        mock_manager = AsyncMock()
        mock_manager.unlock_workspace = AsyncMock(return_value=True)

        with patch("server._workspace_manager", mock_manager):
            result = await unlock_workspace.fn(
                project_id="test-project",
                agent_id="agent-1",
            )

        assert result["success"] is True


class TestJSONRPCEndpoint:
    """Tests for the JSON-RPC endpoint."""

    def test_python_type_to_json_schema_str(self):
        """Test string type conversion."""
        from server import _python_type_to_json_schema

        result = _python_type_to_json_schema(str)
        assert result["type"] == "string"

    def test_python_type_to_json_schema_int(self):
        """Test int type conversion."""
        from server import _python_type_to_json_schema

        result = _python_type_to_json_schema(int)
        assert result["type"] == "integer"

    def test_python_type_to_json_schema_bool(self):
        """Test bool type conversion."""
        from server import _python_type_to_json_schema

        result = _python_type_to_json_schema(bool)
        assert result["type"] == "boolean"

    def test_python_type_to_json_schema_list(self):
        """Test list type conversion."""

        from server import _python_type_to_json_schema

        result = _python_type_to_json_schema(list[str])
        assert result["type"] == "array"
        assert result["items"]["type"] == "string"
