"""
Comprehensive Test Suite for DSS MCP Commands

Tests all 35 DSS MCP tools across 4 categories:
- DSS Core (10 tools)
- DevTools (12 tools)
- Browser Automation (8 tools)
- Context Compiler (5 tools)

Tests validate:
- Tool definitions and schemas
- Required parameters
- Implementation presence
- Security measures
- Error handling patterns
"""

import pytest
import re
from pathlib import Path

# =============================================================================
# TEST CONFIGURATION
# =============================================================================

# Use relative path from project root
MCP_SERVER_PATH = Path(__file__).parent.parent.parent.parent / "dss-claude-plugin/servers/dss-mcp-server.py"

# Complete tool registry - all 35 MCP tools
DSS_CORE_TOOLS = {
    "dss_analyze_project": {
        "required": ["path"],
        "optional": [],
        "impl_func": "analyze_project"
    },
    "dss_extract_tokens": {
        "required": ["path"],
        "optional": ["sources"],
        "impl_func": "extract_tokens"
    },
    "dss_generate_theme": {
        "required": ["format"],
        "optional": ["tokens", "theme_name"],
        "impl_func": "generate_theme"
    },
    "dss_list_themes": {
        "required": [],
        "optional": [],
        "impl_func": "list_themes"
    },
    "dss_get_status": {
        "required": [],
        "optional": ["format"],
        "impl_func": "get_status"
    },
    "dss_audit_components": {
        "required": ["path"],
        "optional": [],
        "impl_func": "audit_components"
    },
    "dss_setup_storybook": {
        "required": ["path"],
        "optional": ["action"],
        "impl_func": "setup_storybook"
    },
    "dss_sync_figma": {
        "required": ["file_key"],
        "optional": [],
        "impl_func": "sync_figma"
    },
    "dss_find_quick_wins": {
        "required": ["path"],
        "optional": [],
        "impl_func": "find_quick_wins"
    },
    "dss_transform_tokens": {
        "required": ["tokens", "output_format"],
        "optional": ["input_format"],
        "impl_func": "transform_tokens"
    },
}

DEVTOOLS_TOOLS = {
    "devtools_launch": {
        "required": [],
        "optional": ["url", "headless"],
        "impl_func": "devtools_launch_impl"
    },
    "devtools_connect": {
        "required": [],
        "optional": ["port", "host"],
        "impl_func": "devtools_connect_impl"
    },
    "devtools_disconnect": {
        "required": [],
        "optional": [],
        "impl_func": "devtools_disconnect_impl"
    },
    "devtools_list_pages": {
        "required": [],
        "optional": [],
        "impl_func": "devtools_list_pages_impl"
    },
    "devtools_select_page": {
        "required": ["page_id"],
        "optional": [],
        "impl_func": "devtools_select_page_impl"
    },
    "devtools_console_logs": {
        "required": [],
        "optional": ["level", "limit", "clear"],
        "impl_func": "devtools_console_logs_impl"
    },
    "devtools_network_requests": {
        "required": [],
        "optional": ["filter_url", "limit"],
        "impl_func": "devtools_network_requests_impl"
    },
    "devtools_evaluate": {
        "required": ["expression"],
        "optional": [],
        "impl_func": "devtools_evaluate_impl"
    },
    "devtools_query_dom": {
        "required": ["selector"],
        "optional": [],
        "impl_func": "devtools_query_dom_impl"
    },
    "devtools_goto": {
        "required": ["url"],
        "optional": ["wait_until"],
        "impl_func": "devtools_goto_impl"
    },
    "devtools_screenshot": {
        "required": [],
        "optional": ["selector", "full_page"],
        "impl_func": "devtools_screenshot_impl"
    },
    "devtools_performance": {
        "required": [],
        "optional": [],
        "impl_func": "devtools_performance_impl"
    },
}

