import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '@chatapp/database';

@Injectable()
export class DmsService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Create or get a DM channel between two users.
   * A DM is a channel with type='dm' that has exactly 2 members.
   */
  async createOrGetDm(userId: string, otherUserId: string, wsId: string) {
    if (userId === otherUserId) {
      throw new ForbiddenException('Cannot create a DM with yourself');
    }

    // Check if a DM channel already exists between these two users
    const existingDm = await this.findExistingDm(userId, otherUserId, wsId);
    if (existingDm) {
      return existingDm;
    }

    // Create a new DM channel
    const handle = `dm-${userId}-${otherUserId}-${Date.now()}`;
    const channel = await this.prisma.channel.create({
      data: {
        workspaceId: wsId,
        name: `DM ${userId.slice(0, 8)}-${otherUserId.slice(0, 8)}`,
        handle,
        type: 'dm',
        createdBy: userId,
        isShared: false,
        isOrgWide: false,
        members: {
          create: [
            {
              userId,
              workspaceId: wsId,
              role: 'member',
              notificationPref: 'all',
            },
            {
              userId: otherUserId,
              workspaceId: wsId,
              role: 'member',
              notificationPref: 'all',
            },
          ],
        },
      },
      include: {
        members: {
          include: {
            user: { select: { id: true, displayName: true, avatarUrl: true } },
          },
        },
      },
    });

    // Update member count
    await this.prisma.channel.update({
      where: { id: channel.id },
      data: { memberCount: 2 },
    });

    return {
      id: channel.id,
      type: channel.type,
      name: channel.name,
      members: channel.members.map((m) => ({
        userId: m.userId,
        displayName: m.user.displayName,
        avatarUrl: m.user.avatarUrl,
      })),
      created_at: channel.createdAt,
    };
  }

  /**
   * Find an existing DM channel between two users.
   */
  private async findExistingDm(userId: string, otherUserId: string, wsId: string) {
    // Find channels of type 'dm' where both users are members
    const userDmChannels = await this.prisma.channelMember.findMany({
      where: { userId, workspaceId: wsId, channel: { type: 'dm' }, leftAt: null },
      select: { channelId: true },
    });

    const channelIds = userDmChannels.map((m) => m.channelId);
    if (channelIds.length === 0) return null;

    // Check if otherUserId is a member of any of these channels
    const sharedDm = await this.prisma.channelMember.findFirst({
      where: {
        channelId: { in: channelIds },
        userId: otherUserId,
        leftAt: null,
      },
      include: {
        channel: {
          include: {
            members: {
              include: {
                user: { select: { id: true, displayName: true, avatarUrl: true } },
              },
            },
          },
        },
      },
    });

    if (!sharedDm) return null;

    // Verify this is truly a 2-person DM
    const memberCount = await this.prisma.channelMember.count({
      where: { channelId: sharedDm.channelId, leftAt: null },
    });

    if (memberCount !== 2) return null;

    return {
      id: sharedDm.channel.id,
      type: sharedDm.channel.type,
      name: sharedDm.channel.name,
      members: sharedDm.channel.members
        .filter((m) => m.leftAt === null)
        .map((m) => ({
          userId: m.userId,
          displayName: m.user.displayName,
          avatarUrl: m.user.avatarUrl,
        })),
      created_at: sharedDm.channel.createdAt,
    };
  }

  /**
   * List all DM conversations for a user.
   */
  async listDms(userId: string, wsId: string) {
    const memberships = await this.prisma.channelMember.findMany({
      where: {
        userId,
        workspaceId: wsId,
        channel: { type: 'dm' },
        leftAt: null,
      },
      include: {
        channel: {
          include: {
            members: {
              where: { leftAt: null },
              include: {
                user: { select: { id: true, displayName: true, avatarUrl: true } },
              },
            },
            messages: {
              where: { isDeleted: false },
              orderBy: { ts: 'desc' },
              take: 1,
              select: { id: true, text: true, ts: true, userId: true },
            },
          },
        },
      },
      orderBy: { channel: { lastMessageAt: 'desc' } },
    });

    return {
      dms: memberships.map((m) => {
        const otherMember = m.channel.members.find((mem) => mem.userId !== userId);
        return {
          id: m.channel.id,
          type: m.channel.type,
          other_user: otherMember
            ? {
                userId: otherMember.userId,
                displayName: otherMember.user.displayName,
                avatarUrl: otherMember.user.avatarUrl,
              }
            : null,
          last_message: m.channel.messages[0] || null,
          last_message_at: m.channel.lastMessageAt,
          unread_count: m.unreadCount,
          unread_mention_count: m.unreadMentionCount,
          last_read_ts: m.lastReadTs,
        };
      }),
      total: memberships.length,
    };
  }

  /**
   * Get messages from a DM channel.
   */
  async getDmMessages(dmId: string, userId: string, query: any) {
    // Verify the user is a member of this DM
    const membership = await this.prisma.channelMember.findFirst({
      where: { channelId: dmId, userId, leftAt: null },
    });

    if (!membership) {
      throw new ForbiddenException('You are not a member of this DM');
    }

    // Verify it's a DM channel
    const channel = await this.prisma.channel.findUnique({
      where: { id: dmId },
    });

    if (!channel || channel.type !== 'dm') {
      throw new NotFoundException('DM not found');
    }

    const limit = Math.min(parseInt(query.limit) || 50, 100);

    const messages = await this.prisma.message.findMany({
      where: { channelId: dmId, isDeleted: false },
      orderBy: { ts: 'desc' },
      take: limit,
      include: {
        user: { select: { id: true, displayName: true, avatarUrl: true } },
        reactions: true,
      },
    });

    const hasMore = messages.length === limit;
    const nextCursor = hasMore && messages.length > 0 ? messages[messages.length - 1].id : null;

    return {
      messages: messages.map((m) => ({
        id: m.id,
        user_id: m.userId,
        display_name: m.user?.displayName,
        avatar_url: m.user?.avatarUrl,
        text: m.text,
        blocks: m.blocks,
        thread_root_id: m.threadRootId,
        reply_count: m.replyCount,
        is_edited: m.isEdited,
        reactions: m.reactions.map((r) => ({ emoji: r.emoji, user_id: r.userId })),
        ts: m.ts,
        created_at: m.createdAt,
      })),
      has_more: hasMore,
      next_cursor: nextCursor,
    };
  }

  /**
   * Create a group DM with multiple users.
   */
  async createGroupDm(userId: string, userIds: string[], wsId: string, name?: string) {
    const allUserIds = Array.from(new Set([userId, ...userIds]));

    const handle = `groupdm-${userId}-${Date.now()}`;
    const channelName = name || `Group DM (${allUserIds.length})`;

    const channel = await this.prisma.channel.create({
      data: {
        workspaceId: wsId,
        name: channelName,
        handle,
        type: 'group_dm',
        createdBy: userId,
        isShared: false,
        isOrgWide: false,
        members: {
          create: allUserIds.map((uid) => ({
            userId: uid,
            workspaceId: wsId,
            role: uid === userId ? 'owner' : 'member',
            notificationPref: 'all',
          })),
        },
      },
      include: {
        members: {
          include: {
            user: { select: { id: true, displayName: true, avatarUrl: true } },
          },
        },
      },
    });

    await this.prisma.channel.update({
      where: { id: channel.id },
      data: { memberCount: allUserIds.length },
    });

    return {
      id: channel.id,
      type: channel.type,
      name: channel.name,
      members: channel.members.map((m) => ({
        userId: m.userId,
        displayName: m.user.displayName,
        avatarUrl: m.user.avatarUrl,
      })),
      created_at: channel.createdAt,
    };
  }

  /**
   * Send a message to a DM channel.
   */
  async sendDmMessage(dmId: string, userId: string, wsId: string, text: string) {
    // Verify the user is a member of this DM
    const membership = await this.prisma.channelMember.findFirst({
      where: { channelId: dmId, userId, leftAt: null },
    });

    if (!membership) {
      throw new ForbiddenException('You are not a member of this DM');
    }

    const channel = await this.prisma.channel.findUnique({ where: { id: dmId } });
    if (!channel || !['dm', 'group_dm'].includes(channel.type)) {
      throw new NotFoundException('DM not found');
    }

    const message = await this.prisma.message.create({
      data: {
        workspaceId: wsId,
        channelId: dmId,
        userId,
        text,
        ts: new Date(),
      },
      include: {
        user: { select: { id: true, displayName: true, avatarUrl: true } },
      },
    });

    // Update channel lastMessageAt
    await this.prisma.channel.update({
      where: { id: dmId },
      data: { lastMessageAt: new Date() },
    });

    return {
      id: message.id,
      user_id: message.userId,
      display_name: message.user?.displayName,
      avatar_url: message.user?.avatarUrl,
      text: message.text,
      ts: message.ts,
      created_at: message.createdAt,
    };
  }
}