import { PostgreSQLService } from '../database/PostgreSQLService';
import {
  MessengerRoom,
  MessengerMessage,
  MessengerFile,
  MessengerUser,
  CreateRoomRequest,
  MessengerParticipant,
} from '../types/messenger';

class MessengerService {
  /**
   * Get rooms for a user with last message and unread count
   */
  async getRooms(userId: string, companyCode: string): Promise<MessengerRoom[]> {
    const result = await PostgreSQLService.query(
      `SELECT r.*,
              m.content AS last_message,
              m.created_at AS last_message_at,
              m.sender_id AS last_sender_id,
              COALESCE(unread.cnt, 0)::int AS unread_count
       FROM messenger_rooms r
       INNER JOIN messenger_participants p ON p.room_id = r.id AND p.user_id = $1
       LEFT JOIN LATERAL (
         SELECT content, created_at, sender_id
         FROM messenger_messages
         WHERE room_id = r.id
         ORDER BY created_at DESC
         LIMIT 1
       ) m ON true
       LEFT JOIN LATERAL (
         SELECT COUNT(*)::int AS cnt
         FROM messenger_messages
         WHERE room_id = r.id
           AND created_at > p.last_read_at
           AND sender_id != $1
       ) unread ON true
       WHERE r.company_code = $2
       ORDER BY COALESCE(m.created_at, r.created_at) DESC`,
      [userId, companyCode]
    );

    // Attach participants to each room
    const rooms: MessengerRoom[] = result.rows;
    if (rooms.length > 0) {
      const roomIds = rooms.map((r) => r.id);
      const partResult = await PostgreSQLService.query(
        `SELECT mp.*, ui.user_name, ui.dept_name,
                CASE WHEN ui.photo IS NOT NULL THEN encode(ui.photo, 'base64') ELSE NULL END AS photo
         FROM messenger_participants mp
         LEFT JOIN user_info ui ON ui.user_id = mp.user_id AND ui.company_code = mp.company_code
         WHERE mp.room_id = ANY($1)`,
        [roomIds]
      );
      const partMap = new Map<number, MessengerParticipant[]>();
      for (const p of partResult.rows) {
        if (!partMap.has(p.room_id)) partMap.set(p.room_id, []);
        partMap.get(p.room_id)!.push(p);
      }
      for (const room of rooms) {
        room.participants = partMap.get(room.id) || [];
      }
    }

    return rooms;
  }

  /**
   * Create a room. For DM, return existing room if one already exists between the two users.
   */
  async createRoom(
    creatorId: string,
    companyCode: string,
    data: CreateRoomRequest
  ): Promise<MessengerRoom> {
    // DM duplicate check
    if (data.room_type === 'dm' && data.participant_ids.length === 1) {
      const otherUserId = data.participant_ids[0];
      const existing = await PostgreSQLService.query(
        `SELECT r.* FROM messenger_rooms r
         WHERE r.company_code = $1 AND r.room_type = 'dm'
           AND EXISTS (SELECT 1 FROM messenger_participants WHERE room_id = r.id AND user_id = $2)
           AND EXISTS (SELECT 1 FROM messenger_participants WHERE room_id = r.id AND user_id = $3)
           AND (SELECT COUNT(*) FROM messenger_participants WHERE room_id = r.id) = 2
         LIMIT 1`,
        [companyCode, creatorId, otherUserId]
      );
      if (existing.rows.length > 0) {
        return existing.rows[0];
      }
    }

    // Create room
    const roomResult = await PostgreSQLService.query(
      `INSERT INTO messenger_rooms (company_code, room_type, room_name, created_by)
       VALUES ($1, $2, $3, $4)
       RETURNING *`,
      [companyCode, data.room_type, data.room_name || null, creatorId]
    );
    const room: MessengerRoom = roomResult.rows[0];

    // Add participants (creator + others)
    const allParticipants = [creatorId, ...data.participant_ids.filter((id) => id !== creatorId)];
    for (const uid of allParticipants) {
      await PostgreSQLService.query(
        `INSERT INTO messenger_participants (room_id, user_id, company_code)
         VALUES ($1, $2, $3)
         ON CONFLICT (room_id, user_id) DO NOTHING`,
        [room.id, uid, companyCode]
      );
    }

    return room;
  }

