"""
Management REST API for the VS Code / Cursor extension.

Provides HTTP endpoints for:
- Server catalog browsing and management
- Instance monitoring and control
- Metrics and dashboard data
- Real-time WebSocket updates
"""

from __future__ import annotations

import json
import logging

from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocket

from mcp_bridge.instances.manager import InstanceManager
from mcp_bridge.metrics.collector import MetricsCollector
from mcp_bridge.registry.catalog import ServerCatalog
from mcp_bridge.registry.models import ServerEntry, ToolFilter

logger = logging.getLogger(__name__)

# Global references (set during app creation)
_catalog: ServerCatalog | None = None
_instance_manager: InstanceManager | None = None
_metrics: MetricsCollector | None = None

# WebSocket clients for real-time updates
_ws_clients: set[WebSocket] = set()


async def _broadcast_event(event_type: str, data: dict) -> None:
    """Broadcast an event to all connected WebSocket clients."""
    global _ws_clients
    message = json.dumps({"type": event_type, "data": data})
    disconnected: set[WebSocket] = set()
    # Iterate over a snapshot so concurrent add/remove from
    # websocket_endpoint cannot mutate the set mid-iteration.
    for ws in list(_ws_clients):
        try:
            await ws.send_text(message)
        except Exception:
            disconnected.add(ws)
    _ws_clients -= disconnected


# --- Catalog endpoints ---


def _server_to_dict(s) -> dict:
    """Convert a ServerEntry to a dict with extension-friendly field names."""
    d = s.model_dump(mode="json")
    # The extension expects "config_options" but the model has "configurable_options"
    d["config_options"] = d.pop("configurable_options", [])
    return d


async def list_servers(request: Request) -> JSONResponse:
    """GET /api/servers - List all servers in the catalog.

    Query params:
        category     - filter by category
        search       - text search
        enabled_only - true/false
        source       - filter by source (e.g. "catalog", "user", "api-import")
        exclude_source - exclude a source (e.g. "catalog" to get only user-added)
    """
    assert _catalog is not None
    category = request.query_params.get("category")
    search = request.query_params.get("search")
    enabled_only = request.query_params.get("enabled_only", "false").lower() == "true"

    servers = await _catalog.list_servers(category=category, search=search, enabled_only=enabled_only)

    # Optional source filtering
    source = request.query_params.get("source")
    exclude_source = request.query_params.get("exclude_source")
    if source:
        servers = [s for s in servers if s.source == source]
    if exclude_source:
        servers = [s for s in servers if s.source != exclude_source]

    return JSONResponse([_server_to_dict(s) for s in servers])


async def get_server(request: Request) -> JSONResponse:
    """GET /api/servers/{server_id} - Get a specific server entry."""
    assert _catalog is not None
    server_id = request.path_params["server_id"]
    server = await _catalog.get_server(server_id)
    if not server:
        return JSONResponse({"error": f"Server '{server_id}' not found"}, status_code=404)
    return JSONResponse(_server_to_dict(server))


async def register_server(request: Request) -> JSONResponse:
    """POST /api/servers - Register a new server."""
    assert _catalog is not None
    data = await request.json()
    try:
        entry = ServerEntry(**data)
        result = await _catalog.register_server(entry)
        await _broadcast_event("server_registered", {"server_id": result.id})
        return JSONResponse(result.model_dump(mode="json"), status_code=201)
    except Exception as e:
        logger.warning("register_server failed: %s", e, exc_info=True)
        return JSONResponse({"error": "Invalid server configuration. Check the provided fields."}, status_code=400)


async def delete_server(request: Request) -> JSONResponse:
    """DELETE /api/servers/{server_id} - Remove a server."""
    assert _catalog is not None
    server_id = request.path_params["server_id"]
    success = await _catalog.unregister_server(server_id)
    if success:
        await _broadcast_event("server_removed", {"server_id": server_id})
        return JSONResponse({"message": f"Server '{server_id}' removed"})
    return JSONResponse({"error": f"Server '{server_id}' not found"}, status_code=404)


async def toggle_server(request: Request) -> JSONResponse:
    """PATCH /api/servers/{server_id}/toggle - Enable/disable a server."""
    assert _catalog is not None
    server_id = request.path_params["server_id"]
    data = await request.json()
    enabled = data.get("enabled", True)

    if enabled:
        success = await _catalog.enable_server(server_id)
    else:
        success = await _catalog.disable_server(server_id)

    if success:
        await _broadcast_event("server_toggled", {"server_id": server_id, "enabled": enabled})
        return JSONResponse({"server_id": server_id, "enabled": enabled})
    return JSONResponse({"error": f"Server '{server_id}' not found"}, status_code=404)


