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

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

  async listChannels(wsId: string, userId: string, query: any) {
    const where: any = { workspaceId: wsId, isArchived: false };
    if (query.type) where.type = query.type;
    if (query.search) where.name = { contains: query.search, mode: 'insensitive' };

    const memberships = await this.prisma.channelMember.findMany({
      where: { userId, workspaceId: wsId, leftAt: null },
      include: { channel: true },
    });

    const channels = memberships.map(m => ({
      id: m.channel.id,
      name: m.channel.name,
      type: m.channel.type,
      topic: m.channel.topic,
      purpose: m.channel.purpose,
      is_archived: m.channel.isArchived,
      member_count: m.channel.memberCount,
      unread_count: m.unreadCount,
      unread_mention_count: m.unreadMentionCount,
      last_read_ts: m.lastReadTs,
      is_muted: m.muted,
      notification_pref: m.notificationPref,
    }));

    return { channels, total: channels.length };
  }

  async create(wsId: string, userId: string, dto: any) {
    const handle = dto.name.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '');
    const channel = await this.prisma.channel.create({
      data: {
        workspaceId: wsId,
        name: dto.name,
        handle,
        type: dto.type || 'public',
        topic: dto.topic,
        purpose: dto.purpose,
        createdBy: userId,
        members: {
          create: { userId, workspaceId: wsId, role: 'owner', notificationPref: 'all' },
        },
      },
    });

    if (dto.user_ids && dto.user_ids.length > 0) {
      await this.prisma.channelMember.createMany({
        data: dto.user_ids.map((uid: string) => ({
          channelId: channel.id,
          userId: uid,
          workspaceId: wsId,
          notificationPref: 'all',
        })),
        skipDuplicates: true,
      });
    }

    return { id: channel.id, name: channel.name, type: channel.type };
  }

  async findById(id: string, wsId: string) {
    const channel = await this.prisma.channel.findFirst({
      where: { id, workspaceId: wsId },
    });
    if (!channel) throw new NotFoundException('Channel not found');
    return channel;
  }

  async update(id: string, wsId: string, userId: string, dto: any) {
    // Verificar que el canal pertenece al workspace
    const channel = await this.prisma.channel.findFirst({
      where: { id, workspaceId: wsId },
    });
    if (!channel) throw new NotFoundException('Channel not found');

    // Verificar que el usuario es miembro del canal con permisos de edición
    const membership = await this.prisma.channelMember.findFirst({
      where: { channelId: id, userId, leftAt: null },
    });
    if (!membership || !['owner', 'admin'].includes(membership.role)) {
      throw new ForbiddenException('Insufficient permissions to edit this channel');
    }

    const data: any = {};
    if (dto.name) data.name = dto.name;
    if (dto.topic !== undefined) data.topic = dto.topic;
    if (dto.purpose !== undefined) data.purpose = dto.purpose;
    return this.prisma.channel.update({ where: { id }, data });
  }

  async join(channelId: string, userId: string, wsId: string) {
    const channel = await this.prisma.channel.findUnique({ where: { id: channelId } });
    if (!channel) throw new NotFoundException('Channel not found');
    if (channel.type === 'private') throw new ForbiddenException('Cannot join private channel without invitation');

    await this.prisma.channelMember.upsert({
      where: { channelId_userId: { channelId, userId } },
      create: { channelId, userId, workspaceId: wsId, notificationPref: 'all' },
      update: { leftAt: null },
    });

    return { joined: true };
  }

  async leave(channelId: string, userId: string) {
    await this.prisma.channelMember.updateMany({
      where: { channelId, userId, leftAt: null },
      data: { leftAt: new Date() },
    });
    return { left: true };
  }

  async invite(channelId: string, userIds: string[], wsId: string, invitedBy: string) {
    if (!userIds || userIds.length === 0) return { invited: [] };

    // Verify inviter is a member of the channel
    const inviterMembership = await this.prisma.channelMember.findFirst({
      where: { channelId, userId: invitedBy, leftAt: null },
    });
    if (!inviterMembership) {
      throw new ForbiddenException('You are not a member of this channel');
    }

    // Verify all invited users are members of the workspace
    const workspaceMembers = await this.prisma.workspaceMember.findMany({
      where: { userId: { in: userIds }, workspaceId: wsId, deactivatedAt: null },
      select: { userId: true },
    });
    const validUserIds = new Set(workspaceMembers.map(m => m.userId));
    const invalidUserIds = userIds.filter(uid => !validUserIds.has(uid));
    if (invalidUserIds.length > 0) {
      throw new ForbiddenException(`Users not found in workspace: ${invalidUserIds.join(', ')}`);
    }

    await this.prisma.channelMember.createMany({
      data: userIds.map(uid => ({
        channelId,
        userId: uid,
        workspaceId: wsId,
        notificationPref: 'all',
      })),
      skipDuplicates: true,
    });
    return { invited: userIds };
  }

  async listMembers(channelId: string, wsId: string) {
    const members = await this.prisma.channelMember.findMany({
      where: { channelId, workspaceId: wsId, leftAt: null },
      include: { user: true },
    });
    return {
      members: members.map(m => ({
        id: m.userId,
        display_name: m.user.displayName,
        avatar_url: m.user.avatarUrl,
        role: m.role,
        joined_at: m.joinedAt,
      })),
    };
  }

  async archive(channelId: string, userId: string, wsId: string) {
    // Verify channel belongs to workspace
    const channel = await this.prisma.channel.findFirst({ where: { id: channelId, workspaceId: wsId } });
    if (!channel) throw new NotFoundException('Channel not found');

    // Verify user has admin/owner permissions in the channel
    const membership = await this.prisma.channelMember.findFirst({
      where: { channelId, userId, leftAt: null },
    });
    if (!membership || !['owner', 'admin'].includes(membership.role)) {
      throw new ForbiddenException('Insufficient permissions to archive this channel');
    }

    await this.prisma.channel.update({
      where: { id: channelId },
      data: { isArchived: true, archivedAt: new Date(), archivedBy: userId },
    });
    return { is_archived: true };
  }

  async getMessages(channelId: string, query: any, wsId: string) {
    const limit = Math.min(parseInt(query.limit) || 50, 100);
    
    const messages = await this.prisma.message.findMany({
      where: { channelId, workspaceId: wsId, 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,
        is_pinned: m.isPinned,
        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,
    };
  }

  // --- Bookmarks ---

  async listBookmarks(channelId: string, wsId: string) {
    const bookmarks = await this.prisma.channelBookmark.findMany({
      where: { channelId, channel: { workspaceId: wsId } },
      orderBy: { orderIndex: 'asc' },
    });
    return {
      bookmarks: bookmarks.map(b => ({
        id: b.id,
        type: b.type,
        title: b.title,
        url: b.url,
        emoji: b.emoji,
        order_index: b.orderIndex,
        created_at: b.createdAt,
      })),
    };
  }

  async createBookmark(channelId: string, userId: string, wsId: string, dto: { type: string; title?: string; url?: string; emoji?: string }) {
    // Verify channel belongs to workspace
    const channel = await this.prisma.channel.findFirst({ where: { id: channelId, workspaceId: wsId } });
    if (!channel) throw new NotFoundException('Channel not found');

    const count = await this.prisma.channelBookmark.count({ where: { channelId } });
    const bookmark = await this.prisma.channelBookmark.create({
      data: {
        channelId,
        type: dto.type,
        title: dto.title || null,
        url: dto.url || null,
        emoji: dto.emoji || null,
        orderIndex: count,
        createdBy: userId,
      },
    });
    return {
      id: bookmark.id,
      type: bookmark.type,
      title: bookmark.title,
      url: bookmark.url,
      emoji: bookmark.emoji,
      order_index: bookmark.orderIndex,
    };
  }

  async deleteBookmark(channelId: string, bookmarkId: string, wsId: string) {
    // Verify bookmark belongs to a channel in this workspace
    const bookmark = await this.prisma.channelBookmark.findFirst({
      where: { id: bookmarkId, channel: { workspaceId: wsId } },
    });
    if (!bookmark) throw new NotFoundException('Bookmark not found');

    await this.prisma.channelBookmark.delete({
      where: { id: bookmarkId },
    });
    return { deleted: true };
  }

  // --- Pins ---

  async listPins(channelId: string, wsId: string) {
    const pins = await this.prisma.messagePin.findMany({
      where: { channelId, channel: { workspaceId: wsId } },
      orderBy: { pinnedAt: 'desc' },
      include: {
        message: {
          include: {
            user: { select: { id: true, displayName: true, avatarUrl: true } },
          },
        },
      },
    });
    return {
      pins: pins.map(p => ({
        id: p.id,
        message_id: p.messageId,
        pinned_by: p.pinnedBy,
        pinned_at: p.pinnedAt,
        message: {
          id: p.message.id,
          text: p.message.text,
          user_id: p.message.userId,
          display_name: p.message.user?.displayName,
          avatar_url: p.message.user?.avatarUrl,
          ts: p.message.ts,
        },
      })),
    };
  }

  async createPin(channelId: string, userId: string, messageId: string, wsId: string) {
    // Verify message belongs to this channel and workspace
    const message = await this.prisma.message.findFirst({
      where: { id: messageId, channelId, workspaceId: wsId },
    });
    if (!message) throw new NotFoundException('Message not found in this channel');

    // Create the pin (unique constraint on channelId+messageId prevents duplicates)
    const pin = await this.prisma.messagePin.create({
      data: {
        channelId,
        messageId,
        pinnedBy: userId,
      },
    });

    // Update message isPinned flag
    await this.prisma.message.update({
      where: { id: messageId },
      data: { isPinned: true },
    });

    return {
      id: pin.id,
      message_id: pin.messageId,
      pinned_by: pin.pinnedBy,
      pinned_at: pin.pinnedAt,
    };
  }

  async deletePin(channelId: string, messageId: string, wsId: string) {
    // Verify the pin belongs to a channel in this workspace
    const pin = await this.prisma.messagePin.findFirst({
      where: { channelId, messageId, channel: { workspaceId: wsId } },
    });
    if (!pin) throw new NotFoundException('Pin not found');

    await this.prisma.messagePin.delete({
      where: { channelId_messageId: { channelId, messageId } },
    });

    // Update message isPinned flag
    await this.prisma.message.update({
      where: { id: messageId },
      data: { isPinned: false },
    });

    return { unpinned: true };
  }
}