  /**
   * Get messages with cursor-based pagination
   */
  async getMessages(
    roomId: number,
    userId: string,
    companyCode: string,
    limit: number = 50,
    before?: number
  ): Promise<MessengerMessage[]> {
    let query: string;
    let params: any[];

    if (before) {
      query = `SELECT msg.*,
                      ui.user_name AS sender_name,
                      CASE WHEN ui.photo IS NOT NULL THEN encode(ui.photo, 'base64') ELSE NULL END AS sender_photo,
                      COALESCE(tc.thread_count, 0)::int AS thread_count
               FROM messenger_messages msg
               LEFT JOIN user_info ui ON ui.user_id = msg.sender_id AND ui.company_code = msg.company_code
               LEFT JOIN LATERAL (
                 SELECT COUNT(*)::int AS thread_count
                 FROM messenger_messages
                 WHERE parent_message_id = msg.id
               ) tc ON true
               WHERE msg.room_id = $1 AND msg.company_code = $2 AND msg.id < $3
               ORDER BY msg.created_at DESC
               LIMIT $4`;
      params = [roomId, companyCode, before, limit];
    } else {
      query = `SELECT msg.*,
                      ui.user_name AS sender_name,
                      CASE WHEN ui.photo IS NOT NULL THEN encode(ui.photo, 'base64') ELSE NULL END AS sender_photo,
                      COALESCE(tc.thread_count, 0)::int AS thread_count
               FROM messenger_messages msg
               LEFT JOIN user_info ui ON ui.user_id = msg.sender_id AND ui.company_code = msg.company_code
               LEFT JOIN LATERAL (
                 SELECT COUNT(*)::int AS thread_count
                 FROM messenger_messages
                 WHERE parent_message_id = msg.id
               ) tc ON true
               WHERE msg.room_id = $1 AND msg.company_code = $2
               ORDER BY msg.created_at DESC
               LIMIT $3`;
      params = [roomId, companyCode, limit];
    }

    const result = await PostgreSQLService.query(query, params);
    // Reverse so messages are in chronological order (query uses DESC for cursor pagination)
    const messages: MessengerMessage[] = result.rows.reverse();

    // Attach reactions and files
    if (messages.length > 0) {
      const msgIds = messages.map((m) => m.id);

      const [reactionsResult, filesResult] = await Promise.all([
        PostgreSQLService.query(
          `SELECT * FROM messenger_reactions WHERE message_id = ANY($1)`,
          [msgIds]
        ),
        PostgreSQLService.query(
          `SELECT * FROM messenger_files WHERE message_id = ANY($1)`,
          [msgIds]
        ),
      ]);

      const reactionsMap = new Map<number, any[]>();
      for (const r of reactionsResult.rows) {
        if (!reactionsMap.has(r.message_id)) reactionsMap.set(r.message_id, []);
        reactionsMap.get(r.message_id)!.push(r);
      }

      const filesMap = new Map<number, any[]>();
      for (const f of filesResult.rows) {
        if (!filesMap.has(f.message_id)) filesMap.set(f.message_id, []);
        filesMap.get(f.message_id)!.push(f);
      }

      for (const msg of messages) {
        msg.reactions = reactionsMap.get(msg.id) || [];
        msg.files = filesMap.get(msg.id) || [];
      }
    }

    return messages;
  }