async def update_tool_filter(request: Request) -> JSONResponse:
    """PATCH /api/servers/{server_id}/tools - Update tool filter."""
    assert _catalog is not None
    server_id = request.path_params["server_id"]
    data = await request.json()

    try:
        tool_filter = ToolFilter(**data)
        success = await _catalog.update_tool_filter(server_id, tool_filter)
        if success:
            return JSONResponse({"message": "Tool filter updated"})
        return JSONResponse({"error": "Server not found"}, status_code=404)
    except Exception as e:
        logger.warning("update_tool_filter failed for %s: %s", server_id, e, exc_info=True)
        return JSONResponse({"error": "Invalid tool filter configuration."}, status_code=400)


async def import_config(request: Request) -> JSONResponse:
    """POST /api/servers/import - Import from mcp.json config."""
    assert _catalog is not None
    data = await request.json()
    try:
        config_data = data.get("config", data)
        source = data.get("source", "api-import")
        imported = await _catalog.import_from_mcp_json(config_data, source=source)
        return JSONResponse(
            {"imported": [s.model_dump(mode="json") for s in imported], "count": len(imported)},
            status_code=201,
        )
    except Exception as e:
        logger.warning("import_config failed: %s", e, exc_info=True)
        return JSONResponse({"error": "Failed to import configuration. Ensure valid MCP JSON format."}, status_code=400)


async def list_categories(request: Request) -> JSONResponse:
    """GET /api/categories - List all categories with counts."""
    assert _catalog is not None
    categories = await _catalog.list_categories()
    return JSONResponse(categories)


# --- Instance endpoints ---


async def create_instance(request: Request) -> JSONResponse:
    """POST /api/instances - Activate a server instance.

    Body: { "server_id": "...", "config": {...}, "session_id": "extension" }
    """
    assert _instance_manager is not None
    data = await request.json()
    server_id = data.get("server_id")
    if not server_id:
        return JSONResponse({"error": "server_id is required"}, status_code=400)

    config = data.get("config")
    session_id = data.get("session_id", "extension")

    try:
        instance = await _instance_manager.configure_instance(
            server_id=server_id,
            agent_session_id=session_id,
            config=config,
        )
        instance = await _instance_manager.activate_instance(instance.instance_id)
        await _broadcast_event(
            "instance_activated",
            {"instance_id": instance.instance_id, "server_id": server_id},
        )
        return JSONResponse(
            {
                "instance_id": instance.instance_id,
                "server_id": instance.server_id,
                "state": instance.state.value,
                "tools": [t.name for t in instance.tools],
            },
            status_code=201,
        )
    except ValueError as e:
        logger.warning("create_instance validation error for %s: %s", server_id, e)
        return JSONResponse({"error": "Invalid configuration for this server."}, status_code=400)
    except RuntimeError as e:
        logger.warning("create_instance conflict for %s: %s", server_id, e)
        return JSONResponse({"error": "Server instance conflict. It may already be active."}, status_code=409)
    except Exception as e:
        logger.error("Failed to activate server %s: %s", server_id, e, exc_info=True)
        return JSONResponse({"error": "Internal error while activating the server."}, status_code=500)


async def list_instances(request: Request) -> JSONResponse:
    """GET /api/instances - List all active instances."""
    assert _instance_manager is not None
    instances = _instance_manager.get_all_instances()
    return JSONResponse([inst.model_dump(mode="json") for inst in instances])


async def get_instance(request: Request) -> JSONResponse:
    """GET /api/instances/{instance_id} - Get instance details."""
    assert _instance_manager is not None
    instance_id = request.path_params["instance_id"]
    instance = _instance_manager.get_instance(instance_id)
    if not instance:
        return JSONResponse({"error": "Instance not found"}, status_code=404)
    return JSONResponse(instance.model_dump(mode="json"))


async def stop_instance(request: Request) -> JSONResponse:
    """DELETE /api/instances/{instance_id} - Stop an instance."""
    assert _instance_manager is not None
    instance_id = request.path_params["instance_id"]
    success = await _instance_manager.deactivate_instance(instance_id)
    if success:
        await _broadcast_event("instance_stopped", {"instance_id": instance_id})
        return JSONResponse({"message": "Instance stopped"})
    return JSONResponse({"error": "Instance not found"}, status_code=404)


# --- Metrics endpoints ---