BROWSER_TOOLS = {
    "browser_init": {
        "required": [],
        "optional": ["mode", "url", "session_id", "headless"],
        "impl_func": "browser_init_impl"
    },
    "browser_get_logs": {
        "required": [],
        "optional": ["level", "limit"],
        "impl_func": "browser_get_logs_impl"
    },
    "browser_screenshot": {
        "required": [],
        "optional": ["selector", "full_page"],
        "impl_func": "browser_screenshot_impl"
    },
    "browser_dom_snapshot": {
        "required": [],
        "optional": [],
        "impl_func": "browser_dom_snapshot_impl"
    },
    "browser_get_errors": {
        "required": [],
        "optional": ["limit"],
        "impl_func": "browser_get_errors_impl"
    },
    "browser_accessibility_audit": {
        "required": [],
        "optional": ["selector"],
        "impl_func": "browser_accessibility_audit_impl"
    },
    "browser_performance": {
        "required": [],
        "optional": [],
        "impl_func": "browser_performance_impl"
    },
    "browser_close": {
        "required": [],
        "optional": [],
        "impl_func": "browser_close_impl"
    },
}

CONTEXT_COMPILER_TOOLS = {
    "dss_get_resolved_context": {
        "required": ["manifest_path"],
        "optional": ["debug", "force_refresh"],
        "impl_func": None  # Handled inline in dispatcher
    },
    "dss_resolve_token": {
        "required": ["manifest_path", "token_path"],
        "optional": ["force_refresh"],
        "impl_func": None
    },
    "dss_validate_manifest": {
        "required": ["manifest_path"],
        "optional": [],
        "impl_func": None
    },
    "dss_list_skins": {
        "required": [],
        "optional": [],
        "impl_func": None
    },
    "dss_get_compiler_status": {
        "required": [],
        "optional": [],
        "impl_func": None
    },
}

ALL_TOOLS = {
    **DSS_CORE_TOOLS,
    **DEVTOOLS_TOOLS,
    **BROWSER_TOOLS,
    **CONTEXT_COMPILER_TOOLS,
}


# =============================================================================
# FIXTURES
# =============================================================================

@pytest.fixture
def mcp_server_content():
    """Load MCP server source code."""
    return MCP_SERVER_PATH.read_text()


# =============================================================================
# TEST CLASS: Tool Definitions
# =============================================================================

class TestToolDefinitions:
    """Verify all 48 tools are properly defined in the MCP server."""

    def test_total_tool_count(self, mcp_server_content):
        """Verify we have exactly 48 tools defined."""
        # Count Tool( occurrences
        tool_definitions = re.findall(r'Tool\(\s*name="([^"]+)"', mcp_server_content)
        assert len(tool_definitions) == 48, f"Expected 48 tools, found {len(tool_definitions)}"

    @pytest.mark.parametrize("tool_name", DSS_CORE_TOOLS.keys())
    def test_dss_core_tool_defined(self, mcp_server_content, tool_name):
        """Verify each DSS core tool is defined."""
        assert f'name="{tool_name}"' in mcp_server_content, f"Tool {tool_name} not found"

    @pytest.mark.parametrize("tool_name", DEVTOOLS_TOOLS.keys())
    def test_devtools_tool_defined(self, mcp_server_content, tool_name):
        """Verify each DevTools tool is defined."""
        assert f'name="{tool_name}"' in mcp_server_content, f"Tool {tool_name} not found"

    @pytest.mark.parametrize("tool_name", BROWSER_TOOLS.keys())
    def test_browser_tool_defined(self, mcp_server_content, tool_name):
        """Verify each Browser automation tool is defined."""
        assert f'name="{tool_name}"' in mcp_server_content, f"Tool {tool_name} not found"

    @pytest.mark.parametrize("tool_name", CONTEXT_COMPILER_TOOLS.keys())
    def test_context_compiler_tool_defined(self, mcp_server_content, tool_name):
        """Verify each Context Compiler tool is defined."""
        assert f'name="{tool_name}"' in mcp_server_content, f"Tool {tool_name} not found"


