#!/usr/bin/env python3
"""
AgentNEX MCP Server - Refactored Version

This MCP server provides AI assistants with secure access to AgentNEX device management,
telemetry data, and system actions through the Model Context Protocol.

Refactored with proper separation of concerns and modular architecture.
"""

import asyncio
import json
import logging
from typing import Any, Sequence

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource

from app.client.backend_client import BackendClient
from app.core.config import settings
from app.core.exceptions import (
    BackendConnectionError,
    AuthenticationError,
    DeviceNotFoundError,
    ActionValidationError,
    MCPServerError
)

# Import all tools
from app.tools import (
    GetDeviceTelemetryTool,
    GetDeviceProcessesTool,
    ListDevicesTool,
    RestartProcessTool,
    KillProcessTool,
    ClearCacheTool,
    FlushDnsTool,
    RestartServiceTool
)

# Import all resources
from app.resources import (
    AllDevicesResource,
    DeviceStatusResource,
    DeviceTelemetryResource
)

# Configure logging - send to stderr to avoid interfering with stdio
logging.basicConfig(
    level=logging.WARNING,
    format='%(levelname)s:%(name)s:%(message)s',
    stream=__import__('sys').stderr
)
logger = logging.getLogger(__name__)

# Create MCP server instance
app = Server("agentnex-server")

# Initialize backend client
backend_client = BackendClient()

# Initialize all tools
tools = [
    ListDevicesTool(backend_client),
    GetDeviceTelemetryTool(backend_client),
    GetDeviceProcessesTool(backend_client),
    RestartProcessTool(backend_client),
    KillProcessTool(backend_client),
    ClearCacheTool(backend_client),
    FlushDnsTool(backend_client),
    RestartServiceTool(backend_client)
]

# Initialize all resources
resources = [
    AllDevicesResource(backend_client),
    DeviceStatusResource(backend_client),
    DeviceTelemetryResource(backend_client)
]

# Create tool lookup dictionary
tool_lookup = {tool.name: tool for tool in tools}

# Create resource lookup dictionary  
resource_lookup = {resource.uri: resource for resource in resources}


@app.list_tools()
async def list_tools() -> list[Tool]:
    """List available MCP tools for accessing AgentNEX device management."""
    return [tool.to_mcp_tool() for tool in tools]


@app.call_tool()
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
    """Execute MCP tools by calling AgentNEX Backend APIs."""
    
    try:
        # Look up the tool
        if name not in tool_lookup:
            available_tools = ", ".join(tool_lookup.keys())
            return [TextContent(
                type="text", 
                text=f"Unknown tool: {name}. Available tools: {available_tools}"
            )]
        
        # Execute the tool (tools return str; wrap in one TextContent for MCP)
        tool = tool_lookup[name]
        result = await tool.execute(arguments)
        if isinstance(result, str):
            return [TextContent(type="text", text=result)]
        return result
    
    except DeviceNotFoundError as e:
        return [TextContent(type="text", text=f"Device Error: {str(e)}")]
    except AuthenticationError as e:
        return [TextContent(type="text", text=f"Authentication Error: {str(e)}")]
    except ActionValidationError as e:
        return [TextContent(type="text", text=f"Action Blocked: {str(e)}")]
    except BackendConnectionError as e:
        return [TextContent(type="text", text=f"Connection Error: {str(e)}")]
    except Exception as e:
        logger.error(f"Tool execution error: {e}")
        return [TextContent(type="text", text=f"Execution Error: {str(e)}")]


@app.list_resources()
async def list_resources() -> list[Resource]:
    """List available MCP resources for accessing AgentNEX data."""
    return [resource.to_mcp_resource() for resource in resources]


@app.read_resource()
async def read_resource(uri: str) -> str:
    """Read MCP resources by URI."""
    # MCP/Inspector may pass URI as AnyUrl; coerce to str for .split() and comparisons
    uri = str(uri)

    try:
        # Handle parameterized URIs by finding matching resource
        matching_resource = None

        for resource in resources:
            resource_pattern = resource.uri
            
            # Handle exact matches
            if uri == resource_pattern:
                matching_resource = resource
                break
            
            # Handle parameterized URIs (e.g., device/{device_id}/status)
            if "{device_id}" in resource_pattern:
                pattern_parts = resource_pattern.split("/")
                uri_parts = uri.split("/")
                
                if len(pattern_parts) == len(uri_parts):
                    # Check if all non-parameter parts match
                    matches = True
                    for i, (pattern_part, uri_part) in enumerate(zip(pattern_parts, uri_parts)):
                        if pattern_part != uri_part and not pattern_part.startswith("{"):
                            matches = False
                            break
                    
                    if matches:
                        matching_resource = resource
                        break
        
        if matching_resource:
            return await matching_resource.read(uri)
        else:
            available_uris = [resource.uri for resource in resources]
            raise ValueError(f"Unknown resource URI: {uri}. Available: {available_uris}")
    
    except Exception as e:
        logger.error(f"Resource read error: {e}")
        return json.dumps({"error": str(e)}, indent=2)


async def main():
    """Main entry point for the MCP server."""
    logger.info("Starting AgentNEX MCP Server (Refactored)...")
    
    try:
        # Skip backend health check at startup - tools validate devices individually when called
        # if not await backend_client.health_check():
        #     logger.warning("Backend health check failed - server may not be available")
        logger.info("Skipping backend health check - tools will validate devices individually")
        
        # Log tool and resource counts
        logger.info(f"Loaded {len(tools)} tools and {len(resources)} resources")
        
        # Start MCP server with stdio transport
        async with stdio_server() as (read_stream, write_stream):
            await app.run(
                read_stream, 
                write_stream, 
                app.create_initialization_options()
            )
    
    except Exception as e:
        logger.error(f"MCP Server startup failed: {e}")
        raise


def cli():
    """CLI entry point for the MCP server package."""
    asyncio.run(main())


if __name__ == "__main__":
    cli()