"""
Tests for Coolify MCP Plugin

Unit tests with mocked HTTP responses.
"""

import json
from unittest.mock import AsyncMock

import pytest

from plugins.coolify.client import CoolifyClient
from plugins.coolify.handlers import applications, deployments, servers
from plugins.coolify.plugin import CoolifyPlugin

# === Fixtures ===


@pytest.fixture
def client():
    """Create a CoolifyClient instance for testing."""
    return CoolifyClient(site_url="https://coolify.test.com", token="test-token-123")


@pytest.fixture
def plugin():
    """Create a CoolifyPlugin instance for testing."""
    config = {"url": "https://coolify.test.com", "token": "test-token-123"}
    return CoolifyPlugin(config, project_id="coolify_test")


# === Client Tests ===


class TestCoolifyClient:
    """Tests for CoolifyClient."""

    def test_init(self, client):
        """Test client initialization."""
        assert client.api_base == "https://coolify.test.com/api/v1"
        assert client.token == "test-token-123"

    def test_headers(self, client):
        """Test Bearer token authentication headers."""
        headers = client._get_headers()
        assert headers["Authorization"] == "Bearer test-token-123"
        assert headers["Content-Type"] == "application/json"
        assert headers["Accept"] == "application/json"

    def test_url_trailing_slash(self):
        """Test URL normalization."""
        client = CoolifyClient(site_url="https://coolify.test.com/", token="t")
        assert client.api_base == "https://coolify.test.com/api/v1"


# === Plugin Tests ===


class TestCoolifyPlugin:
    """Tests for CoolifyPlugin."""

    def test_plugin_name(self):
        """Test plugin name."""
        assert CoolifyPlugin.get_plugin_name() == "coolify"

    def test_required_config_keys(self):
        """Test required config keys."""
        assert CoolifyPlugin.get_required_config_keys() == ["url", "token"]

    def test_missing_config_raises(self):
        """Test that missing config raises ValueError."""
        with pytest.raises(ValueError, match="Missing required"):
            CoolifyPlugin({"url": "https://coolify.test.com"})

    def test_tool_specifications(self):
        """Test that tool specifications are returned."""
        specs = CoolifyPlugin.get_tool_specifications()
        assert len(specs) == 67  # 17 apps + 16 dbs + 5 deploys + 8 projects + 8 servers + 13 svcs

        # Check all specs have required fields
        for spec in specs:
            assert "name" in spec
            assert "method_name" in spec
            assert "description" in spec
            assert "schema" in spec
            assert "scope" in spec
            assert spec["scope"] in ("read", "write", "admin")

    def test_tool_names_unique(self):
        """Test that all tool names are unique."""
        specs = CoolifyPlugin.get_tool_specifications()
        names = [s["name"] for s in specs]
        assert len(names) == len(set(names))

    def test_plugin_init(self, plugin):
        """Test plugin initialization."""
        assert plugin.client is not None
        assert plugin.client.token == "test-token-123"


# === Application Handler Tests ===


