import { Server as HTTPServer } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
import { writeAuditLog } from './db.ts';

export type WebSocketEventType =
  | 'user_online'
  | 'user_offline'
  | 'user_typing'
  | 'message_sent'
  | 'message_read'
  | 'status_update'
  | 'achievement_unlocked'
  | 'challenge_completed'
  | 'friend_request'
  | 'friend_accepted'
  | 'notification';

export interface WebSocketEvent {
  type: WebSocketEventType;
  userId: number;
  data: Record<string, any>;
  timestamp: Date;
}

export interface WebSocketClient {
  userId: number;
  socket: WebSocket;
  connectionId: string;
  connectedAt: Date;
}

/**
 * WebSocket Manager - Handles real-time communication
 */
export class WebSocketManager {
  private wss: WebSocketServer;
  private clients: Map<string, WebSocketClient> = new Map(); // connectionId -> client
  private userConnections: Map<number, Set<string>> = new Map(); // userId -> set of connectionIds
  private messageQueue: WebSocketEvent[] = [];

  constructor(httpServer: HTTPServer) {
    this.wss = new WebSocketServer({ server: httpServer });
    this.setupEventHandlers();
  }

  /**
   * Setup WebSocket event handlers
   */
  private setupEventHandlers(): void {
    this.wss.on('connection', (socket: WebSocket) => {
      const connectionId = `conn_${Date.now()}_${Math.random().toString(36).substring(7)}`;

      socket.on('message', async (data: string) => {
        try {
          const message = JSON.parse(data);
          await this.handleMessage(connectionId, message);
        } catch (error) {
          console.error('WebSocket message error:', error);
          socket.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
        }
      });

      socket.on('close', async () => {
        await this.handleDisconnect(connectionId);
      });

      socket.on('error', (error: any) => {
        console.error('WebSocket error:', error);
      });

      // Send connection confirmation
      socket.send(JSON.stringify({ type: 'connected', connectionId }));
    });
  }

  /**
   * Handle incoming WebSocket message
   */
  private async handleMessage(connectionId: string, message: any): Promise<void> {
    const { type, userId, data } = message;

    if (type === 'auth') {
      // Authenticate user
      const client: WebSocketClient = {
        userId,
        socket: this.wss.clients.values().next().value,
        connectionId,
        connectedAt: new Date(),
      };

      this.clients.set(connectionId, client);

      if (!this.userConnections.has(userId)) {
        this.userConnections.set(userId, new Set());
      }
      this.userConnections.get(userId)!.add(connectionId);

      // Broadcast user online status
      await this.broadcastEvent({
        type: 'user_online',
        userId,
        data: { userId, status: 'online' },
        timestamp: new Date(),
      });

      // Send confirmation
      const socket = this.clients.get(connectionId)?.socket;
      if (socket) {
        socket.send(JSON.stringify({ type: 'auth_success', userId }));
      }
    } else if (type === 'message') {
      // Handle chat message
      await this.broadcastEvent({
        type: 'message_sent',
        userId,
        data,
        timestamp: new Date(),
      });
    } else if (type === 'typing') {
      // Broadcast typing status
      await this.broadcastEvent({
        type: 'user_typing',
        userId,
        data: { recipientId: data.recipientId, isTyping: data.isTyping },
        timestamp: new Date(),
      });
    } else if (type === 'read') {
      // Broadcast message read
      await this.broadcastEvent({
        type: 'message_read',
        userId,
        data,
        timestamp: new Date(),
      });
    } else if (type === 'status') {
      // Update user status
      await this.broadcastEvent({
        type: 'status_update',
        userId,
        data: { status: data.status },
        timestamp: new Date(),
      });
    }
  }

  /**
   * Handle user disconnect
   */
  private async handleDisconnect(connectionId: string): Promise<void> {
    const client = this.clients.get(connectionId);
    if (!client) return;

    this.clients.delete(connectionId);

    const userConnections = this.userConnections.get(client.userId);
    if (userConnections) {
      userConnections.delete(connectionId);

      // If no more connections, broadcast offline
      if (userConnections.size === 0) {
        await this.broadcastEvent({
          type: 'user_offline',
          userId: client.userId,
          data: { userId: client.userId, status: 'offline' },
          timestamp: new Date(),
        });

        this.userConnections.delete(client.userId);
      }
    }
  }

