#!/usr/bin/env python3
"""
HTTP/HTTPS MCP Server for AgentNEX

This exposes the MCP server functionality over HTTP/HTTPS using FastAPI.
NEXA platform connects to this server via REST API endpoints.

Endpoints:
- GET  /mcp/tools         - List available tools
- POST /mcp/tools/call    - Execute a tool
- GET  /mcp/resources     - List available resources
- POST /mcp/resources/read - Read a resource
- GET  /health            - Health check
"""

import asyncio
import json
import logging
from typing import Any, Optional
from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException, Header, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

from app.client.backend_client import BackendClient
from app.core.config import settings

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

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

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


# Request models
class ToolCallRequest(BaseModel):
    name: str
    arguments: dict[str, Any]


class ResourceReadRequest(BaseModel):
    uri: str


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

resources = [
    AllDevicesResource(),
    DeviceStatusResource(),
    DeviceTelemetryResource()
]

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

# Global backend client
backend_client: Optional[BackendClient] = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Lifespan context manager for FastAPI."""
    global backend_client
    logger.info("Starting AgentNEX HTTP MCP Server...")
    backend_client = BackendClient()
    
    # Test backend connection
    try:
        healthy = await backend_client.health_check()
        if healthy:
            logger.info("Backend connection: OK")
        else:
            logger.warning("Backend connection: FAILED (server may not be available)")
    except Exception as e:
        logger.warning(f"Backend connection test failed: {e}")
    
    yield
    
    logger.info("Shutting down AgentNEX HTTP MCP Server...")


# Create FastAPI app
app = FastAPI(
    title="AgentNEX MCP Server",
    description="HTTP/HTTPS interface for AgentNEX MCP tools and resources. Provides device management, telemetry, and action execution capabilities for NEXA platform integration.",
    version="1.0.0",
    lifespan=lifespan
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, restrict to specific origins
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


def get_api_key_from_header(request: Request) -> Optional[str]:
    """Extract API key from request headers."""
    # Check Authorization header (Bearer token)
    auth_header = request.headers.get("Authorization", "")
    if auth_header.startswith("Bearer "):
        return auth_header[7:]
    
    # Check X-API-Key header
    api_key = request.headers.get("X-API-Key")
    if api_key:
        return api_key
    
    # Check X-AgentNEX-API-Key header
    api_key = request.headers.get("X-AgentNEX-API-Key")
    if api_key:
        return api_key
    
    return None


@app.get("/")
async def root():
    """Root endpoint - service info."""
    return {
        "service": "AgentNEX MCP Server",
        "status": "running",
        "version": "1.0.0",
        "description": "Device management and monitoring tools for NEXA platform"
    }


@app.get("/health")
async def health():
    """Health check endpoint."""
    backend_healthy = False
    if backend_client:
        try:
            backend_healthy = await backend_client.health_check()
        except:
            pass
    
    return {
        "status": "healthy",
        "backend_connected": backend_healthy
    }


@app.get("/mcp/tools")
async def list_tools(
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
    authorization: Optional[str] = Header(None)
):
    """List available MCP tools."""
    try:
        tools_list = []
        for tool in tools:
            tools_list.append({
                "name": tool.name,
                "description": tool.description,
                "inputSchema": tool.input_schema
            })
        
        return {"tools": tools_list}
    except Exception as e:
        logger.error(f"Error listing tools: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/mcp/tools/call")
async def call_tool(
    tool_request: ToolCallRequest,
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
    authorization: Optional[str] = Header(None)
):
    """Call an MCP tool."""
    try:
        # Get tool
        tool = tool_map.get(tool_request.name)
        if not tool:
            raise HTTPException(
                status_code=404, 
                detail=f"Unknown tool: {tool_request.name}. Available tools: {list(tool_map.keys())}"
            )
        
        # Execute tool
        logger.info(f"Executing tool: {tool_request.name} with args: {tool_request.arguments}")
        result = await tool.execute(tool_request.arguments, backend_client)
        
        # Parse JSON result if it's a JSON string, otherwise return as text
        try:
            parsed_result = json.loads(result)
            return {
                "tool": tool_request.name,
                "content": parsed_result
            }
        except (json.JSONDecodeError, TypeError):
            # Not JSON, return as text
            return {
                "tool": tool_request.name,
                "content": [{"type": "text", "text": result}]
            }
        
    except HTTPException:
        raise
    except KeyError as e:
        logger.error(f"Missing required parameter: {e}")
        return {
            "tool": tool_request.name,
            "content": {"status": "error", "message": f"Missing required parameter: {e}"},
            "isError": True
        }
    except Exception as e:
        logger.error(f"Error calling tool {tool_request.name}: {e}")
        return {
            "tool": tool_request.name,
            "content": {"status": "error", "message": str(e)},
            "isError": True
        }


@app.get("/mcp/resources")
async def list_resources(
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
    authorization: Optional[str] = Header(None)
):
    """List available MCP resources."""
    try:
        resources_list = []
        for resource in resources:
            resources_list.append({
                "uri": resource.uri,
                "name": resource.name,
                "description": resource.description,
                "mimeType": resource.mime_type
            })
        
        return {"resources": resources_list}
    except Exception as e:
        logger.error(f"Error listing resources: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/mcp/resources/read")
async def read_resource(
    resource_request: ResourceReadRequest,
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
    authorization: Optional[str] = Header(None)
):
    """Read an MCP resource."""
    try:
        uri = resource_request.uri
        
        # Find 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):
                    matches = True
                    for pattern_part, uri_part in 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 not matching_resource:
            available_uris = [r.uri for r in resources]
            raise HTTPException(
                status_code=404,
                detail=f"Unknown resource URI: {uri}. Available: {available_uris}"
            )
        
        # Read resource
        content = await matching_resource.read(uri, backend_client)
        
        # Parse JSON string to return as proper JSON object
        try:
            parsed_content = json.loads(content)
            return {
                "uri": uri,
                "content": parsed_content
            }
        except json.JSONDecodeError:
            # If not valid JSON, return as string
            return {
                "uri": uri,
                "content": content
            }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error reading resource {resource_request.uri}: {e}")
        raise HTTPException(status_code=500, detail=str(e))


# ============================================================================
# Convenience REST API endpoints (for direct API access)
# ============================================================================

@app.get("/api/devices")
async def get_devices_api(
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key")
):
    """Get all devices - REST API endpoint."""
    try:
        devices = await backend_client.get_devices()
        return {"data": devices, "count": len(devices)}
    except Exception as e:
        logger.error(f"Error getting devices: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/devices/{device_id}")
async def get_device_api(
    device_id: str,
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key")
):
    """Get device details - REST API endpoint."""
    try:
        device = await backend_client.get_device(device_id)
        if not device:
            raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
        return {"data": device}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error getting device {device_id}: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/devices/{device_id}/telemetry")
async def get_device_telemetry_api(
    device_id: str,
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key")
):
    """Get device telemetry - REST API endpoint."""
    try:
        telemetry = await backend_client.get_device_telemetry(device_id)
        return {"data": telemetry}
    except Exception as e:
        logger.error(f"Error getting telemetry for device {device_id}: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/devices/{device_id}/processes")
async def get_device_processes_api(
    device_id: str,
    request: Request,
    x_api_key: Optional[str] = Header(None, alias="X-API-Key")
):
    """Get device processes - REST API endpoint."""
    try:
        processes = await backend_client.get_device_processes(device_id)
        return {"data": processes, "count": len(processes) if processes else 0}
    except Exception as e:
        logger.error(f"Error getting processes for device {device_id}: {e}")
        raise HTTPException(status_code=500, detail=str(e))


if __name__ == "__main__":
    import uvicorn
    port = settings.agentnex_mcp_server_port
    uvicorn.run(app, host="0.0.0.0", port=port)