class TestApplicationHandlers:
    """Tests for application handler functions."""

    def test_app_tool_count(self):
        """Test application tool specification count."""
        specs = applications.get_tool_specifications()
        assert len(specs) == 17

    @pytest.mark.asyncio
    async def test_list_applications(self, client):
        """Test list_applications handler."""
        mock_apps = [
            {"uuid": "app-1", "name": "test-app", "status": "running"},
            {"uuid": "app-2", "name": "test-app-2", "status": "stopped"},
        ]
        client.list_applications = AsyncMock(return_value=mock_apps)

        result = await applications.list_applications(client)
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 2
        assert len(data["applications"]) == 2
        client.list_applications.assert_called_once_with(tag=None)

    @pytest.mark.asyncio
    async def test_get_application(self, client):
        """Test get_application handler."""
        mock_app = {"uuid": "app-1", "name": "mcphub", "status": "running", "fqdn": "mcp.test.com"}
        client.get_application = AsyncMock(return_value=mock_app)

        result = await applications.get_application(client, uuid="app-1")
        data = json.loads(result)

        assert data["success"] is True
        assert data["application"]["name"] == "mcphub"
        client.get_application.assert_called_once_with("app-1")

    @pytest.mark.asyncio
    async def test_start_application(self, client):
        """Test start_application handler."""
        mock_response = {"message": "Deployment request queued.", "deployment_uuid": "dep-123"}
        client.start_application = AsyncMock(return_value=mock_response)

        result = await applications.start_application(client, uuid="app-1", force=True)
        data = json.loads(result)

        assert data["success"] is True
        assert "deployment queued" in data["message"]
        client.start_application.assert_called_once_with("app-1", force=True, instant_deploy=False)

    @pytest.mark.asyncio
    async def test_stop_application(self, client):
        """Test stop_application handler."""
        mock_response = {"message": "Application stopping request queued."}
        client.stop_application = AsyncMock(return_value=mock_response)

        result = await applications.stop_application(client, uuid="app-1")
        data = json.loads(result)

        assert data["success"] is True
        client.stop_application.assert_called_once_with("app-1", docker_cleanup=True)

    @pytest.mark.asyncio
    async def test_restart_application(self, client):
        """Test restart_application handler."""
        mock_response = {"message": "Restart request queued.", "deployment_uuid": "dep-456"}
        client.restart_application = AsyncMock(return_value=mock_response)

        result = await applications.restart_application(client, uuid="app-1")
        data = json.loads(result)

        assert data["success"] is True
        client.restart_application.assert_called_once_with("app-1")

    @pytest.mark.asyncio
    async def test_get_application_logs(self, client):
        """Test get_application_logs handler."""
        mock_logs = {"logs": "2026-04-02 Starting application..."}
        client.get_application_logs = AsyncMock(return_value=mock_logs)

        result = await applications.get_application_logs(client, uuid="app-1", lines=50)
        data = json.loads(result)

        assert data["success"] is True
        client.get_application_logs.assert_called_once_with("app-1", lines=50)

    @pytest.mark.asyncio
    async def test_list_application_envs(self, client):
        """Test list_application_envs handler."""
        mock_envs = [
            {"key": "DATABASE_URL", "value": "postgres://..."},
            {"key": "SECRET_KEY", "value": "***"},
        ]
        client.list_application_envs = AsyncMock(return_value=mock_envs)

        result = await applications.list_application_envs(client, uuid="app-1")
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 2

    @pytest.mark.asyncio
    async def test_create_application_env(self, client):
        """Test create_application_env handler."""
        mock_response = {"uuid": "env-123"}
        client.create_application_env = AsyncMock(return_value=mock_response)

        result = await applications.create_application_env(
            client, uuid="app-1", key="NEW_VAR", value="test_value"
        )
        data = json.loads(result)

        assert data["success"] is True
        assert "NEW_VAR" in data["message"]

    @pytest.mark.asyncio
    async def test_delete_application_env(self, client):
        """Test delete_application_env handler."""
        client.delete_application_env = AsyncMock(return_value=None)

        result = await applications.delete_application_env(client, uuid="app-1", env_uuid="env-123")
        data = json.loads(result)

        assert data["success"] is True
        client.delete_application_env.assert_called_once_with("app-1", "env-123")

    @pytest.mark.asyncio
    async def test_create_application_public(self, client):
        """Test create_application_public handler."""
        mock_response = {"uuid": "new-app-uuid"}
        client.create_application_public = AsyncMock(return_value=mock_response)

        result = await applications.create_application_public(
            client,
            project_uuid="proj-1",
            server_uuid="srv-1",
            environment_name="production",
            git_repository="https://github.com/test/repo",
            git_branch="main",
            build_pack="nixpacks",
            ports_exposes="3000",
        )
        data = json.loads(result)

        assert data["success"] is True
        assert data["application"]["uuid"] == "new-app-uuid"

    @pytest.mark.asyncio
    async def test_update_application(self, client):
        """Test update_application handler."""
        mock_response = {"uuid": "app-1"}
        client.update_application = AsyncMock(return_value=mock_response)

        result = await applications.update_application(
            client, uuid="app-1", name="new-name", domains="app.test.com"
        )
        data = json.loads(result)

        assert data["success"] is True
        client.update_application.assert_called_once_with(
            "app-1", {"name": "new-name", "domains": "app.test.com"}
        )

    @pytest.mark.asyncio
    async def test_delete_application(self, client):
        """Test delete_application handler."""
        mock_response = {"message": "Application deleted."}
        client.delete_application = AsyncMock(return_value=mock_response)

        result = await applications.delete_application(client, uuid="app-1")
        data = json.loads(result)

        assert data["success"] is True


# === Deployment Handler Tests ===