# =============================================================================
# TEST CLASS: Tool Dispatcher
# =============================================================================

class TestToolDispatcher:
    """Verify tool dispatcher handles all tools."""

    @pytest.mark.parametrize("tool_name", ALL_TOOLS.keys())
    def test_tool_in_dispatcher(self, mcp_server_content, tool_name):
        """Verify each tool has a dispatcher case."""
        # Check for: elif name == "tool_name" or if name == "tool_name"
        pattern = rf'(if|elif)\s+name\s*==\s*"{tool_name}"'
        assert re.search(pattern, mcp_server_content), f"Tool {tool_name} not in dispatcher"


# =============================================================================
# TEST CLASS: Implementation Functions
# =============================================================================

class TestImplementationFunctions:
    """Verify implementation functions exist."""

    @pytest.mark.parametrize("tool_name,config", [
        (k, v) for k, v in DSS_CORE_TOOLS.items() if v["impl_func"]
    ])
    def test_dss_core_impl_exists(self, mcp_server_content, tool_name, config):
        """Verify DSS core tool implementations exist."""
        impl_func = config["impl_func"]
        pattern = rf'async def {impl_func}\('
        assert re.search(pattern, mcp_server_content), f"Implementation {impl_func} not found for {tool_name}"

    @pytest.mark.parametrize("tool_name,config", [
        (k, v) for k, v in DEVTOOLS_TOOLS.items() if v["impl_func"]
    ])
    def test_devtools_impl_exists(self, mcp_server_content, tool_name, config):
        """Verify DevTools implementations exist."""
        impl_func = config["impl_func"]
        pattern = rf'async def {impl_func}\('
        assert re.search(pattern, mcp_server_content), f"Implementation {impl_func} not found for {tool_name}"

    @pytest.mark.parametrize("tool_name,config", [
        (k, v) for k, v in BROWSER_TOOLS.items() if v["impl_func"]
    ])
    def test_browser_impl_exists(self, mcp_server_content, tool_name, config):
        """Verify Browser tool implementations exist."""
        impl_func = config["impl_func"]
        pattern = rf'async def {impl_func}\('
        assert re.search(pattern, mcp_server_content), f"Implementation {impl_func} not found for {tool_name}"


# =============================================================================
# TEST CLASS: Input Schemas
# =============================================================================

class TestInputSchemas:
    """Verify input schemas are properly defined."""

    def test_all_tools_have_input_schema(self, mcp_server_content):
        """Verify all tools have inputSchema defined."""
        tool_definitions = re.findall(r'Tool\(\s*name="([^"]+)"', mcp_server_content)
        for tool in tool_definitions:
            # Find Tool definition and check for inputSchema
            pattern = rf'name="{tool}".*?inputSchema'
            assert re.search(pattern, mcp_server_content, re.DOTALL), f"Tool {tool} missing inputSchema"

    @pytest.mark.parametrize("tool_name,config", list(ALL_TOOLS.items()))
    def test_required_params_in_schema(self, mcp_server_content, tool_name, config):
        """Verify required parameters are marked in schema."""
        if not config["required"]:
            return  # Skip tools with no required params

        # Find the tool's schema section
        tool_pattern = rf'name="{tool_name}".*?inputSchema=\{{(.*?)\}}\s*\)'
        match = re.search(tool_pattern, mcp_server_content, re.DOTALL)
        if match:
            schema_content = match.group(1)
            # Check for "required": [...] with our params
            for param in config["required"]:
                # The param should appear in the required array or properties
                assert param in schema_content, f"Required param '{param}' not in schema for {tool_name}"


# =============================================================================
# TEST CLASS: Security Measures
# =============================================================================