  /**
   * Send a message and return the saved message
   */
  async sendMessage(
    roomId: number,
    senderId: string,
    companyCode: string,
    content: string,
    messageType: string = 'text',
    parentMessageId?: number
  ): Promise<MessengerMessage> {
    const result = await PostgreSQLService.query(
      `INSERT INTO messenger_messages (room_id, sender_id, company_code, content, message_type, parent_message_id)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [roomId, senderId, companyCode, content, messageType, parentMessageId || null]
    );

    // Update room's updated_at
    await PostgreSQLService.query(
      `UPDATE messenger_rooms SET updated_at = NOW() WHERE id = $1`,
      [roomId]
    );

    // Get sender info
    const userResult = await PostgreSQLService.query(
      `SELECT user_name,
              CASE WHEN photo IS NOT NULL THEN encode(photo, 'base64') ELSE NULL END AS photo
       FROM user_info WHERE user_id = $1 AND company_code = $2`,
      [senderId, companyCode]
    );

    const message = result.rows[0];
    if (userResult.rows.length > 0) {
      message.sender_name = userResult.rows[0].user_name;
      message.sender_photo = userResult.rows[0].photo;
    }
    message.reactions = [];
    message.files = [];

    return message;
  }

  /**
   * Mark messages as read
   */
  async markAsRead(roomId: number, userId: string): Promise<void> {
    await PostgreSQLService.query(
      `UPDATE messenger_participants SET last_read_at = NOW()
       WHERE room_id = $1 AND user_id = $2`,
      [roomId, userId]
    );
  }

  /**
   * Get company users for user picker
   */
  async getCompanyUsers(companyCode: string, excludeUserId?: string): Promise<MessengerUser[]> {
    let query: string;
    let params: any[];

    if (excludeUserId) {
      query = `SELECT user_id, user_name, dept_name, email,
                      CASE WHEN photo IS NOT NULL THEN encode(photo, 'base64') ELSE NULL END AS photo
               FROM user_info
               WHERE company_code = $1 AND user_id != $2
               ORDER BY user_name`;
      params = [companyCode, excludeUserId];
    } else {
      query = `SELECT user_id, user_name, dept_name, email,
                      CASE WHEN photo IS NOT NULL THEN encode(photo, 'base64') ELSE NULL END AS photo
               FROM user_info
               WHERE company_code = $1
               ORDER BY user_name`;
      params = [companyCode];
    }

    const result = await PostgreSQLService.query(query, params);
    return result.rows;
  }

  /**
   * Add a reaction to a message
   */
  async addReaction(messageId: number, userId: string, emoji: string): Promise<void> {
    await PostgreSQLService.query(
      `INSERT INTO messenger_reactions (message_id, user_id, emoji)
       VALUES ($1, $2, $3)
       ON CONFLICT (message_id, user_id, emoji) DO NOTHING`,
      [messageId, userId, emoji]
    );
  }

  /**
   * Remove a reaction from a message
   */
  async removeReaction(messageId: number, userId: string, emoji: string): Promise<void> {
    await PostgreSQLService.query(
      `DELETE FROM messenger_reactions
       WHERE message_id = $1 AND user_id = $2 AND emoji = $3`,
      [messageId, userId, emoji]
    );
  }

  /**
   * Get total unread message count for badge
   */
  async getUnreadCount(userId: string, companyCode: string): Promise<number> {
    const result = await PostgreSQLService.query(
      `SELECT COALESCE(SUM(cnt), 0)::int AS total_unread
       FROM (
         SELECT COUNT(*) AS cnt
         FROM messenger_participants p
         INNER JOIN messenger_messages m ON m.room_id = p.room_id
           AND m.created_at > p.last_read_at
           AND m.sender_id != $1
         WHERE p.user_id = $1 AND p.company_code = $2
         GROUP BY p.room_id
       ) sub`,
      [userId, companyCode]
    );
    return result.rows[0]?.total_unread || 0;
  }

  /**
   * Save file info for a message
   */
  async saveFile(
    messageId: number,
    fileInfo: { originalName: string; storedName: string; filePath: string; fileSize: number; mimeType: string }
  ): Promise<MessengerFile> {
    const result = await PostgreSQLService.query(
      `INSERT INTO messenger_files (message_id, original_name, stored_name, file_path, file_size, mime_type)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [messageId, fileInfo.originalName, fileInfo.storedName, fileInfo.filePath, fileInfo.fileSize, fileInfo.mimeType]
    );
    return result.rows[0];
  }

  /**
   * Get room by ID with participants
   */
  async getRoomById(roomId: number, companyCode: string): Promise<MessengerRoom | null> {
    const result = await PostgreSQLService.query(
      `SELECT * FROM messenger_rooms WHERE id = $1 AND company_code = $2`,
      [roomId, companyCode]
    );
    if (result.rows.length === 0) return null;

    const room: MessengerRoom = result.rows[0];

    const partResult = await PostgreSQLService.query(
      `SELECT mp.*, ui.user_name, ui.dept_name,
              CASE WHEN ui.photo IS NOT NULL THEN encode(ui.photo, 'base64') ELSE NULL END AS photo
       FROM messenger_participants mp
       LEFT JOIN user_info ui ON ui.user_id = mp.user_id AND ui.company_code = mp.company_code
       WHERE mp.room_id = $1`,
      [roomId]
    );
    room.participants = partResult.rows;

    return room;
  }

  /**
   * Update room name
   */
  async updateRoom(roomId: number, companyCode: string, roomName: string): Promise<MessengerRoom | null> {
    const result = await PostgreSQLService.query(
      `UPDATE messenger_rooms SET room_name = $1, updated_at = NOW()
       WHERE id = $2 AND company_code = $3
       RETURNING *`,
      [roomName, roomId, companyCode]
    );
    return result.rows.length > 0 ? result.rows[0] : null;
  }

  /**
   * Get file by ID
   */
  async getFileById(fileId: number): Promise<MessengerFile | null> {
    const result = await PostgreSQLService.query(
      `SELECT * FROM messenger_files WHERE id = $1`,
      [fileId]
    );
    return result.rows.length > 0 ? result.rows[0] : null;
  }

  /**
   * Get participant room IDs for socket join
   */
  async getUserRoomIds(userId: string, companyCode: string): Promise<number[]> {
    const result = await PostgreSQLService.query(
      `SELECT room_id FROM messenger_participants
       WHERE user_id = $1 AND company_code = $2`,
      [userId, companyCode]
    );
    return result.rows.map((r: any) => r.room_id);
  }
}

export const messengerService = new MessengerService();