  /**
   * Broadcast event to all connected clients
   */
  async broadcastEvent(event: WebSocketEvent): Promise<void> {
    const eventData = JSON.stringify(event);

    for (const client of this.clients.values()) {
      if (client.socket.readyState === WebSocket.OPEN) {
        client.socket.send(eventData);
      }
    }

    // Queue event for offline users
    this.messageQueue.push(event);

    // Keep only last 1000 events
    if (this.messageQueue.length > 1000) {
      this.messageQueue.shift();
    }
  }

  /**
   * Send event to specific user
   */
  async sendToUser(userId: number, event: WebSocketEvent): Promise<void> {
    const connectionIds = this.userConnections.get(userId);
    if (!connectionIds) return;

    const eventData = JSON.stringify(event);

    for (const connectionId of connectionIds) {
      const client = this.clients.get(connectionId);
      if (client && client.socket.readyState === WebSocket.OPEN) {
        client.socket.send(eventData);
      }
    }
  }

  /**
   * Send event to multiple users
   */
  async sendToUsers(userIds: number[], event: WebSocketEvent): Promise<void> {
    for (const userId of userIds) {
      await this.sendToUser(userId, event);
    }
  }

  /**
   * Get connected users
   */
  getConnectedUsers(): number[] {
    return Array.from(this.userConnections.keys());
  }

  /**
   * Check if user is online
   */
  isUserOnline(userId: number): boolean {
    const connections = this.userConnections.get(userId);
    return connections ? connections.size > 0 : false;
  }

  /**
   * Get user connection count
   */
  getUserConnectionCount(userId: number): number {
    const connections = this.userConnections.get(userId);
    return connections ? connections.size : 0;
  }

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

  /**
   * Get WebSocket stats
   */
  getStats(): {
    totalConnectedUsers: number;
    totalConnections: number;
    messageQueueSize: number;
  } {
    return {
      totalConnectedUsers: this.userConnections.size,
      totalConnections: this.clients.size,
      messageQueueSize: this.messageQueue.length,
    };
  }

  /**
   * Notify achievement unlock
   */
  async notifyAchievementUnlock(userId: number, achievementName: string, reward: number): Promise<void> {
    await this.sendToUser(userId, {
      type: 'achievement_unlocked',
      userId,
      data: { achievementName, reward },
      timestamp: new Date(),
    });
  }

  /**
   * Notify challenge completion
   */
  async notifyChallengeCompletion(userId: number, challengeName: string, reward: number): Promise<void> {
    await this.sendToUser(userId, {
      type: 'challenge_completed',
      userId,
      data: { challengeName, reward },
      timestamp: new Date(),
    });
  }

  /**
   * Notify friend request
   */
  async notifyFriendRequest(userId: number, requesterName: string): Promise<void> {
    await this.sendToUser(userId, {
      type: 'friend_request',
      userId,
      data: { requesterName },
      timestamp: new Date(),
    });
  }

  /**
   * Notify friend accepted
   */
  async notifyFriendAccepted(userId: number, friendName: string): Promise<void> {
    await this.sendToUser(userId, {
      type: 'friend_accepted',
      userId,
      data: { friendName },
      timestamp: new Date(),
    });
  }

  /**
   * Send notification
   */
  async sendNotification(userId: number, title: string, message: string, icon?: string): Promise<void> {
    await this.sendToUser(userId, {
      type: 'notification',
      userId,
      data: { title, message, icon },
      timestamp: new Date(),
    });
  }
}

let wsManager: WebSocketManager | null = null;

/**
 * Initialize WebSocket manager
 */
export function initializeWebSocket(httpServer: HTTPServer): WebSocketManager {
  if (!wsManager) {
    wsManager = new WebSocketManager(httpServer);
  }
  return wsManager;
}

/**
 * Get WebSocket manager instance
 */
export function getWebSocketManager(): WebSocketManager {
  if (!wsManager) {
    throw new Error('WebSocket manager not initialized');
  }
  return wsManager;
}
