/**
 * Socket.io Service
 * Handles real-time communication for messaging, presence, and calls
 */

const { Server } = require('socket.io');
const jwt = require('jsonwebtoken');
const db = require('../database/db');

class SocketService {
  constructor() {
    this.io = null;
    this.userSockets = new Map(); // userId -> Set of socket IDs
  }
  
  /**
   * Initialize Socket.io server
   */
  initialize(httpServer) {
    this.io = new Server(httpServer, {
      cors: {
        origin: process.env.FRONTEND_URL || 'http://localhost:5173',
        credentials: true
      }
    });
    
    // Authentication middleware
    this.io.use(async (socket, next) => {
      try {
        const token = socket.handshake.auth.token;
        
        if (!token) {
          return next(new Error('Authentication required'));
        }
        
        // Verify JWT
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        
        // Get user from database
        const result = await db.query(
          'SELECT id, username, verified_domain FROM users WHERE id = $1',
          [decoded.userId]
        );
        
        if (result.rows.length === 0) {
          return next(new Error('User not found'));
        }
        
        socket.user = result.rows[0];
        next();
      } catch (error) {
        next(new Error('Invalid token'));
      }
    });
    
    // Connection handler
    this.io.on('connection', (socket) => {
      this.handleConnection(socket);
    });
    
    console.log('✓ Socket.io initialized');
    
    return this.io;
  }
  
  /**
   * Handle new socket connection
   */
  handleConnection(socket) {
    const userId = socket.user.id;
    
    console.log(`User connected: ${socket.user.username} (${userId})`);
    
    // Track user's socket
    if (!this.userSockets.has(userId)) {
      this.userSockets.set(userId, new Set());
    }
    this.userSockets.get(userId).add(socket.id);
    
    // Update user status to online
    this.updateUserStatus(userId, 'online');
    
    // Join user's personal room (for direct messages)
    socket.join(`user:${userId}`);
    
    // Join all conversations user is part of
    this.joinUserConversations(socket, userId);
    
    // Handle events with error handling
    socket.on('join_conversation', async (data) => {
      try {
        await this.handleJoinConversation(socket, data);
      } catch (error) {
        console.error('Error handling join_conversation:', error);
        socket.emit('error', { event: 'join_conversation', message: error.message });
      }
    });

    socket.on('leave_conversation', async (data) => {
      try {
        await this.handleLeaveConversation(socket, data);
      } catch (error) {
        console.error('Error handling leave_conversation:', error);
        socket.emit('error', { event: 'leave_conversation', message: error.message });
      }
    });

    socket.on('typing_start', async (data) => {
      try {
        await this.handleTypingStart(socket, data);
      } catch (error) {
        console.error('Error handling typing_start:', error);
        socket.emit('error', { event: 'typing_start', message: error.message });
      }
    });

    socket.on('typing_stop', async (data) => {
      try {
        await this.handleTypingStop(socket, data);
      } catch (error) {
        console.error('Error handling typing_stop:', error);
        socket.emit('error', { event: 'typing_stop', message: error.message });
      }
    });

    socket.on('call_signal', async (data) => {
      try {
        await this.handleCallSignal(socket, data);
      } catch (error) {
        console.error('Error handling call_signal:', error);
        socket.emit('error', { event: 'call_signal', message: error.message });
      }
    });

    // Call signaling events
    socket.on('call:offer', async (data) => {
      try {
        await this.handleCallOffer(socket, data);
      } catch (error) {
        console.error('Error handling call:offer:', error);
        socket.emit('call:error', { type: 'offer', message: error.message });
      }
    });

    socket.on('call:answer', async (data) => {
      try {
        await this.handleCallAnswer(socket, data);
      } catch (error) {
        console.error('Error handling call:answer:', error);
        socket.emit('call:error', { type: 'answer', message: error.message });
      }
    });

    socket.on('call:ice-candidate', async (data) => {
      try {
        await this.handleIceCandidate(socket, data);
      } catch (error) {
        console.error('Error handling call:ice-candidate:', error);
        socket.emit('call:error', { type: 'ice-candidate', message: error.message });
      }
    });

    // Disconnect handler
    socket.on('disconnect', async () => {
      try {
        await this.handleDisconnect(socket, userId);
      } catch (error) {
        console.error('Error handling disconnect:', error);
        // Don't emit error as socket is disconnecting
      }
    });
  }
  
  /**
   * Join all conversations user is part of
   */
  async joinUserConversations(socket, userId) {
    try {
      const result = await db.query(
        `SELECT conversation_id FROM conversation_participants WHERE user_id = $1`,
        [userId]
      );
      
      result.rows.forEach(row => {
        socket.join(row.conversation_id);
      });
      
      console.log(`User ${userId} joined ${result.rows.length} conversations`);
    } catch (error) {
      console.error('Error joining conversations:', error);
    }
  }
  
  /**
   * Handle join conversation event
   */
  handleJoinConversation(socket, data) {
    const { conversationId } = data;
    socket.join(conversationId);
    console.log(`User ${socket.user.id} joined conversation ${conversationId}`);
  }
  
  /**
   * Handle leave conversation event
   */
  handleLeaveConversation(socket, data) {
    const { conversationId } = data;
    socket.leave(conversationId);
    console.log(`User ${socket.user.id} left conversation ${conversationId}`);
  }
  
  /**
   * Handle typing start event
   */
  handleTypingStart(socket, data) {
    const { conversationId } = data;
    socket.to(conversationId).emit('user_typing', {
      conversationId,
      userId: socket.user.id,
      username: socket.user.username
    });
  }
  
