/**
 * Messaging Service
 * Handles all messaging operations including conversations, messages, and real-time delivery
 */

const db = require('../database/db');
const crypto = require('crypto');

class MessagingService {
  
  /**
   * Get all conversations for a user
   */
  async getUserConversations(userId, { limit = 50, offset = 0 } = {}) {
    const result = await db.query(
      `SELECT 
        c.*,
        cp.last_read_at,
        cp.notification_settings,
        (
          SELECT COUNT(*)
          FROM messages m
          WHERE m.conversation_id = c.id
            AND m.created_at > COALESCE(cp.last_read_at, c.created_at)
            AND m.sender_id != $1
            AND m.deleted_at IS NULL
        ) as unread_count,
        (
          SELECT json_build_object(
            'id', m.id,
            'content', m.content_encrypted,
            'sender_id', m.sender_id,
            'created_at', m.created_at
          )
          FROM messages m
          WHERE m.conversation_id = c.id
            AND m.deleted_at IS NULL
          ORDER BY m.created_at DESC
          LIMIT 1
        ) as last_message,
        (
          SELECT json_agg(
            json_build_object(
              'user_id', u.id,
              'username', u.username,
              'verified_domain', u.verified_domain,
              'avatar_url', u.avatar_url,
              'status', u.status
            )
          )
          FROM conversation_participants cp2
          JOIN users u ON u.id = cp2.user_id
          WHERE cp2.conversation_id = c.id
            AND cp2.user_id != $1
        ) as other_participants
      FROM conversations c
      JOIN conversation_participants cp ON cp.conversation_id = c.id
      WHERE cp.user_id = $1
        AND c.is_archived = false
      ORDER BY c.updated_at DESC
      LIMIT $2 OFFSET $3`,
      [userId, limit, offset]
    );
    
    return result.rows.map(row => ({
      id: row.id,
      type: row.type,
      title: row.title,
      description: row.description,
      avatarUrl: row.avatar_url,
      unreadCount: parseInt(row.unread_count),
      lastMessage: row.last_message,
      otherParticipants: row.other_participants || [],
      updatedAt: row.updated_at,
      createdAt: row.created_at
    }));
  }
  
  /**
   * Get or create a direct conversation between two users
   */
  async getOrCreateDirectConversation(userId1, userId2) {
    // Use the database function
    const result = await db.query(
      `SELECT get_or_create_direct_conversation($1, $2) as conversation_id`,
      [userId1, userId2]
    );
    
    const conversationId = result.rows[0].conversation_id;
    
    // Get full conversation details
    return await this.getConversation(conversationId, userId1);
  }
  
