const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { WebSocketServer } = require('ws');

const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = process.env.PORT || 3000;

// Create Next.js app
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

// WebSocket connections management
const connections = new Map();
const userConnections = new Map();
const roomConnections = new Map();
const onlineUsers = new Set();

// Expose connections globally for API routes
global.wsConnections = connections;
global.wsUserConnections = userConnections;
global.wsRoomConnections = roomConnections;

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

function sendToClient(ws, data) {
  if (ws.readyState === 1) { // WebSocket.OPEN
    try {
      ws.send(JSON.stringify(data));
    } catch (error) {
      console.error('Error sending to client:', error);
    }
  }
}

function sendToUser(userId, data) {
  console.log(`🔍 [sendToUser] Looking up user: ${userId}`);
  const connectionId = userConnections.get(userId);
  console.log(`🔍 [sendToUser] Found connection ID: ${connectionId}`);
  
  if (connectionId) {
    const ws = connections.get(connectionId);
    console.log(`🔍 [sendToUser] Found WebSocket: ${!!ws}`);
    if (ws) {
      console.log(`🔍 [sendToUser] Sending data to user ${userId}:`, data.type);
      sendToClient(ws, data);
      return true;
    }
  }
  
  console.log(`❌ [sendToUser] Failed to send to user ${userId}`);
  return false;
}