class TestSecurityMeasures:
    """Verify security measures are in place."""

    def test_audit_logging_for_evaluate(self, mcp_server_content):
        """Verify devtools_evaluate has audit logging."""
        # Check for AUDIT log in devtools_evaluate_impl
        pattern = r'def devtools_evaluate_impl.*?\[AUDIT\]'
        assert re.search(pattern, mcp_server_content, re.DOTALL), "devtools_evaluate missing audit logging"

    def test_playwright_availability_check(self, mcp_server_content):
        """Verify Playwright availability is checked before DevTools operations."""
        assert "PLAYWRIGHT_AVAILABLE" in mcp_server_content, "Missing Playwright availability check"
        assert 'not PLAYWRIGHT_AVAILABLE and name.startswith("devtools_")' in mcp_server_content

    def test_dss_availability_check(self, mcp_server_content):
        """Verify DSS availability is checked before DSS operations."""
        assert "DSS_AVAILABLE" in mcp_server_content, "Missing DSS availability check"
        assert 'not DSS_AVAILABLE and name.startswith("dss_")' in mcp_server_content

    def test_context_compiler_availability_check(self, mcp_server_content):
        """Verify Context Compiler availability is checked."""
        assert "CONTEXT_COMPILER_AVAILABLE" in mcp_server_content, "Missing Context Compiler availability check"

    def test_figma_token_validation(self, mcp_server_content):
        """Verify Figma sync checks for API token."""
        assert 'FIGMA_TOKEN' in mcp_server_content, "Missing Figma token check"
        # Should return error if token not configured
        assert 'FIGMA_TOKEN not configured' in mcp_server_content

    def test_path_validation(self, mcp_server_content):
        """Verify path validation is performed."""
        # Check that Path.resolve() is used for path inputs
        assert "Path(path).resolve()" in mcp_server_content, "Missing path resolution"
        # Check for existence validation
        assert "not project_path.exists()" in mcp_server_content or "not target_path.exists()" in mcp_server_content


# =============================================================================
# TEST CLASS: Async/Timeout Handling
# =============================================================================

class TestAsyncHandling:
    """Verify async operations are properly handled."""

    def test_timeout_decorator_exists(self, mcp_server_content):
        """Verify timeout decorator is defined."""
        assert "def with_timeout" in mcp_server_content, "Missing timeout decorator"

    def test_timeout_config_exists(self, mcp_server_content):
        """Verify timeout configuration is defined."""
        assert "TIMEOUT_CONFIG" in mcp_server_content, "Missing timeout configuration"
        # Check for expected timeout keys
        expected_keys = ["analyze", "extract", "generate", "figma_api", "storybook", "devtools_connect"]
        for key in expected_keys:
            assert f'"{key}"' in mcp_server_content, f"Missing timeout key: {key}"

    def test_devtools_timeout_applied(self, mcp_server_content):
        """Verify DevTools operations have timeouts."""
        # Check for @with_timeout decorator on critical functions
        assert '@with_timeout("devtools_connect")' in mcp_server_content

    def test_run_in_executor_usage(self, mcp_server_content):
        """Verify blocking operations use run_in_executor."""
        assert "loop.run_in_executor" in mcp_server_content, "Missing run_in_executor for blocking operations"


# =============================================================================
# TEST CLASS: State Management
# =============================================================================

class TestStateManagement:
    """Verify state management classes are properly defined."""

    def test_devtools_state_class(self, mcp_server_content):
        """Verify DevToolsState dataclass is defined."""
        assert "class DevToolsState:" in mcp_server_content
        assert "@dataclass" in mcp_server_content

    def test_browser_automation_state_class(self, mcp_server_content):
        """Verify BrowserAutomationState dataclass is defined."""
        assert "class BrowserAutomationState:" in mcp_server_content

    def test_devtools_state_instance(self, mcp_server_content):
        """Verify DevTools state instance is created."""
        assert "devtools = DevToolsState()" in mcp_server_content

    def test_browser_state_instance(self, mcp_server_content):
        """Verify Browser state instance is created."""
        assert "browser_state = BrowserAutomationState()" in mcp_server_content

    def test_bounded_buffers(self, mcp_server_content):
        """Verify bounded deques are used for log capture."""
        assert "deque(maxlen=" in mcp_server_content, "Missing bounded deque for log capture"
        assert "DEVTOOLS_CONSOLE_MAX_ENTRIES" in mcp_server_content
        assert "DEVTOOLS_NETWORK_MAX_ENTRIES" in mcp_server_content