  /**
   * Handle typing stop event
   */
  handleTypingStop(socket, data) {
    const { conversationId } = data;
    socket.to(conversationId).emit('user_stopped_typing', {
      conversationId,
      userId: socket.user.id
    });
  }
  
  /**
   * Handle WebRTC call signaling
   */
  handleCallSignal(socket, data) {
    const { targetUserId, signal, type } = data;
    
    // Send signal to target user
    this.io.to(`user:${targetUserId}`).emit('call_signal', {
      fromUserId: socket.user.id,
      fromUsername: socket.user.username,
      signal,
      type
    });
  }
  
  /**
   * Handle disconnect
   */
  handleDisconnect(socket, userId) {
    console.log(`User disconnected: ${socket.user.username} (${userId})`);
    
    // Remove socket from tracking
    if (this.userSockets.has(userId)) {
      this.userSockets.get(userId).delete(socket.id);
      
      // If no more sockets for this user, mark as offline
      if (this.userSockets.get(userId).size === 0) {
        this.userSockets.delete(userId);
        this.updateUserStatus(userId, 'offline');
      }
    }
  }
  
  /**
   * Update user status in database
   */
  async updateUserStatus(userId, status) {
    try {
      await db.query(
        'UPDATE users SET status = $1, last_seen_at = NOW() WHERE id = $2',
        [status, userId]
      );
      
      // Broadcast status change to all user's contacts
      this.broadcastStatusChange(userId, status);
    } catch (error) {
      console.error('Error updating user status:', error);
    }
  }
  
  /**
   * Broadcast status change to user's contacts
   */
  async broadcastStatusChange(userId, status) {
    try {
      // Get all users who have conversations with this user
      const result = await db.query(
        `SELECT DISTINCT cp2.user_id
         FROM conversation_participants cp1
         JOIN conversation_participants cp2 ON cp2.conversation_id = cp1.conversation_id
         WHERE cp1.user_id = $1 AND cp2.user_id != $1`,
        [userId]
      );
      
      // Emit to each contact
      result.rows.forEach(row => {
        this.io.to(`user:${row.user_id}`).emit('user_status_changed', {
          userId,
          status
        });
      });
    } catch (error) {
      console.error('Error broadcasting status change:', error);
    }
  }
  
  /**
   * Send message to conversation (called from REST API)
   */
  sendMessage(conversationId, message) {
    this.io.to(conversationId).emit('new_message', message);
  }
  
  /**
   * Notify user of new conversation
   */
  notifyNewConversation(userId, conversation) {
    this.io.to(`user:${userId}`).emit('new_conversation', conversation);
  }
  
  /**
   * Get online status of users
   */
  getOnlineUsers() {
    return Array.from(this.userSockets.keys());
  }
  
  /**
   * Check if user is online
   */
  isUserOnline(userId) {
    return this.userSockets.has(userId);
  }
  
  /**
   * Handle WebRTC offer
   */
  handleCallOffer(socket, data) {
    const { callId, targetUserId, offer } = data;
    
    console.log(`Call offer from ${socket.user.id} to ${targetUserId}`);
    
    // Forward offer to target user
    this.io.to(`user:${targetUserId}`).emit('call:offer', {
      callId: callId,
      fromUserId: socket.user.id,
      offer: offer
    });
  }
  
  /**
   * Handle WebRTC answer
   */
  handleCallAnswer(socket, data) {
    const { callId, targetUserId, answer } = data;
    
    console.log(`Call answer from ${socket.user.id} to ${targetUserId}`);
    
    // Forward answer to initiator
    this.io.to(`user:${targetUserId}`).emit('call:answer', {
      callId: callId,
      fromUserId: socket.user.id,
      answer: answer
    });
  }
  
  /**
   * Handle ICE candidate
   */
  handleIceCandidate(socket, data) {
    const { callId, targetUserId, candidate } = data;
    
    // Forward ICE candidate to target user
    this.io.to(`user:${targetUserId}`).emit('call:ice-candidate', {
      callId: callId,
      fromUserId: socket.user.id,
      candidate: candidate
    });
  }
  
  /**
   * Notify user of incoming call
   */
  notifyIncomingCall(userId, callData) {
    this.io.to(`user:${userId}`).emit('call:incoming', callData);
  }
  
  /**
   * Notify users that call has ended
   */
  notifyCallEnded(callId, participantIds, reason, endedBy) {
    participantIds.forEach(userId => {
      this.io.to(`user:${userId}`).emit('call:ended', {
        callId: callId,
        reason: reason,
        endedBy: endedBy
      });
    });
  }
  
  /**
   * Notify participant joined group call
   */
  notifyParticipantJoined(callId, participantIds, newParticipant) {
    participantIds.forEach(userId => {
      this.io.to(`user:${userId}`).emit('call:participant-joined', {
        callId: callId,
        userId: newParticipant.userId,
        userName: newParticipant.userName,
        userIdentifier: newParticipant.userIdentifier
      });
    });
  }
  
  /**
   * Notify participant left group call
   */
  notifyParticipantLeft(callId, participantIds, leftUserId) {
    participantIds.forEach(userId => {
      this.io.to(`user:${userId}`).emit('call:participant-left', {
        callId: callId,
        userId: leftUserId
      });
    });
  }
  
  /**
   * Notify media state changed
   */
  notifyMediaStateChanged(callId, participantIds, userId, mediaState) {
    participantIds.forEach(participantId => {
      if (participantId !== userId) {
        this.io.to(`user:${participantId}`).emit('call:media-state-changed', {
          callId: callId,
          userId: userId,
          mediaState: mediaState
        });
      }
    });
  }
}

module.exports = new SocketService();