class TestDeploymentHandlers:
    """Tests for deployment handler functions."""

    def test_deployment_tool_count(self):
        """Test deployment tool specification count."""
        specs = deployments.get_tool_specifications()
        assert len(specs) == 5

    @pytest.mark.asyncio
    async def test_list_deployments(self, client):
        """Test list_deployments handler."""
        mock_deps = [
            {"deployment_uuid": "dep-1", "status": "in_progress"},
            {"deployment_uuid": "dep-2", "status": "queued"},
        ]
        client.list_deployments = AsyncMock(return_value=mock_deps)

        result = await deployments.list_deployments(client)
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 2

    @pytest.mark.asyncio
    async def test_deploy(self, client):
        """Test deploy handler."""
        mock_response = {
            "deployments": [
                {"message": "Deploying", "resource_uuid": "app-1", "deployment_uuid": "dep-1"}
            ]
        }
        client.deploy = AsyncMock(return_value=mock_response)

        result = await deployments.deploy(client, uuid="app-1", force=True)
        data = json.loads(result)

        assert data["success"] is True
        client.deploy.assert_called_once_with(tag=None, uuid="app-1", force=True)

    @pytest.mark.asyncio
    async def test_cancel_deployment(self, client):
        """Test cancel_deployment handler."""
        mock_response = {"message": "Cancelled", "status": "cancelled-by-user"}
        client.cancel_deployment = AsyncMock(return_value=mock_response)

        result = await deployments.cancel_deployment(client, uuid="dep-1")
        data = json.loads(result)

        assert data["success"] is True
        client.cancel_deployment.assert_called_once_with("dep-1")

    @pytest.mark.asyncio
    async def test_list_app_deployments(self, client):
        """Test list_app_deployments handler."""
        mock_deps = [{"deployment_uuid": "dep-1", "status": "finished"}]
        client.list_app_deployments = AsyncMock(return_value=mock_deps)

        result = await deployments.list_app_deployments(client, uuid="app-1", skip=0, take=5)
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 1
        client.list_app_deployments.assert_called_once_with("app-1", skip=0, take=5)


# === Server Handler Tests ===


class TestServerHandlers:
    """Tests for server handler functions."""

    def test_server_tool_count(self):
        """Test server tool specification count."""
        specs = servers.get_tool_specifications()
        assert len(specs) == 8

    @pytest.mark.asyncio
    async def test_list_servers(self, client):
        """Test list_servers handler."""
        mock_servers = [
            {"uuid": "srv-1", "name": "main-server", "ip": "1.2.3.4"},
        ]
        client.list_servers = AsyncMock(return_value=mock_servers)

        result = await servers.list_servers(client)
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 1
        assert data["servers"][0]["name"] == "main-server"

    @pytest.mark.asyncio
    async def test_get_server(self, client):
        """Test get_server handler."""
        mock_server = {
            "uuid": "srv-1",
            "name": "main-server",
            "ip": "1.2.3.4",
            "settings": {"is_reachable": True},
        }
        client.get_server = AsyncMock(return_value=mock_server)

        result = await servers.get_server(client, uuid="srv-1")
        data = json.loads(result)

        assert data["success"] is True
        assert data["server"]["settings"]["is_reachable"] is True

    @pytest.mark.asyncio
    async def test_get_server_resources(self, client):
        """Test get_server_resources handler."""
        mock_resources = [
            {"uuid": "app-1", "name": "mcphub", "type": "application", "status": "running"},
            {"uuid": "db-1", "name": "postgres", "type": "database", "status": "running"},
        ]
        client.get_server_resources = AsyncMock(return_value=mock_resources)

        result = await servers.get_server_resources(client, uuid="srv-1")
        data = json.loads(result)

        assert data["success"] is True
        assert data["count"] == 2

    @pytest.mark.asyncio
    async def test_get_server_domains(self, client):
        """Test get_server_domains handler."""
        mock_domains = [{"ip": "1.2.3.4", "domains": ["mcp.test.com", "blog.test.com"]}]
        client.get_server_domains = AsyncMock(return_value=mock_domains)

        result = await servers.get_server_domains(client, uuid="srv-1")
        data = json.loads(result)

        assert data["success"] is True
        assert len(data["domains"]) == 1

    @pytest.mark.asyncio
    async def test_validate_server(self, client):
        """Test validate_server handler."""
        mock_response = {"message": "Validation started."}
        client.validate_server = AsyncMock(return_value=mock_response)

        result = await servers.validate_server(client, uuid="srv-1")
        data = json.loads(result)

        assert data["success"] is True
        assert "validation started" in data["message"].lower()


# === Health Check Tests ===


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

    @pytest.mark.asyncio
    async def test_health_check_success(self, plugin):
        """Test successful health check."""
        plugin.client.request = AsyncMock(return_value={"version": "4.0.0"})

        result = await plugin.health_check()
        assert result["healthy"] is True

    @pytest.mark.asyncio
    async def test_health_check_failure(self, plugin):
        """Test failed health check."""
        plugin.client.request = AsyncMock(side_effect=Exception("Connection refused"))

        result = await plugin.health_check()
        assert result["healthy"] is False
        assert "Connection refused" in result["message"]