# =============================================================================
# TEST CLASS: Error Handling
# =============================================================================

class TestErrorHandling:
    """Verify error handling patterns."""

    def test_try_except_in_dispatcher(self, mcp_server_content):
        """Verify dispatcher has error handling."""
        assert "except Exception as e:" in mcp_server_content
        assert '"error":' in mcp_server_content or "'error':" in mcp_server_content

    def test_safe_serialize_function(self, mcp_server_content):
        """Verify safe_serialize function exists for JSON serialization."""
        assert "def safe_serialize" in mcp_server_content

    def test_import_error_handling(self, mcp_server_content):
        """Verify import errors are captured."""
        assert "except ImportError" in mcp_server_content
        assert "DSS_IMPORT_ERROR" in mcp_server_content
        assert "CONTEXT_COMPILER_IMPORT_ERROR" in mcp_server_content


# =============================================================================
# TEST CLASS: Browser Automation Modes
# =============================================================================

class TestBrowserAutomationModes:
    """Verify Browser automation supports LOCAL and REMOTE modes."""

    def test_local_mode_support(self, mcp_server_content):
        """Verify LOCAL mode is supported."""
        assert 'mode == "local"' in mcp_server_content
        assert "LocalBrowserStrategy" in mcp_server_content

    def test_remote_mode_support(self, mcp_server_content):
        """Verify REMOTE mode is supported."""
        assert 'mode == "remote"' in mcp_server_content
        assert "remote_api_url" in mcp_server_content
        assert "session_id" in mcp_server_content

    def test_aiohttp_for_remote(self, mcp_server_content):
        """Verify aiohttp is used for remote API calls."""
        assert "import aiohttp" in mcp_server_content
        assert "aiohttp.ClientSession()" in mcp_server_content


# =============================================================================
# TEST CLASS: Server Configuration
# =============================================================================

class TestServerConfiguration:
    """Verify server is properly configured."""

    def test_mcp_server_created(self, mcp_server_content):
        """Verify MCP server instance is created."""
        assert 'server = Server("dss-server")' in mcp_server_content

    def test_list_tools_decorator(self, mcp_server_content):
        """Verify list_tools is registered."""
        assert "@server.list_tools()" in mcp_server_content

    def test_call_tool_decorator(self, mcp_server_content):
        """Verify call_tool is registered."""
        assert "@server.call_tool()" in mcp_server_content

    def test_main_function(self, mcp_server_content):
        """Verify main function exists."""
        assert "async def main():" in mcp_server_content
        assert 'if __name__ == "__main__":' in mcp_server_content

    def test_stdio_server_usage(self, mcp_server_content):
        """Verify stdio_server is used for transport."""
        assert "stdio_server" in mcp_server_content
        assert "async with stdio_server()" in mcp_server_content


# =============================================================================
# TEST CLASS: Cleanup Handling
# =============================================================================

class TestCleanupHandling:
    """Verify cleanup is properly handled."""

    def test_disconnect_cleanup(self, mcp_server_content):
        """Verify DevTools disconnect cleans up properly."""
        # Should reset state
        assert "devtools = DevToolsState()" in mcp_server_content
        # Should remove event listeners
        assert "remove_listener" in mcp_server_content

    def test_browser_close_cleanup(self, mcp_server_content):
        """Verify browser close cleans up properly."""
        assert "browser_state = BrowserAutomationState()" in mcp_server_content

    def test_main_finally_cleanup(self, mcp_server_content):
        """Verify main function has cleanup in finally block."""
        # Check for cleanup on server shutdown
        assert "finally:" in mcp_server_content
        assert "devtools_disconnect_impl()" in mcp_server_content
        assert "browser_close_impl()" in mcp_server_content


