import { WebSocketServer, WebSocket } from 'ws';
import { IncomingMessage } from 'http';
import { prisma } from '@/lib/prisma';

interface WebSocketWithMetadata extends WebSocket {
  userId?: string;
  connectionId?: string;
  rooms?: Set<string>;
}

interface MessageData {
  type: string;
  userId?: string;
  roomId?: string;
  content?: string;
  receiverId?: string;
  projectId?: string;
  bidId?: string;
  [key: string]: any;
}

class WebSocketManager {
  private wss: WebSocketServer | null = null;
  private connections: Map<string, WebSocketWithMetadata> = new Map();
  private userConnections: Map<string, string> = new Map(); // userId -> connectionId
  private roomConnections: Map<string, Set<string>> = new Map(); // roomId -> Set<connectionId>

  initialize(server?: any) {
    // This WebSocket manager is deprecated - WebSocket server is now handled in server.js
    // Keep this class for compatibility but don't initialize a separate server
    console.warn('WebSocketManager.initialize() called but WebSocket server is handled in server.js');
    return;
  }

  private handleConnection(ws: WebSocketWithMetadata, req: IncomingMessage) {
    const connectionId = this.generateConnectionId();
    ws.connectionId = connectionId;
    ws.rooms = new Set();
    
    this.connections.set(connectionId, ws);


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

    ws.on('close', () => {
      this.handleDisconnection(ws);
    });

    ws.on('error', (error) => {
      this.handleDisconnection(ws);
    });

    // Send connection acknowledgment
    this.sendToClient(ws, { type: 'connected', connectionId });
  }

  private async handleMessage(ws: WebSocketWithMetadata, message: MessageData) {
    switch (message.type) {
      case 'auth':
        await this.handleAuth(ws, message);
        break;
      case 'join_room':
        this.handleJoinRoom(ws, message);
        break;
      case 'leave_room':
        this.handleLeaveRoom(ws, message);
        break;
      case 'send_message':
        await this.handleSendMessage(ws, message);
        break;
      case 'typing_start':
        this.handleTyping(ws, message, true);
        break;
      case 'typing_stop':
        this.handleTyping(ws, message, false);
        break;
      case 'ping':
        this.sendToClient(ws, { type: 'pong' });
        break;
      default:
        this.sendToClient(ws, { type: 'error', message: 'Unknown message type' });
    }
  }

  private async handleAuth(ws: WebSocketWithMetadata, message: MessageData) {
    try {
      const { userId, email } = message;
      
      if (!userId && !email) {
        this.sendToClient(ws, { type: 'auth_error', message: 'User ID or email required' });
        return;
      }

      // Verify user exists in database
      let user;
      if (userId) {
        user = await prisma.user.findUnique({ where: { id: userId } });
      } else if (email) {
        user = await prisma.user.findUnique({ where: { email } });
      }

      if (!user) {
        this.sendToClient(ws, { type: 'auth_error', message: 'User not found' });
        return;
      }

      ws.userId = user.id;
      
      // Remove any existing connection for this user
      const existingConnectionId = this.userConnections.get(user.id);
      if (existingConnectionId) {
        const existingWs = this.connections.get(existingConnectionId);
        if (existingWs && existingWs.readyState === WebSocket.OPEN) {
          existingWs.close();
        }
      }

      this.userConnections.set(user.id, ws.connectionId!);
      
      this.sendToClient(ws, { 
        type: 'auth_success', 
        userId: user.id,
        connectionId: ws.connectionId 
      });

    } catch (error) {
      this.sendToClient(ws, { type: 'auth_error', message: 'Authentication failed' });
    }
  }

  private handleJoinRoom(ws: WebSocketWithMetadata, message: MessageData) {
    const { roomId } = message;
    if (!roomId || !ws.connectionId) return;

    ws.rooms?.add(roomId);
    
    if (!this.roomConnections.has(roomId)) {
      this.roomConnections.set(roomId, new Set());
    }
    this.roomConnections.get(roomId)?.add(ws.connectionId);

    this.sendToClient(ws, { type: 'room_joined', roomId });
    this.broadcastToRoom(roomId, { 
      type: 'user_joined', 
      userId: ws.userId, 
      roomId 
    }, ws.connectionId);

  }

  private handleLeaveRoom(ws: WebSocketWithMetadata, message: MessageData) {
    const { roomId } = message;
    if (!roomId || !ws.connectionId) return;

    ws.rooms?.delete(roomId);
    this.roomConnections.get(roomId)?.delete(ws.connectionId);

    this.sendToClient(ws, { type: 'room_left', roomId });
    this.broadcastToRoom(roomId, { 
      type: 'user_left', 
      userId: ws.userId, 
      roomId 
    }, ws.connectionId);

  }

