/**
 * Internal Messaging System
 * IM between users within the system
 */

import { prisma } from '@/lib/db';
import { auditLogger } from './audit-logger';

export interface MessageInput {
  content: string;
  contentType?: 'text' | 'file' | 'system';
  attachments?: any[];
  replyToId?: string;
}

/**
 * Create or get a direct conversation between two users
 */
export async function getOrCreateDirectConversation(
  userId1: string,
  userId2: string
): Promise<string> {
  // Check if direct conversation already exists
  const existing = await prisma.conversation.findFirst({
    where: {
      type: 'direct',
      AND: [
        { participants: { some: { userId: userId1 } } },
        { participants: { some: { userId: userId2 } } },
      ],
    },
  });

  if (existing) return existing.id;

  // Create new direct conversation
  const conversation = await prisma.conversation.create({
    data: {
      type: 'direct',
      createdById: userId1,
      participants: {
        create: [
          { userId: userId1, role: 'member' },
          { userId: userId2, role: 'member' },
        ],
      },
    },
  });

  return conversation.id;
}

/**
 * Create a group conversation
 */
export async function createGroupConversation(
  name: string,
  createdById: string,
  memberIds: string[],
  description?: string
): Promise<string> {
  const allMembers = [...new Set([createdById, ...memberIds])];

  const conversation = await prisma.conversation.create({
    data: {
      type: 'group',
      name,
      description,
      createdById,
      participants: {
        create: allMembers.map((uid) => ({
          userId: uid,
          role: uid === createdById ? 'admin' : 'member',
        })),
      },
    },
  });

  await auditLogger
    .setContext({ userId: createdById })
    .logCrud('create', 'conversation', conversation.id, {
      newState: { name, type: 'group', memberCount: allMembers.length },
    });

  return conversation.id;
}

/**
 * Create a channel (public conversation)
 */
export async function createChannel(
  name: string,
  createdById: string,
  description?: string
): Promise<string> {
  const conversation = await prisma.conversation.create({
    data: {
      type: 'channel',
      name,
      description,
      createdById,
      participants: {
        create: [{ userId: createdById, role: 'admin' }],
      },
    },
  });

  await auditLogger
    .setContext({ userId: createdById })
    .logCrud('create', 'conversation', conversation.id, {
      newState: { name, type: 'channel' },
    });

  return conversation.id;
}

/**
 * Send a message
 */
export async function sendMessage(
  conversationId: string,
  senderId: string,
  input: MessageInput
): Promise<string> {
  // Verify sender is a participant
  const participant = await prisma.conversationParticipant.findUnique({
    where: { conversationId_userId: { conversationId, userId: senderId } },
  });

  if (!participant || participant.leftAt) {
    throw new Error('User is not a member of this conversation');
  }

  const message = await prisma.internalMessage.create({
    data: {
      conversationId,
      senderId,
      content: input.content,
      contentType: input.contentType || 'text',
      attachments: input.attachments || [],
      replyToId: input.replyToId,
    },
  });

  // Update conversation last message time
  await prisma.conversation.update({
    where: { id: conversationId },
    data: { lastMessageAt: new Date() },
  });

  // Create notifications for other participants
  const otherParticipants = await prisma.conversationParticipant.findMany({
    where: {
      conversationId,
      userId: { not: senderId },
      leftAt: null,
      isMuted: false,
    },
  });

  if (otherParticipants.length > 0) {
    const sender = await prisma.gfsUser.findUnique({
      where: { id: senderId },
      select: { name: true, email: true },
    });

    await prisma.userNotification.createMany({
      data: otherParticipants.map((p: any) => ({
        userId: p.userId,
        type: 'message',
        title: `New message from ${sender?.name || 'User'}`,
        message: input.content.substring(0, 100),
        link: `/messages/${conversationId}`,
        data: { conversationId, messageId: message.id },
      })),
    });
  }

  await auditLogger
    .setContext({ userId: senderId })
    .log({
      action: 'message_send',
      resource: 'message',
      resourceId: message.id,
      description: `Message sent to conversation ${conversationId}`,
      metadata: { conversationId, contentType: input.contentType },
    });

  return message.id;
}

/**
 * Get messages for a conversation
 */
export async function getMessages(
  conversationId: string,
  userId: string,
  options?: {
    limit?: number;
    before?: string;
    after?: string;
  }
) {
  // Verify user is a participant
  const participant = await prisma.conversationParticipant.findUnique({
    where: { conversationId_userId: { conversationId, userId } },
  });

  if (!participant) {
    throw new Error('User is not a member of this conversation');
  }

  const where: any = {
    conversationId,
    isDeleted: false,
  };

  if (options?.before) {
    const beforeMsg = await prisma.internalMessage.findUnique({ where: { id: options.before } });
    if (beforeMsg) {
      where.createdAt = { lt: beforeMsg.createdAt };
    }
  }

  if (options?.after) {
    const afterMsg = await prisma.internalMessage.findUnique({ where: { id: options.after } });
    if (afterMsg) {
      where.createdAt = { ...where.createdAt, gt: afterMsg.createdAt };
    }
  }

  const messages = await prisma.internalMessage.findMany({
    where,
    orderBy: { createdAt: 'desc' },
    take: options?.limit || 50,
    include: {
      sender: {
        select: { id: true, name: true, email: true, avatar: true },
      },
      replyTo: {
        select: { id: true, content: true, senderId: true },
      },
      readReceipts: {
        select: { userId: true, readAt: true },
      },
    },
  });

  return messages.reverse();
}

