"""Tests for complyform MCP server."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest

from complyform.cli.mcp_server import (
    _TOOL_HANDLERS,
    _error_json,
    _handle_assess,
    _handle_config_status,
    _handle_doctor,
    _handle_explain,
    _handle_frameworks_diff,
    _handle_frameworks_info,
    _handle_frameworks_list,
    _handle_scan,
    _success_json,
)


class TestHelpers:
    def test_error_json(self) -> None:
        result = json.loads(
            _error_json(code=42, title="Test", message="msg", fix="fix it")
        )
        assert result["error"] is True
        assert result["code"] == 42
        assert result["title"] == "Test"
        assert result["message"] == "msg"
        assert result["fix"] == "fix it"

    def test_success_json(self) -> None:
        result = json.loads(_success_json({"key": "value"}))
        assert result["key"] == "value"

    def test_success_json_with_non_serializable(self) -> None:
        from pathlib import Path

        result = json.loads(_success_json({"path": Path("/tmp")}))
        assert result["path"] == "/tmp"


class TestToolRegistration:
    def test_eight_tools_registered(self) -> None:
        assert len(_TOOL_HANDLERS) == 8

    def test_all_tool_names(self) -> None:
        expected = {
            "complyform_scan",
            "complyform_assess",
            "complyform_frameworks_list",
            "complyform_frameworks_info",
            "complyform_frameworks_diff",
            "complyform_explain",
            "complyform_doctor",
            "complyform_config_status",
        }
        assert set(_TOOL_HANDLERS.keys()) == expected


class TestCreateServer:
    def test_create_server(self) -> None:
        """Verify _create_server is callable (requires mcp SDK)."""
        try:
            from complyform.cli.mcp_server import _create_server

            server = _create_server()
            assert server is not None
        except ImportError:
            pytest.skip("mcp SDK not installed")


class TestHandleScan:
    @pytest.mark.asyncio
    @patch("complyform.core.scanner.state_parser.StateParser")
    async def test_scan(self, mock_parser_cls: MagicMock) -> None:
        mock_resource = MagicMock()
        mock_resource.model_dump.return_value = {
            "provider": "google",
            "resource_type": "google_storage_bucket",
            "name": "my-bucket",
            "address": "google_storage_bucket.my_bucket",
            "attributes": {},
            "module": None,
            "source_state": "terraform.tfstate",
        }
        mock_parser_cls.return_value.parse.return_value = ([mock_resource], MagicMock())

        result = json.loads(
            await _handle_scan({"state_path": "/tmp/terraform.tfstate"})
        )
        assert result["resource_count"] == 1
        assert result["cloud"] == "gcp"


class TestHandleAssess:
    @pytest.mark.asyncio
    @patch("complyform.core.profiles.cross_mappings.get_all", return_value=[])
    @patch("complyform.core.profiles.mapping_loader.load_mappings")
    @patch("complyform.core.assessor.control_matcher.ControlMatcher")
    @patch("complyform.core.scanner.state_parser.StateParser")
    async def test_assess(
        self,
        mock_parser: MagicMock,
        mock_matcher_cls: MagicMock,
        mock_load_mappings: MagicMock,
        mock_get_all: MagicMock,
    ) -> None:
        mock_parser.return_value.parse.return_value = ([], MagicMock())
        mock_finding = MagicMock()
        mock_finding.model_dump.return_value = {
            "control_id": "CC6.1",
            "framework": "soc2",
            "status": "FAIL",
        }
        mock_matcher_cls.return_value.match.return_value = [mock_finding]

        result = json.loads(
            await _handle_assess(
                {
                    "state_path": "/tmp/terraform.tfstate",
                    "frameworks": ["soc2"],
                }
            )
        )
        assert result["finding_count"] == 1
        assert result["frameworks"] == ["soc2"]


class TestHandleFrameworksList:
    @pytest.mark.asyncio
    @patch("complyform.core.profiles.registry.list_frameworks")
    async def test_list(self, mock_list: MagicMock) -> None:
        mock_list.return_value = [
            {"id": "soc2", "name": "SOC 2"},
            {"id": "hipaa", "name": "HIPAA"},
        ]
        result = json.loads(await _handle_frameworks_list({}))
        assert result["framework_count"] == 2


class TestHandleFrameworksInfo:
    @pytest.mark.asyncio
    @patch("complyform.core.profiles.registry.get_framework")
    async def test_info(self, mock_get: MagicMock) -> None:
        mock_get.return_value = {
            "id": "soc2",
            "name": "SOC 2",
            "controls": 50,
        }
        result = json.loads(await _handle_frameworks_info({"framework": "soc2"}))
        assert result["id"] == "soc2"


class TestHandleFrameworksDiff:
    @pytest.mark.asyncio
    @patch("complyform.core.profiles.cross_mappings.lookup")
    async def test_diff(self, mock_lookup: MagicMock) -> None:
        mock_lookup.return_value = [{"control_a": "CC6.1", "control_b": "A.8.24"}]
        result = json.loads(
            await _handle_frameworks_diff(
                {"framework_a": "soc2", "framework_b": "iso27001"}
            )
        )
        assert result["framework_a"] == "soc2"
        assert result["framework_b"] == "iso27001"


class TestHandleExplain:
    @pytest.mark.asyncio
    @patch("complyform.core.explainer.deterministic.explain_finding")
    async def test_explain(self, mock_explain: MagicMock) -> None:
        mock_result = MagicMock()
        mock_result.model_dump.return_value = {
            "control_id": "CC6.1",
            "explanation": "This control requires...",
        }
        mock_explain.return_value = mock_result
        result = json.loads(
            await _handle_explain({"target": "CC6.1", "framework": "soc2"})
        )
        assert result["control_id"] == "CC6.1"


class TestHandleDoctor:
    @pytest.mark.asyncio
    @patch(
        "complyform.cli.doctor_cmd.build_diagnostic_string",
        return_value="diag",
    )
    @patch("complyform.cli.doctor_cmd.run_doctor_checks")
    async def test_doctor(self, mock_checks: MagicMock, mock_diag: MagicMock) -> None:
        mock_result = MagicMock()
        mock_result.checks = [
            MagicMock(
                number=1,
                label="Python 3.14",
                status="pass",
                message="ok",
                fix=None,
            )
        ]
        mock_result.passed = 1
        mock_result.errors = 0
        mock_result.warnings = 0
        mock_result.info = 0
        mock_checks.return_value = mock_result

        result = json.loads(await _handle_doctor({}))
        assert result["summary"]["passed"] == 1
        assert result["diagnostic_string"] == "diag"


class TestHandleConfigStatus:
    @pytest.mark.asyncio
    @patch("complyform.core.auth.license.LicenseManager")
    async def test_status_no_license(self, mock_mgr_cls: MagicMock) -> None:
        mock_mgr_cls.return_value.load_cache.return_value = None
        result = json.loads(await _handle_config_status({}))
        assert result["tier"] == "community"

    @pytest.mark.asyncio
    @patch("complyform.core.auth.license.LicenseManager")
    async def test_status_with_license(self, mock_mgr_cls: MagicMock) -> None:
        cache = MagicMock()
        cache.tier = "team"
        cache.expires_at = "2027-02-25"
        mock_mgr_cls.return_value.load_cache.return_value = cache
        result = json.loads(await _handle_config_status({}))
        assert result["tier"] == "team"

    @pytest.mark.asyncio
    @patch("complyform.core.auth.license.LicenseManager")
    async def test_status_verbose(self, mock_mgr_cls: MagicMock) -> None:
        mock_mgr_cls.return_value.load_cache.return_value = None
        result = json.loads(await _handle_config_status({"verbose": True}))
        assert "features" in result
        assert "scan_sources" in result


class TestErrorHandling:
    @pytest.mark.asyncio
    async def test_complyform_error_raises(self) -> None:
        """ComplyFormError raised in handler propagates (caught by call_tool)."""
        from complyform.core.errors import ComplyFormError

        with (
            patch(
                "complyform.core.scanner.state_parser.StateParser",
                side_effect=ComplyFormError(
                    title="State Error",
                    message="File not found",
                    fix="Check the path",
                    error_code=10,
                ),
            ),
            pytest.raises(ComplyFormError),
        ):
            await _handle_scan({"state_path": "/nonexistent"})

    @pytest.mark.asyncio
    async def test_unexpected_error_raises(self) -> None:
        """Unexpected exceptions propagate (caught by call_tool)."""
        with (
            patch(
                "complyform.core.scanner.state_parser.StateParser",
                side_effect=RuntimeError("something broke"),
            ),
            pytest.raises(RuntimeError, match="something broke"),
        ):
            await _handle_scan({"state_path": "/nonexistent"})

    def test_error_json_formats_complyform_error(self) -> None:
        """Verify error_json produces correct JSON for ComplyFormError."""
        result = json.loads(
            _error_json(
                code=10,
                title="State Error",
                message="File not found",
                fix="Check the path",
            )
        )
        assert result["error"] is True
        assert result["code"] == 10
        assert result["title"] == "State Error"

    def test_error_json_formats_unexpected_error(self) -> None:
        """Verify error_json produces correct JSON for unexpected errors."""
        result = json.loads(
            _error_json(
                title="Unexpected error",
                message="something broke",
            )
        )
        assert result["error"] is True
        assert result["title"] == "Unexpected error"