  private async handleSendMessage(ws: WebSocketWithMetadata, message: MessageData) {
    try {
      const { content, receiverId, projectId, bidId, messageType = 'text' } = message;
      
      if (!ws.userId) {
        this.sendToClient(ws, { type: 'error', message: 'Not authenticated' });
        return;
      }

      if (!content || !receiverId) {
        this.sendToClient(ws, { type: 'error', message: 'Content and receiver required' });
        return;
      }

      // Save message to database
      const savedMessage = await prisma.message.create({
        data: {
          content,
          senderId: ws.userId,
          receiverId,
          projectId,
          bidId,
          messageType
        },
        include: {
          sender: {
            include: {
              profile: true
            }
          },
          receiver: {
            include: {
              profile: true
            }
          },
          project: {
            select: {
              id: true,
              title: true
            }
          }
        }
      });

      // Format message for broadcast
      const formattedMessage = {
        id: savedMessage.id,
        content: savedMessage.content,
        senderId: savedMessage.senderId,
        receiverId: savedMessage.receiverId,
        projectId: savedMessage.projectId,
        bidId: savedMessage.bidId,
        messageType: savedMessage.messageType,
        createdAt: savedMessage.createdAt.toISOString(),
        isRead: savedMessage.isRead,
        sender: savedMessage.sender,
        receiver: savedMessage.receiver,
        project: savedMessage.project
      };

      // Send to sender (confirmation)
      this.sendToClient(ws, {
        type: 'message_sent',
        message: formattedMessage
      });

      // Send to receiver
      this.sendToUser(receiverId, {
        type: 'new_message',
        message: formattedMessage
      });

      // If it's a project/bid message, also broadcast to room
      if (projectId) {
        const roomId = bidId ? `bid_${bidId}` : `project_${projectId}`;
        this.broadcastToRoom(roomId, {
          type: 'new_message',
          message: formattedMessage
        });
      }

    } catch (error) {
      this.sendToClient(ws, { type: 'error', message: 'Failed to send message' });
    }
  }

  private handleTyping(ws: WebSocketWithMetadata, message: MessageData, isTyping: boolean) {
    const { receiverId, roomId } = message;
    
    if (receiverId) {
      this.sendToUser(receiverId, {
        type: isTyping ? 'typing_start' : 'typing_stop',
        userId: ws.userId
      });
    }

    if (roomId) {
      this.broadcastToRoom(roomId, {
        type: isTyping ? 'typing_start' : 'typing_stop',
        userId: ws.userId
      }, ws.connectionId);
    }
  }

  private handleDisconnection(ws: WebSocketWithMetadata) {
    if (!ws.connectionId) return;

    // Remove from user connections
    if (ws.userId) {
      this.userConnections.delete(ws.userId);
    }

    // Remove from room connections
    ws.rooms?.forEach(roomId => {
      this.roomConnections.get(roomId)?.delete(ws.connectionId!);
      this.broadcastToRoom(roomId, {
        type: 'user_left',
        userId: ws.userId
      }, ws.connectionId);
    });

    // Remove from connections
    this.connections.delete(ws.connectionId);

  }

  private sendToClient(ws: WebSocketWithMetadata, data: any) {
    if (ws.readyState === WebSocket.OPEN) {
      try {
        ws.send(JSON.stringify(data));
      } catch (error) {
      }
    }
  }

  private sendToUser(userId: string, data: any) {
    const connectionId = this.userConnections.get(userId);
    if (connectionId) {
      const ws = this.connections.get(connectionId);
      if (ws) {
        this.sendToClient(ws, data);
      }
    }
  }

  private broadcastToRoom(roomId: string, data: any, excludeConnectionId?: string) {
    const roomConnections = this.roomConnections.get(roomId);
    if (!roomConnections) return;

    roomConnections.forEach(connectionId => {
      if (connectionId !== excludeConnectionId) {
        const ws = this.connections.get(connectionId);
        if (ws) {
          this.sendToClient(ws, data);
        }
      }
    });
  }

  private generateConnectionId(): string {
    return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // Public methods for external use
  public sendMessageToUser(userId: string, data: any) {
    this.sendToUser(userId, data);
  }

  public broadcastToRoomExternal(roomId: string, data: any) {
    this.broadcastToRoom(roomId, data);
  }

  public getConnectedUsers(): string[] {
    return Array.from(this.userConnections.keys());
  }

  public isUserConnected(userId: string): boolean {
    return this.userConnections.has(userId);
  }
}

// Singleton instance
export const websocketManager = new WebSocketManager();

// DEPRECATED: This file is no longer used for WebSocket server implementation
// The main WebSocket server is implemented directly in server.js
// Use WebSocketBridge from websocket-bridge.ts to interact with the WebSocket server from API routes