async def get_metrics_summary(request: Request) -> JSONResponse:
    """GET /api/metrics/summary - Get metrics summary."""
    assert _metrics is not None
    assert _instance_manager is not None
    assert _catalog is not None

    hours = int(request.query_params.get("hours", "24"))
    summary = await _metrics.get_summary(hours)

    # Enrich with live counts the extension needs
    summary["active_instances"] = _instance_manager.active_instance_count
    summary["total_servers"] = await _catalog.get_server_count()
    summary["successful_calls"] = round(
        summary.get("total_calls", 0) * summary.get("success_rate", 100) / 100
    )
    summary["failed_calls"] = summary.get("total_calls", 0) - summary.get("successful_calls", 0)

    return JSONResponse(summary)


async def get_recent_calls(request: Request) -> JSONResponse:
    """GET /api/metrics/calls - Get recent tool calls."""
    assert _metrics is not None
    limit = int(request.query_params.get("limit", "50"))
    server_id = request.query_params.get("server_id")
    calls = await _metrics.get_recent_calls(limit, server_id)
    return JSONResponse(calls)


async def export_metrics(request: Request) -> JSONResponse:
    """GET /api/metrics/export - Export metrics as JSON."""
    assert _metrics is not None
    hours = int(request.query_params.get("hours", "24"))
    data = await _metrics.export_json(hours)
    return JSONResponse(data)


# --- System endpoints ---


async def get_system_status(request: Request) -> JSONResponse:
    """GET /api/status - Get bridge system status."""
    import psutil

    assert _instance_manager is not None
    assert _catalog is not None

    return JSONResponse(
        {
            "status": "running",
            "version": "1.0.0-beta-rc",
            "active_instances": _instance_manager.active_instance_count,
            "total_registered_servers": await _catalog.get_server_count(),
            "system": {
                "cpu_percent": psutil.cpu_percent(),
                "memory_percent": psutil.virtual_memory().percent,
                "memory_available_mb": round(psutil.virtual_memory().available / (1024 * 1024)),
            },
            "websocket_clients": len(_ws_clients),
        }
    )


# --- WebSocket ---


async def websocket_endpoint(websocket: WebSocket) -> None:
    """WebSocket endpoint for real-time updates."""
    await websocket.accept()
    _ws_clients.add(websocket)
    logger.info("WebSocket client connected (total: %d)", len(_ws_clients))

    try:
        while True:
            # Keep connection alive, handle incoming messages if needed
            data = await websocket.receive_text()
            # Could handle commands from the extension here
            if data == "ping":
                await websocket.send_text(json.dumps({"type": "pong"}))
    except Exception as e:
        logger.debug("WebSocket connection closed: %s", type(e).__name__)
    finally:
        _ws_clients.discard(websocket)
        logger.info("WebSocket client disconnected (total: %d)", len(_ws_clients))


def create_management_app(
    catalog: ServerCatalog,
    instance_manager: InstanceManager,
    metrics: MetricsCollector,
) -> Starlette:
    """Create the Starlette management API application."""
    global _catalog, _instance_manager, _metrics
    _catalog = catalog
    _instance_manager = instance_manager
    _metrics = metrics

    routes = [
        # Catalog
        Route("/api/servers", list_servers, methods=["GET"]),
        Route("/api/servers", register_server, methods=["POST"]),
        Route("/api/servers/import", import_config, methods=["POST"]),
        Route("/api/servers/{server_id}", get_server, methods=["GET"]),
        Route("/api/servers/{server_id}", delete_server, methods=["DELETE"]),
        Route("/api/servers/{server_id}/toggle", toggle_server, methods=["PATCH"]),
        Route("/api/servers/{server_id}/tools", update_tool_filter, methods=["PATCH"]),
        Route("/api/categories", list_categories, methods=["GET"]),
        # Instances
        Route("/api/instances", create_instance, methods=["POST"]),
        Route("/api/instances", list_instances, methods=["GET"]),
        Route("/api/instances/{instance_id}", get_instance, methods=["GET"]),
        Route("/api/instances/{instance_id}", stop_instance, methods=["DELETE"]),
        # Metrics
        Route("/api/metrics/summary", get_metrics_summary, methods=["GET"]),
        Route("/api/metrics/calls", get_recent_calls, methods=["GET"]),
        Route("/api/metrics/export", export_metrics, methods=["GET"]),
        # System
        Route("/api/status", get_system_status, methods=["GET"]),
        # WebSocket
        WebSocketRoute("/ws", websocket_endpoint),
    ]

    return Starlette(routes=routes)