function broadcastToRoom(roomId, data, excludeConnectionId) {
  const roomConns = roomConnections.get(roomId);
  if (!roomConns) return;

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

function broadcastToAllUsers(data, excludeConnectionId) {
  connections.forEach((ws, connectionId) => {
    if (connectionId !== excludeConnectionId && ws.userId) {
      sendToClient(ws, data);
    }
  });
}

async function handleWebSocketMessage(ws, message) {
  try {
    const data = JSON.parse(message.toString());
    console.log(`📥 [WebSocket] Received message from ${ws.userId || 'unauthenticated'}:`, data);
    
    switch (data.type) {
      case 'auth':
        console.log(`🔑 [WebSocket] Authentication attempt:`, {
          userId: data.userId,
          email: data.email,
          connectionId: ws.connectionId
        });
        
        // Prefer userId over email for authentication
        const authId = data.userId || data.email;
        if (authId) {
          // Remove existing connection for this user
          const existingConnectionId = userConnections.get(authId);
          if (existingConnectionId && existingConnectionId !== ws.connectionId) {
            console.log(`🔄 [WebSocket] Removing existing connection for user ${authId}`);
            const existingWs = connections.get(existingConnectionId);
            if (existingWs) {
              existingWs.close();
            }
            connections.delete(existingConnectionId);
            onlineUsers.delete(authId);
          }
          
          ws.userId = authId;
          userConnections.set(authId, ws.connectionId);
          onlineUsers.add(authId);
          
          sendToClient(ws, { 
            type: 'auth_success', 
            userId: authId,
            connectionId: ws.connectionId 
          });
          
          // Send current online users to this user
          sendToClient(ws, {
            type: 'online_users',
            users: Array.from(onlineUsers)
          });
          
          // Broadcast that this user came online
          broadcastToAllUsers({
            type: 'user_joined',
            userId: authId
          }, ws.connectionId);
          
          console.log(`✅ [WebSocket] User authenticated: ${authId}, online users: ${onlineUsers.size}`);
          console.log(`👥 [WebSocket] Current online users:`, Array.from(onlineUsers));
        } else {
          console.log(`❌ [WebSocket] Authentication failed: no user ID or email provided`);
          sendToClient(ws, { type: 'auth_error', message: 'User ID or email required' });
        }
        break;

      case 'join_room':
        if (data.roomId && ws.connectionId) {
          if (!ws.rooms) ws.rooms = new Set();
          ws.rooms.add(data.roomId);
          
          if (!roomConnections.has(data.roomId)) {
            roomConnections.set(data.roomId, new Set());
          }
          roomConnections.get(data.roomId).add(ws.connectionId);
          
          sendToClient(ws, { type: 'room_joined', roomId: data.roomId });
          broadcastToRoom(data.roomId, { 
            type: 'user_joined', 
            userId: ws.userId, 
            roomId: data.roomId 
          }, ws.connectionId);
          console.log(`🚪 [WebSocket] User ${ws.userId} joined room ${data.roomId}`);
        }
        break;
        
      case 'leave_room':
        if (data.roomId && ws.connectionId && ws.rooms) {
          ws.rooms.delete(data.roomId);
          
          const roomConns = roomConnections.get(data.roomId);
          if (roomConns) {
            roomConns.delete(ws.connectionId);
            if (roomConns.size === 0) {
              roomConnections.delete(data.roomId);
            }
          }
          
          sendToClient(ws, { type: 'room_left', roomId: data.roomId });
          broadcastToRoom(data.roomId, {
            type: 'user_left',
            userId: ws.userId,
            roomId: data.roomId
          }, ws.connectionId);
          console.log(`🚪 [WebSocket] User ${ws.userId} left room ${data.roomId}`);
        }
        break;

      case 'send_message':
        console.log(`📤 [WebSocket] Processing send_message from ${ws.userId}:`, {
          receiverId: data.receiverId,
          content: data.content?.substring(0, 50) + '...',
          hasMessageData: !!data.messageData,
          projectId: data.projectId,
          bidId: data.bidId
        });

        // If messageData is provided (from HTTP API), use that; otherwise create a basic message
        const messageToSend = data.messageData || {
          id: `temp_${Date.now()}`,
          content: data.content,
          senderId: ws.userId,
          receiverId: data.receiverId,
          projectId: data.projectId,
          bidId: data.bidId,
          messageType: data.messageType || 'text',
          createdAt: new Date().toISOString(),
          isRead: false,
          sender: { id: ws.userId }
        };

        console.log('📡 [WebSocket] Broadcasting message:', {
          senderId: ws.userId,
          receiverId: data.receiverId,
          messageId: messageToSend.id,
          projectId: data.projectId,
          bidId: data.bidId,
          isReceiverOnline: userConnections.has(data.receiverId)
        });

        // Broadcast the message to the receiver
        if (data.receiverId) {
          console.log(`🔍 [WebSocket] Looking up receiver: ${data.receiverId}`);
          console.log(`🔍 [WebSocket] Available users:`, Array.from(userConnections.keys()));
          console.log(`🔍 [WebSocket] User connections map:`, Object.fromEntries(userConnections));
          
          const sent = sendToUser(data.receiverId, {
            type: 'new_message',
            message: messageToSend
          });
          console.log(`${sent ? '✅' : '❌'} [WebSocket] Message to user ${data.receiverId}: ${sent ? 'sent' : 'failed'}`);
        }

        // If it's a project/bid message, also broadcast to room
        if (data.projectId || data.bidId) {
          const roomId = data.bidId ? `bid_${data.bidId}` : `project_${data.projectId}`;
          broadcastToRoom(roomId, {
            type: 'new_message',
            message: messageToSend
          }, ws.connectionId);
          console.log(`📢 [WebSocket] Broadcasted to room ${roomId}`);
        }

        // Send confirmation to sender
        sendToClient(ws, { 
          type: 'message_sent', 
          messageId: messageToSend.id,
          success: true
        });
        console.log('✅ [WebSocket] Sent message_sent confirmation');
        break;

      case 'typing_start':
      case 'typing_stop':
        if (data.receiverId) {
          sendToUser(data.receiverId, {
            type: data.type,
            userId: ws.userId
          });
        }
        if (data.roomId) {
          broadcastToRoom(data.roomId, {
            type: data.type,
            userId: ws.userId
          }, ws.connectionId);
        }
        break;

      case 'ping':
        sendToClient(ws, { type: 'pong' });
        break;

      default:
        sendToClient(ws, { type: 'error', message: 'Unknown message type' });
    }
  } catch (error) {
    console.error('Error handling WebSocket message:', error);
    sendToClient(ws, { type: 'error', message: 'Invalid message format' });
  }
}

function handleWebSocketConnection(ws) {
  const connectionId = generateConnectionId();
  ws.connectionId = connectionId;
  ws.rooms = new Set();
  
  connections.set(connectionId, ws);
  console.log(`WebSocket client connected: ${connectionId}`);

  ws.on('message', (message) => {
    handleWebSocketMessage(ws, message);
  });

  ws.on('close', () => {
    // Clean up connections
    if (ws.userId) {
      userConnections.delete(ws.userId);
      onlineUsers.delete(ws.userId);
      
      // Broadcast that this user went offline
      broadcastToAllUsers({
        type: 'user_left',
        userId: ws.userId
      }, connectionId);
    }

    if (ws.rooms) {
      ws.rooms.forEach(roomId => {
        const roomConns = roomConnections.get(roomId);
        if (roomConns) {
          roomConns.delete(connectionId);
          if (roomConns.size === 0) {
            roomConnections.delete(roomId);
          }
        }
        broadcastToRoom(roomId, {
          type: 'user_left',
          userId: ws.userId
        }, connectionId);
      });
    }

    connections.delete(connectionId);
    console.log(`WebSocket client disconnected: ${connectionId}, online users: ${onlineUsers.size}`);
  });

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

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

app.prepare().then(() => {
  const server = createServer(async (req, res) => {
    try {
      const parsedUrl = parse(req.url, true);
      await handle(req, res, parsedUrl);
    } catch (err) {
      console.error('Error occurred handling', req.url, err);
      res.statusCode = 500;
      res.end('internal server error');
    }
  });

  // Create WebSocket server
  const wss = new WebSocketServer({ 
    server,
    path: '/api/websocket',
    perMessageDeflate: false,
    clientTracking: true,
    verifyClient: (info) => {
      console.log('🔍 [WebSocket] Verifying client:', {
        origin: info.origin,
        secure: info.secure,
        protocol: info.req.headers['sec-websocket-protocol'],
        userAgent: info.req.headers['user-agent']
      });
      return true; // Allow all connections for now
    }
  });

  wss.on('connection', (ws, request) => {
    console.log('🔌 [WebSocket] New connection from:', request.url, 'Client IP:', request.socket.remoteAddress);
    handleWebSocketConnection(ws);
  });

  wss.on('error', (error) => {
    console.error('❌ [WebSocket Server] Error:', error);
  });

  server.once('error', (err) => {
    console.error('❌ [Server] Error:', err);
    process.exit(1);
  });

  server.listen(port, () => {
    console.log(`🚀 [Server] Ready on http://${hostname}:${port}`);
    console.log(`🔌 [WebSocket] Server ready on ws://${hostname}:${port}/api/websocket`);
    console.log(`📡 [WebSocket] Connections: ${wss.clients.size}`);
  });
});