/**
 * Mark messages as read
 */
export async function markAsRead(
  conversationId: string,
  userId: string,
  messageId?: string
): Promise<void> {
  // Update participant's last read time
  await prisma.conversationParticipant.update({
    where: { conversationId_userId: { conversationId, userId } },
    data: { lastReadAt: new Date() },
  });

  // If specific message, create read receipt
  if (messageId) {
    await prisma.messageReadReceipt.upsert({
      where: { messageId_userId: { messageId, userId } },
      update: { readAt: new Date() },
      create: { messageId, userId },
    });
  }

  // Mark related notifications as read
  await prisma.userNotification.updateMany({
    where: {
      userId,
      type: 'message',
      isRead: false,
      data: { path: ['conversationId'], equals: conversationId },
    },
    data: { isRead: true, readAt: new Date() },
  });
}

/**
 * Get user's conversations
 */
export async function getUserConversations(userId: string) {
  const participations = await prisma.conversationParticipant.findMany({
    where: { userId, leftAt: null },
    include: {
      conversation: {
        include: {
          participants: {
            where: { leftAt: null },
            include: {
              user: {
                select: { id: true, name: true, email: true, avatar: true },
              },
            },
          },
          messages: {
            orderBy: { createdAt: 'desc' },
            take: 1,
            include: {
              sender: {
                select: { id: true, name: true },
              },
            },
          },
        },
      },
    },
    orderBy: { conversation: { lastMessageAt: 'desc' } },
  });

  return participations.map((p: any) => ({
    ...p.conversation,
    isMuted: p.isMuted,
    lastReadAt: p.lastReadAt,
    unreadCount: 0, // Would calculate based on lastReadAt
  }));
}

/**
 * Edit a message
 */
export async function editMessage(
  messageId: string,
  userId: string,
  newContent: string
): Promise<void> {
  const message = await prisma.internalMessage.findUnique({
    where: { id: messageId },
  });

  if (!message || message.senderId !== userId) {
    throw new Error('Cannot edit this message');
  }

  await prisma.internalMessage.update({
    where: { id: messageId },
    data: {
      content: newContent,
      isEdited: true,
      editedAt: new Date(),
    },
  });

  await auditLogger
    .setContext({ userId })
    .logCrud('update', 'message', messageId, {
      previousState: { content: message.content },
      newState: { content: newContent },
    });
}

/**
 * Delete a message (soft delete)
 */
export async function deleteMessage(
  messageId: string,
  userId: string
): Promise<void> {
  const message = await prisma.internalMessage.findUnique({
    where: { id: messageId },
  });

  if (!message || message.senderId !== userId) {
    throw new Error('Cannot delete this message');
  }

  await prisma.internalMessage.update({
    where: { id: messageId },
    data: {
      isDeleted: true,
      deletedAt: new Date(),
    },
  });

  await auditLogger
    .setContext({ userId })
    .logCrud('delete', 'message', messageId);
}

/**
 * Add participant to group/channel
 */
export async function addParticipant(
  conversationId: string,
  userId: string,
  addedById: string
): Promise<void> {
  const conversation = await prisma.conversation.findUnique({
    where: { id: conversationId },
  });

  if (!conversation || conversation.type === 'direct') {
    throw new Error('Cannot add participants to direct conversations');
  }

  await prisma.conversationParticipant.upsert({
    where: { conversationId_userId: { conversationId, userId } },
    update: { leftAt: null },
    create: { conversationId, userId, role: 'member' },
  });

  // System message
  await sendMessage(conversationId, addedById, {
    content: `Added a new member`,
    contentType: 'system',
  });
}

/**
 * Remove participant from group/channel
 */
export async function removeParticipant(
  conversationId: string,
  userId: string,
  removedById: string
): Promise<void> {
  await prisma.conversationParticipant.update({
    where: { conversationId_userId: { conversationId, userId } },
    data: { leftAt: new Date() },
  });

  // System message
  await sendMessage(conversationId, removedById, {
    content: `Removed a member`,
    contentType: 'system',
  });
}

/**
 * Mute/unmute conversation for user
 */
export async function setMuted(
  conversationId: string,
  userId: string,
  muted: boolean
): Promise<void> {
  await prisma.conversationParticipant.update({
    where: { conversationId_userId: { conversationId, userId } },
    data: { isMuted: muted },
  });
}