  /**
   * Create a group conversation
   */
  async createGroupConversation(creatorId, { title, description, participantIds }) {
    const client = await db.getClient();
    
    try {
      await client.query('BEGIN');
      
      // Create conversation
      const convResult = await client.query(
        `INSERT INTO conversations (type, title, description, created_by)
         VALUES ('group', $1, $2, $3)
         RETURNING *`,
        [title, description, creatorId]
      );
      
      const conversation = convResult.rows[0];
      
      // Add creator as admin
      await client.query(
        `INSERT INTO conversation_participants (conversation_id, user_id, role)
         VALUES ($1, $2, 'admin')`,
        [conversation.id, creatorId]
      );
      
      // Add other participants as members
      if (participantIds && participantIds.length > 0) {
        const values = participantIds
          .filter(id => id !== creatorId)
          .map((id, i) => `($1, $${i + 2}, 'member')`)
          .join(', ');
        
        if (values) {
          await client.query(
            `INSERT INTO conversation_participants (conversation_id, user_id, role)
             VALUES ${values}`,
            [conversation.id, ...participantIds.filter(id => id !== creatorId)]
          );
        }
      }
      
      await client.query('COMMIT');
      
      return await this.getConversation(conversation.id, creatorId);
      
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }
  
  /**
   * Get conversation details
   */
  async getConversation(conversationId, userId) {
    const result = await db.query(
      `SELECT 
        c.*,
        cp.role,
        cp.last_read_at,
        cp.notification_settings,
        (
          SELECT json_agg(
            json_build_object(
              'id', u.id,
              'username', u.username,
              'verified_domain', u.verified_domain,
              'avatar_url', u.avatar_url,
              'status', u.status,
              'role', cp2.role
            )
          )
          FROM conversation_participants cp2
          JOIN users u ON u.id = cp2.user_id
          WHERE cp2.conversation_id = c.id
        ) as participants
      FROM conversations c
      JOIN conversation_participants cp ON cp.conversation_id = c.id
      WHERE c.id = $1 AND cp.user_id = $2`,
      [conversationId, userId]
    );
    
    if (result.rows.length === 0) {
      throw new Error('Conversation not found or access denied');
    }
    
    const row = result.rows[0];
    
    return {
      id: row.id,
      type: row.type,
      title: row.title,
      description: row.description,
      avatarUrl: row.avatar_url,
      role: row.role,
      lastReadAt: row.last_read_at,
      notificationSettings: row.notification_settings,
      participants: row.participants || [],
      createdAt: row.created_at,
      updatedAt: row.updated_at
    };
  }
  
  /**
   * Get messages for a conversation
   */
  async getMessages(conversationId, userId, { limit = 50, before = null } = {}) {
    // Verify user is participant
    await this.verifyParticipant(conversationId, userId);
    
    let query = `
      SELECT 
        m.*,
        u.username as sender_username,
        u.verified_domain as sender_domain,
        u.avatar_url as sender_avatar,
        i.identifier as sender_identity,
        (
          SELECT json_agg(
            json_build_object(
              'user_id', mr.user_id,
              'emoji', mr.emoji
            )
          )
          FROM message_reactions mr
          WHERE mr.message_id = m.id
        ) as reactions
      FROM messages m
      JOIN users u ON u.id = m.sender_id
      LEFT JOIN identities i ON i.id = m.sender_identity_id
      WHERE m.conversation_id = $1
        AND m.deleted_at IS NULL
    `;
    
    const params = [conversationId];
    
    if (before) {
      query += ` AND m.created_at < $${params.length + 1}`;
      params.push(before);
    }
    
    query += ` ORDER BY m.created_at DESC LIMIT $${params.length + 1}`;
    params.push(limit);
    
    const result = await db.query(query, params);
    
    return result.rows.map(row => ({
      id: row.id,
      conversationId: row.conversation_id,
      senderId: row.sender_id,
      senderUsername: row.sender_username,
      senderDomain: row.sender_domain,
      senderAvatar: row.sender_avatar,
      senderIdentity: row.sender_identity,
      content: row.content_encrypted, // Client will decrypt
      contentType: row.content_type,
      metadata: row.metadata,
      replyToId: row.reply_to_id,
      reactions: row.reactions || [],
      editedAt: row.edited_at,
      createdAt: row.created_at
    })).reverse(); // Reverse to get chronological order
  }
  
  /**
   * Send a message
   */
  async sendMessage(userId, conversationId, { content, contentType = 'text', metadata = null, replyToId = null, identityId = null }) {
    // Validate required parameters
    if (!userId || typeof userId !== 'string') {
      throw new Error('Invalid userId parameter');
    }

    if (!conversationId || typeof conversationId !== 'string') {
      throw new Error('Invalid conversationId parameter');
    }

    if (!content || typeof content !== 'string' || content.trim().length === 0) {
      throw new Error('Message content cannot be empty');
    }

    if (content.length > 10000) {
      throw new Error('Message content exceeds maximum length of 10000 characters');
    }

    // Validate contentType
    const validContentTypes = ['text', 'image', 'file', 'audio', 'video', 'code'];
    if (!validContentTypes.includes(contentType)) {
      throw new Error(`Invalid contentType. Must be one of: ${validContentTypes.join(', ')}`);
    }

    // Validate metadata if provided
    if (metadata !== null && typeof metadata !== 'object') {
      throw new Error('metadata must be an object or null');
    }

    // Verify user is participant
    await this.verifyParticipant(conversationId, userId);

    // In production, content should already be encrypted by client
    // For now, we'll just store it as-is
    const contentEncrypted = content;
    
    const result = await db.query(
      `INSERT INTO messages (
        conversation_id, sender_id, sender_identity_id, 
        content_encrypted, content_type, metadata, reply_to_id
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *`,
      [conversationId, userId, identityId, contentEncrypted, contentType, metadata, replyToId]
    );
    
    const message = result.rows[0];
    
    // Get sender info
    const userResult = await db.query(
      `SELECT username, verified_domain, avatar_url FROM users WHERE id = $1`,
      [userId]
    );

    if (userResult.rows.length === 0) {
      throw new Error(`User ${userId} not found`);
    }

    const user = userResult.rows[0];

    return {
      id: message.id,
      conversationId: message.conversation_id,
      senderId: message.sender_id,
      senderUsername: user.username,
      senderDomain: user.verified_domain,
      senderAvatar: user.avatar_url,
      content: message.content_encrypted,
      contentType: message.content_type,
      metadata: message.metadata,
      replyToId: message.reply_to_id,
      reactions: [],
      createdAt: message.created_at
    };
  }
  
  /**
   * Edit a message
   */
  async editMessage(userId, messageId, newContent) {
    // Verify ownership
    const check = await db.query(
      `SELECT * FROM messages WHERE id = $1 AND sender_id = $2`,
      [messageId, userId]
    );
    
    if (check.rows.length === 0) {
      throw new Error('Message not found or access denied');
    }
    
    const result = await db.query(
      `UPDATE messages 
       SET content_encrypted = $1, edited_at = NOW()
       WHERE id = $2
       RETURNING *`,
      [newContent, messageId]
    );
    
    return result.rows[0];
  }
  
  /**
   * Delete a message (soft delete)
   */
  async deleteMessage(userId, messageId) {
    // Verify ownership
    const check = await db.query(
      `SELECT * FROM messages WHERE id = $1 AND sender_id = $2`,
      [messageId, userId]
    );
    
    if (check.rows.length === 0) {
      throw new Error('Message not found or access denied');
    }
    
    await db.query(
      `UPDATE messages SET deleted_at = NOW() WHERE id = $1`,
      [messageId]
    );
    
    return { success: true };
  }
  
  /**
   * Add reaction to message
   */
  async addReaction(userId, messageId, emoji) {
    // Verify user is participant of the conversation
    const msgResult = await db.query(
      `SELECT conversation_id FROM messages WHERE id = $1`,
      [messageId]
    );
    
    if (msgResult.rows.length === 0) {
      throw new Error('Message not found');
    }
    
    await this.verifyParticipant(msgResult.rows[0].conversation_id, userId);
    
    // Add reaction (or do nothing if already exists due to UNIQUE constraint)
    await db.query(
      `INSERT INTO message_reactions (message_id, user_id, emoji)
       VALUES ($1, $2, $3)
       ON CONFLICT (message_id, user_id, emoji) DO NOTHING`,
      [messageId, userId, emoji]
    );
    
    return { success: true };
  }
  
  /**
   * Remove reaction from message
   */
  async removeReaction(userId, messageId, emoji) {
    await db.query(
      `DELETE FROM message_reactions
       WHERE message_id = $1 AND user_id = $2 AND emoji = $3`,
      [messageId, userId, emoji]
    );
    
    return { success: true };
  }
  
  /**
   * Mark conversation as read
   */
  async markAsRead(userId, conversationId) {
    await db.query(
      `UPDATE conversation_participants
       SET last_read_at = NOW()
       WHERE conversation_id = $1 AND user_id = $2`,
      [conversationId, userId]
    );
    
    return { success: true };
  }
  
  /**
   * Add participants to group conversation
   */
  async addParticipants(userId, conversationId, participantIds) {
    // Verify user is admin or moderator
    await this.verifyRole(conversationId, userId, ['admin', 'moderator']);
    
    // Add participants
    for (const participantId of participantIds) {
      await db.query(
        `INSERT INTO conversation_participants (conversation_id, user_id, role)
         VALUES ($1, $2, 'member')
         ON CONFLICT (conversation_id, user_id) DO NOTHING`,
        [conversationId, participantId]
      );
    }
    
    return { success: true };
  }
  
  /**
   * Remove participant from group conversation
   */
  async removeParticipant(userId, conversationId, participantId) {
    // Verify user is admin or it's themselves leaving
    if (userId !== participantId) {
      await this.verifyRole(conversationId, userId, ['admin']);
    }
    
    await db.query(
      `DELETE FROM conversation_participants
       WHERE conversation_id = $1 AND user_id = $2`,
      [conversationId, participantId]
    );
    
    return { success: true };
  }
  
  /**
   * Search for users by domain/username
   */
  async searchUsers(query, currentUserId, limit = 10) {
    const result = await db.query(
      `SELECT id, username, verified_domain, avatar_url, status
       FROM users
       WHERE (username ILIKE $1 OR verified_domain ILIKE $1)
         AND id != $2
       LIMIT $3`,
      [`%${query}%`, currentUserId, limit]
    );
    
    return result.rows.map(row => ({
      id: row.id,
      username: row.username,
      verifiedDomain: row.verified_domain,
      avatarUrl: row.avatar_url,
      status: row.status
    }));
  }
  
  /**
   * Helper: Verify user is participant of conversation
   */
  async verifyParticipant(conversationId, userId) {
    const result = await db.query(
      `SELECT * FROM conversation_participants
       WHERE conversation_id = $1 AND user_id = $2`,
      [conversationId, userId]
    );
    
    if (result.rows.length === 0) {
      throw new Error('Access denied: Not a participant of this conversation');
    }
    
    return result.rows[0];
  }
  
  /**
   * Helper: Verify user has required role
   */
  async verifyRole(conversationId, userId, allowedRoles) {
    const result = await db.query(
      `SELECT role FROM conversation_participants
       WHERE conversation_id = $1 AND user_id = $2`,
      [conversationId, userId]
    );
    
    if (result.rows.length === 0 || !allowedRoles.includes(result.rows[0].role)) {
      throw new Error('Access denied: Insufficient permissions');
    }
    
    return true;
  }
}

module.exports = new MessagingService();