# =============================================================================
# TEST CLASS: Category Counts
# =============================================================================

class TestCategoryCounts:
    """Verify tool counts per category."""

    def test_dss_core_count(self):
        """Verify DSS core has 10 tools."""
        assert len(DSS_CORE_TOOLS) == 10, f"Expected 10 DSS core tools, got {len(DSS_CORE_TOOLS)}"

    def test_devtools_count(self):
        """Verify DevTools has 12 tools."""
        assert len(DEVTOOLS_TOOLS) == 12, f"Expected 12 DevTools tools, got {len(DEVTOOLS_TOOLS)}"

    def test_browser_count(self):
        """Verify Browser automation has 8 tools."""
        assert len(BROWSER_TOOLS) == 8, f"Expected 8 Browser tools, got {len(BROWSER_TOOLS)}"

    def test_context_compiler_count(self):
        """Verify Context Compiler has 5 tools."""
        assert len(CONTEXT_COMPILER_TOOLS) == 5, f"Expected 5 Context Compiler tools, got {len(CONTEXT_COMPILER_TOOLS)}"

    def test_total_count(self):
        """Verify total registered tools in test suite (subset of all 48 server tools)."""
        total = len(DSS_CORE_TOOLS) + len(DEVTOOLS_TOOLS) + len(BROWSER_TOOLS) + len(CONTEXT_COMPILER_TOOLS)
        assert total == 35, f"Expected 35 registered test tools, got {total}"


# =============================================================================
# TEST CLASS: DSS Core Functionality
# =============================================================================

class TestDSSCoreFunctionality:
    """Test DSS core tool specific requirements."""

    def test_project_scanner_usage(self, mcp_server_content):
        """Verify ProjectScanner is used for analysis."""
        assert "ProjectScanner" in mcp_server_content

    def test_react_analyzer_usage(self, mcp_server_content):
        """Verify ReactAnalyzer is used for component analysis."""
        assert "ReactAnalyzer" in mcp_server_content

    def test_style_analyzer_usage(self, mcp_server_content):
        """Verify StyleAnalyzer is used for style analysis."""
        assert "StyleAnalyzer" in mcp_server_content

    def test_token_sources(self, mcp_server_content):
        """Verify all token sources are available."""
        sources = ["CSSTokenSource", "SCSSTokenSource", "TailwindTokenSource", "JSONTokenSource"]
        for source in sources:
            assert source in mcp_server_content, f"Missing token source: {source}"

    def test_token_merger_usage(self, mcp_server_content):
        """Verify TokenMerger is used for combining tokens."""
        assert "TokenMerger" in mcp_server_content
        assert "MergeStrategy" in mcp_server_content

    def test_storybook_support(self, mcp_server_content):
        """Verify Storybook classes are used."""
        classes = ["StorybookScanner", "StoryGenerator", "ThemeGenerator"]
        for cls in classes:
            assert cls in mcp_server_content, f"Missing Storybook class: {cls}"


# =============================================================================
# TEST CLASS: DevTools Functionality
# =============================================================================

class TestDevToolsFunctionality:
    """Test DevTools-specific requirements."""

    def test_console_handler(self, mcp_server_content):
        """Verify console message handler exists."""
        assert "async def _on_console" in mcp_server_content

    def test_request_handler(self, mcp_server_content):
        """Verify network request handler exists."""
        assert "async def _on_request" in mcp_server_content

    def test_get_active_page_helper(self, mcp_server_content):
        """Verify _get_active_page helper exists."""
        assert "def _get_active_page" in mcp_server_content

    def test_cdp_connection(self, mcp_server_content):
        """Verify CDP connection method is used."""
        assert "connect_over_cdp" in mcp_server_content

    def test_playwright_launch(self, mcp_server_content):
        """Verify Playwright launch for headless mode."""
        assert "chromium.launch" in mcp_server_content
        assert "--no-sandbox" in mcp_server_content  # Required for Docker


# =============================================================================
# RUN TESTS
# =============================================================================

if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
