import type { Server } from "http";
import { WebSocketServer, WebSocket } from "ws";

import {
  detailedMetrics,
  type DetailedMetricsSnapshot,
} from "../detailed-metrics.js";
import { metrics, type ConnectionEvent } from "../metrics.js";
import type { EndpointId } from "../providers/types.js";
import { registry, type DynamicConfig } from "../registry.js";

interface WebSocketMessage {
  type: "metrics" | "detailed_metrics" | "event" | "config" | "error";
  data: unknown;
}

export class AdminWebSocketServer {
  private wss: WebSocketServer | null = null;
  private clients: Set<WebSocket> = new Set();
  private metricsInterval: ReturnType<typeof setInterval> | null = null;

  /**
   * Attach WebSocket server to existing HTTP server
   */
  attach(server: Server): void {
    this.wss = new WebSocketServer({ server, path: "/ws" });

    this.wss.on("connection", (ws) => {
      this.clients.add(ws);

      // Send initial state
      this.send(ws, { type: "metrics", data: metrics.getSnapshot() });
      this.send(ws, {
        type: "detailed_metrics",
        data: detailedMetrics.getSnapshot(),
      });
      this.send(ws, { type: "config", data: registry.getFullConfig() });

      ws.on("message", (data) => {
        try {
          const message = JSON.parse(data.toString());
          this.handleMessage(ws, message);
        } catch {
          this.send(ws, { type: "error", data: "Invalid message format" });
        }
      });

      ws.on("close", () => {
        this.clients.delete(ws);
      });

      ws.on("error", (error) => {
        console.error("WebSocket error:", error);
        this.clients.delete(ws);
      });
    });

    // Subscribe to metrics events
    metrics.on("event", (event: ConnectionEvent) => {
      this.broadcast({ type: "event", data: event });
    });

    // Subscribe to config changes
    registry.on("config_change", (config: DynamicConfig) => {
      this.broadcast({ type: "config", data: config });
    });

    // Subscribe to detailed metrics ticks (1 second interval)
    detailedMetrics.on("tick", (snapshot: DetailedMetricsSnapshot) => {
      if (this.clients.size > 0) {
        this.broadcast({ type: "detailed_metrics", data: snapshot });
      }
    });

    // Send metrics snapshot every 100ms for smooth updates
    this.metricsInterval = setInterval(() => {
      if (this.clients.size > 0) {
        this.broadcast({ type: "metrics", data: metrics.getSnapshot() });
      }
    }, 100);
  }

  /**
   * Handle incoming WebSocket message
   */
  private handleMessage(
    ws: WebSocket,
    message: { type: string; data?: unknown }
  ): void {
    switch (message.type) {
      case "get_metrics":
        this.send(ws, { type: "metrics", data: metrics.getSnapshot() });
        break;

      case "get_config":
        this.send(ws, { type: "config", data: registry.getFullConfig() });
        break;

      case "update_global_config":
        if (message.data && typeof message.data === "object") {
          registry.updateGlobalConfig(message.data as Record<string, unknown>);
        }
        break;

      case "update_endpoint_config":
        if (message.data && typeof message.data === "object") {
          const { endpoint, config } = message.data as {
            endpoint: string;
            config: Record<string, unknown>;
          };
          if (endpoint && config) {
            registry.updateEndpointConfig(endpoint as EndpointId, config);
          }
        }
        break;

      case "reset_config":
        registry.reset();
        break;

      case "reset_metrics":
        metrics.reset();
        break;

      case "reset_rate_limiters":
        registry.resetAllRateLimiters();
        break;

      case "reset_detailed_metrics":
        detailedMetrics.reset();
        this.send(ws, {
          type: "detailed_metrics",
          data: detailedMetrics.getSnapshot(),
        });
        break;

      case "get_detailed_metrics":
        this.send(ws, {
          type: "detailed_metrics",
          data: detailedMetrics.getSnapshot(),
        });
        break;

      default:
        this.send(ws, {
          type: "error",
          data: `Unknown message type: ${message.type}`,
        });
    }
  }

  /**
   * Send message to a single client
   */
  private send(ws: WebSocket, message: WebSocketMessage): void {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(message));
    }
  }

  /**
   * Broadcast message to all connected clients
   */
  private broadcast(message: WebSocketMessage): void {
    const data = JSON.stringify(message);
    for (const client of this.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data);
      }
    }
  }

  /**
   * Get number of connected clients
   */
  getClientCount(): number {
    return this.clients.size;
  }

  /**
   * Close WebSocket server
   */
  close(): void {
    if (this.metricsInterval) {
      clearInterval(this.metricsInterval);
      this.metricsInterval = null;
    }

    for (const client of this.clients) {
      client.close();
    }
    this.clients.clear();

    if (this.wss) {
      this.wss.close();
      this.wss = null;
    }
  }
}

// Singleton instance
export const adminWebSocket = new AdminWebSocketServer();
