import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
import { FamilyService } from './family.service';
import { NotificationsService } from './notifications.service';

interface PollPayload {
  question: string;
  options: string[];
  allowsMultiple?: boolean;
  isAnonymous?: boolean;
}

@Injectable()
export class ChatService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly family: FamilyService,
    private readonly notifications: NotificationsService
  ) {}

  async listRooms(userId: string, groupId: string) {
    await this.family.assertFamilyMember(groupId, userId);
    let generalRoom = await this.prisma.chatRoom.findFirst({ where: { groupId, type: 'GENERAL' } });
    if (!generalRoom) {
      const members = await this.prisma.familyMember.findMany({ where: { groupId } });
      generalRoom = await this.prisma.chatRoom.create({
        data: {
          groupId,
          type: 'GENERAL',
          name: 'Общий чат',
          members: { create: members.map((member) => ({ userId: member.userId })) }
        }
      });
    }
    const rooms = await this.prisma.chatRoom.findMany({
      where: {
        groupId,
        members: { some: { userId } }
      },
      include: {
        members: { include: { user: true } },
        messages: {
          orderBy: { createdAt: 'desc' },
          take: 1,
          include: {
            sender: true,
            poll: { include: { options: { include: { votes: true } } } }
          }
        }
      },
      orderBy: [{ type: 'asc' }, { updatedAt: 'desc' }]
    });

    return {
      rooms: await Promise.all(
        rooms.map(async (room) => {
          const membership = room.members.find((member) => member.userId === userId);
          const lastMessage = room.messages[0];
          return {
            id: room.id,
            groupId: room.groupId,
            type: room.type,
            name: room.name,
            hasAvatar: room.hasAvatar,
            updatedAt: room.updatedAt.toISOString(),
            members: room.members.map((member) => ({
              id: member.id,
              userId: member.userId,
              displayName: member.user.displayName,
              hasAvatar: Boolean(member.user.avatarStorageKey),
              notificationsMuted: member.notificationsMuted
            })),
            lastMessage: lastMessage ? await this.toMessage(lastMessage, userId) : undefined
          };
        })
      )
    };
  }

  async createRoom(userId: string, groupId: string, name: string, memberUserIds: string[]) {
    const group = await this.family.assertFamilyMember(groupId, userId);
    const trimmedName = name.trim();
    if (!trimmedName) {
      throw new BadRequestException('Укажите название группового чата');
    }

    const uniqueMembers = Array.from(new Set([userId, ...memberUserIds]));
    for (const memberId of uniqueMembers) {
      if (!group.members.some((member) => member.userId === memberId)) {
        throw new BadRequestException('Все участники чата должны состоять в семье');
      }
    }

    const room = await this.prisma.chatRoom.create({
      data: {
        groupId,
        type: 'GROUP',
        name: trimmedName,
        createdById: userId,
        members: {
          create: uniqueMembers.map((memberId) => ({ userId: memberId }))
        }
      },
      include: {
        members: { include: { user: true } },
        messages: true
      }
    });

    return {
      id: room.id,
      groupId: room.groupId,
      type: room.type,
      name: room.name,
      hasAvatar: room.hasAvatar,
      updatedAt: room.updatedAt.toISOString(),
      members: room.members.map((member) => ({
        id: member.id,
        userId: member.userId,
        displayName: member.user.displayName,
        hasAvatar: Boolean(member.user.avatarStorageKey),
        notificationsMuted: member.notificationsMuted
      }))
    };
  }

  async updateRoomSettings(userId: string, roomId: string, name?: string, notificationsMuted?: boolean) {
    const room = await this.getRoomForMember(roomId, userId);
    if (name !== undefined) {
      const trimmedName = name.trim();
      if (!trimmedName) {
        throw new BadRequestException('Укажите название чата');
      }
      if (room.type === 'GENERAL') {
        throw new BadRequestException('Нельзя переименовать общий чат');
      }
      await this.prisma.chatRoom.update({ where: { id: roomId }, data: { name: trimmedName } });
    }
    if (notificationsMuted !== undefined) {
      await this.prisma.chatRoomMember.update({
        where: { roomId_userId: { roomId, userId } },
        data: { notificationsMuted }
      });
    }
    return this.getRoomResponse(roomId, userId);
  }

  async listMessages(userId: string, roomId: string, beforeMessageId?: string, limit = 50) {
    await this.getRoomForMember(roomId, userId);
    const take = Math.min(Math.max(limit, 1), 100);

    let cursorDate: Date | undefined;
    if (beforeMessageId) {
      const cursor = await this.prisma.chatMessage.findFirst({ where: { id: beforeMessageId, roomId } });
      if (cursor) cursorDate = cursor.createdAt;
    }

    const messages = await this.prisma.chatMessage.findMany({
      where: {
        roomId,
        ...(cursorDate ? { createdAt: { lt: cursorDate } } : {})
      },
      include: {
        sender: true,
        poll: { include: { options: { include: { votes: true } } } }
      },
      orderBy: { createdAt: 'desc' },
      take
    });

    const ordered = messages.reverse();
    return {
      messages: await Promise.all(ordered.map((message) => this.toMessage(message, userId)))
    };
  }

  async sendMessage(
    userId: string,
    roomId: string,
    type: string,
    content?: string,
    replyToId?: string,
    storageKey?: string,
    mimeType?: string,
    metadataJson?: string,
    poll?: PollPayload
  ) {
    const room = await this.getRoomForMember(roomId, userId);
    const allowedTypes = new Set(['TEXT', 'IMAGE', 'AUDIO', 'VOICE', 'FILE', 'EMOJI', 'POLL']);
    if (!allowedTypes.has(type)) {
      throw new BadRequestException('Неподдерживаемый тип сообщения');
    }

    if (type === 'POLL') {
      if (!poll?.question?.trim() || !poll.options?.length) {
        throw new BadRequestException('Укажите вопрос и варианты опроса');
      }
      if (poll.options.length < 2) {
        throw new BadRequestException('В опросе должно быть минимум 2 варианта');
      }
    } else if (type !== 'IMAGE' && type !== 'AUDIO' && type !== 'VOICE' && type !== 'FILE') {
      if (!content?.trim()) {
        throw new BadRequestException('Сообщение не может быть пустым');
      }
    }

    if ((type === 'IMAGE' || type === 'AUDIO' || type === 'VOICE' || type === 'FILE') && !storageKey) {
      throw new BadRequestException('Не указан файл для отправки');
    }

    if (storageKey && !storageKey.startsWith(`chat/${roomId}/`)) {
      throw new BadRequestException('Некорректный ключ медиафайла');
    }

    const metadata = metadataJson ? JSON.parse(metadataJson) : undefined;

    const message = await this.prisma.$transaction(async (tx) => {
      const created = await tx.chatMessage.create({
        data: {
          roomId,
          senderId: userId,
          type,
          content: content?.trim() || null,
          replyToId: replyToId || null,
          storageKey: storageKey || null,
          mimeType: mimeType || null,
          metadata: metadata ?? undefined
        },
        include: { sender: true }
      });

      if (type === 'POLL' && poll) {
        await tx.chatPoll.create({
          data: {
            messageId: created.id,
            question: poll.question.trim(),
            allowsMultiple: poll.allowsMultiple ?? false,
            isAnonymous: poll.isAnonymous ?? false,
            options: {
              create: poll.options.map((text) => ({ text: text.trim() })).filter((item) => item.text)
            }
          }
        });
      }

      await tx.chatRoom.update({ where: { id: roomId }, data: { updatedAt: new Date() } });
      return created;
    });

    const fullMessage = await this.prisma.chatMessage.findUnique({
      where: { id: message.id },
      include: {
        sender: true,
        poll: { include: { options: { include: { votes: true } } } }
      }
    });

    const response = await this.toMessage(fullMessage!, userId);
    await this.notifyRoomMembers(room, userId, response);
    return response;
  }

  async votePoll(userId: string, messageId: string, optionIds: string[]) {
    const message = await this.prisma.chatMessage.findUnique({
      where: { id: messageId },
      include: {
        poll: { include: { options: true } },
        room: { include: { members: true, group: true } }
      }
    });
    if (!message?.poll) {
      throw new NotFoundException('Опрос не найден');
    }
    await this.getRoomForMember(message.roomId, userId);

    const validOptionIds = new Set(message.poll.options.map((option) => option.id));
    for (const optionId of optionIds) {
      if (!validOptionIds.has(optionId)) {
        throw new BadRequestException('Некорректный вариант опроса');
      }
    }
    if (!message.poll.allowsMultiple && optionIds.length > 1) {
      throw new BadRequestException('Можно выбрать только один вариант');
    }

    await this.prisma.$transaction(async (tx) => {
      await tx.chatPollVote.deleteMany({
        where: {
          userId,
          option: { pollId: message.poll!.id }
        }
      });
      for (const optionId of optionIds) {
        await tx.chatPollVote.create({ data: { optionId, userId } });
      }
    });

    const updated = await this.prisma.chatMessage.findUnique({
      where: { id: messageId },
      include: {
        sender: true,
        poll: { include: { options: { include: { votes: true } } } }
      }
    });
    return this.toMessage(updated!, userId);
  }

  async setRoomNotificationsMuted(userId: string, roomId: string, muted: boolean) {
    await this.getRoomForMember(roomId, userId);
    await this.prisma.chatRoomMember.update({
      where: { roomId_userId: { roomId, userId } },
      data: { notificationsMuted: muted }
    });
    return { count: 1 };
  }

  async assertRoomMember(roomId: string, userId: string) {
    return this.getRoomForMember(roomId, userId);
  }

  private async getRoomForMember(roomId: string, userId: string) {
    const room = await this.prisma.chatRoom.findUnique({
      where: { id: roomId },
      include: {
        members: true,
        group: { include: { members: true } }
      }
    });
    if (!room) {
      throw new NotFoundException('Чат не найден');
    }
    if (!room.members.some((member) => member.userId === userId)) {
      throw new ForbiddenException('Вы не состоите в этом чате');
    }
    return room;
  }

  private async getRoomResponse(roomId: string, userId: string) {
    const room = await this.getRoomForMember(roomId, userId);
    const full = await this.prisma.chatRoom.findUnique({
      where: { id: roomId },
      include: { members: { include: { user: true } } }
    });
    return {
      id: full!.id,
      groupId: full!.groupId,
      type: full!.type,
      name: full!.name,
      hasAvatar: full!.hasAvatar,
      updatedAt: full!.updatedAt.toISOString(),
      members: full!.members.map((member) => ({
        id: member.id,
        userId: member.userId,
        displayName: member.user.displayName,
        hasAvatar: Boolean(member.user.avatarStorageKey),
        notificationsMuted: member.notificationsMuted
      }))
    };
  }

  private async notifyRoomMembers(
    room: { id: string; name: string; members: Array<{ userId: string; notificationsMuted: boolean }> },
    senderId: string,
    message: Awaited<ReturnType<ChatService['toMessage']>>
  ) {
    const sender = await this.prisma.user.findUnique({ where: { id: senderId } });
    const preview =
      message.type === 'TEXT' || message.type === 'EMOJI'
        ? (message.content ?? '')
        : message.type === 'POLL'
          ? '📊 Опрос'
          : message.type === 'VOICE'
            ? '🎤 Голосовое'
            : message.type === 'AUDIO'
              ? '🎵 Аудио'
              : message.type === 'FILE'
                ? '📄 Файл'
                : message.type === 'IMAGE'
                  ? '📷 Фото'
                  : '📎 Медиа';

    for (const member of room.members) {
      if (member.userId === senderId) continue;
      await this.notifications.publishRealtime(member.userId, 'chat_message', room.name, preview.slice(0, 120), {
        roomId: room.id,
        message
      });
    }

    for (const member of room.members) {
      if (member.userId === senderId || member.notificationsMuted) continue;
      await this.notifications.create(
        member.userId,
        'chat_message',
        room.name,
        `${sender?.displayName ?? 'Участник'}: ${preview.slice(0, 120)}`,
        { roomId: room.id, messageId: message.id },
        { skipPublish: true }
      );
    }
  }

  private async toMessage(
    message: {
      id: string;
      roomId: string;
      senderId: string;
      type: string;
      content: string | null;
      replyToId: string | null;
      storageKey: string | null;
      mimeType: string | null;
      metadata: unknown;
      createdAt: Date;
      sender: { displayName: string; avatarStorageKey: string | null };
      poll?: {
        id: string;
        question: string;
        allowsMultiple: boolean;
        isAnonymous: boolean;
        closedAt: Date | null;
        options: Array<{ id: string; text: string; votes: Array<{ userId: string }> }>;
      } | null;
    },
    viewerId: string
  ) {
    return {
      id: message.id,
      roomId: message.roomId,
      senderId: message.senderId,
      senderName: message.sender.displayName,
      senderHasAvatar: Boolean(message.sender.avatarStorageKey),
      type: message.type,
      content: message.content ?? undefined,
      replyToId: message.replyToId ?? undefined,
      storageKey: message.storageKey ?? undefined,
      mimeType: message.mimeType ?? undefined,
      metadataJson: message.metadata ? JSON.stringify(message.metadata) : undefined,
      createdAt: message.createdAt.toISOString(),
      poll: message.poll
        ? {
            id: message.poll.id,
            question: message.poll.question,
            allowsMultiple: message.poll.allowsMultiple,
            isAnonymous: message.poll.isAnonymous,
            closedAt: message.poll.closedAt?.toISOString(),
            options: message.poll.options.map((option) => ({
              id: option.id,
              text: option.text,
              voteCount: option.votes.length,
              votedByMe: option.votes.some((vote) => vote.userId === viewerId)
            }))
          }
        : undefined
    };
  }
}
