import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
import {
  AdminInsightsQueryOptions,
  AdminInsightsRetentionService
} from './admin-insights-retention.service';

const MODERATABLE_ROOM_FILTER = {
  isE2E: false,
  type: { notIn: ['E2E', 'BOT'] as string[] }
};

const DOCUMENT_TYPE_LABELS: Record<string, string> = {
  PASSPORT: 'Паспорт',
  DRIVER_LICENSE: 'Водительское удостоверение',
  SNILS: 'СНИЛС',
  INN: 'ИНН',
  BIRTH_CERTIFICATE: 'Свидетельство о рождении',
  OTHER: 'Документ'
};

const ROOM_TYPE_LABELS: Record<string, string> = {
  GENERAL: 'Общий чат семьи',
  DIRECT: 'Личный чат',
  GROUP: 'Групповой чат'
};

@Injectable()
export class AdminUserInsightsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly retention: AdminInsightsRetentionService
  ) {}

  async ensureHumanUser(userId: string) {
    const user = await this.prisma.user.findFirst({
      where: { id: userId, deletedAt: null, isSystemAccount: false, linkedBotId: null }
    });
    if (!user) {
      throw new NotFoundException('Пользователь не найден');
    }
    return user;
  }

  async getSignInHistory(userId: string, options: AdminInsightsQueryOptions = {}) {
    await this.ensureHumanUser(userId);
    const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
    const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
    const skip = Math.max(options.offset ?? 0, 0);
    const searchTerm = options.search?.trim();
    const createdAt = this.retention.createdAtFilter(range);

    const where = {
      userId,
      createdAt,
      ...(searchTerm
        ? {
            OR: [
              { ipAddress: { contains: searchTerm, mode: 'insensitive' as const } },
              { userAgent: { contains: searchTerm, mode: 'insensitive' as const } },
              { reason: { contains: searchTerm, mode: 'insensitive' as const } },
              { device: { name: { contains: searchTerm, mode: 'insensitive' as const } } }
            ]
          }
        : {})
    };

    const [events, total] = await Promise.all([
      this.prisma.signInEvent.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        take,
        skip,
        include: { device: true }
      }),
      this.prisma.signInEvent.count({ where })
    ]);

    return {
      ...this.retention.insightsMeta(range),
      total,
      events: events.map((event) => ({
        id: event.id,
        success: event.success,
        reason: event.reason ?? undefined,
        ipAddress: event.ipAddress ?? undefined,
        userAgent: event.userAgent ?? undefined,
        deviceName: event.device?.name ?? undefined,
        createdAt: event.createdAt.toISOString()
      }))
    };
  }

  async getActivityTimeline(userId: string, options: AdminInsightsQueryOptions = {}) {
    await this.ensureHumanUser(userId);
    const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
    const searchTerm = options.search?.trim().toLowerCase();
    const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
    const createdAt = this.retention.createdAtFilter(range);

    const [logs, documents, messages, rooms, families, consents] = await Promise.all([
      this.prisma.userActivityLog.findMany({
        where: { userId, createdAt },
        orderBy: { createdAt: 'desc' },
        take: 200
      }),
      this.prisma.userDocument.findMany({
        where: { userId, createdAt },
        orderBy: { createdAt: 'desc' },
        take: 100
      }),
      this.prisma.chatMessage.findMany({
        where: {
          senderId: userId,
          deletedAt: null,
          createdAt,
          room: MODERATABLE_ROOM_FILTER
        },
        include: { room: { include: { group: true } } },
        orderBy: { createdAt: 'desc' },
        take: 100
      }),
      this.prisma.chatRoom.findMany({
        where: { createdById: userId, createdAt, ...MODERATABLE_ROOM_FILTER },
        include: { group: true },
        orderBy: { createdAt: 'desc' },
        take: 50
      }),
      this.prisma.familyGroup.findMany({
        where: { ownerId: userId, createdAt },
        orderBy: { createdAt: 'desc' },
        take: 50
      }),
      this.prisma.oAuthConsent.findMany({
        where: { userId, createdAt },
        include: { client: true },
        orderBy: { createdAt: 'desc' },
        take: 50
      })
    ]);

    type TimelineItem = {
      id: string;
      action: string;
      title: string;
      detail?: string;
      entityType?: string;
      entityId?: string;
      createdAt: string;
    };

    const items: TimelineItem[] = [
      ...logs.map((item) => ({
        id: item.id,
        action: item.action,
        title: item.title,
        detail: item.detail ?? undefined,
        entityType: item.entityType ?? undefined,
        entityId: item.entityId ?? undefined,
        createdAt: item.createdAt.toISOString()
      })),
      ...documents.map((doc) => ({
        id: `doc-${doc.id}`,
        action: 'DOCUMENT_CREATED',
        title: 'Добавлен документ',
        detail: `${DOCUMENT_TYPE_LABELS[doc.type] ?? doc.type} · ${doc.number}`,
        entityType: 'document',
        entityId: doc.id,
        createdAt: doc.createdAt.toISOString()
      })),
      ...messages.map((message) => ({
        id: `msg-${message.id}`,
        action: 'CHAT_MESSAGE_SENT',
        title: 'Сообщение в чате',
        detail: this.formatMessagePreview(message.type, message.content, message.isEncrypted),
        entityType: 'chat_message',
        entityId: message.id,
        createdAt: message.createdAt.toISOString()
      })),
      ...rooms.map((room) => ({
        id: `room-${room.id}`,
        action: 'CHAT_ROOM_CREATED',
        title: 'Создан чат',
        detail: `${ROOM_TYPE_LABELS[room.type] ?? room.type}: ${room.name} (${room.group.name})`,
        entityType: 'chat_room',
        entityId: room.id,
        createdAt: room.createdAt.toISOString()
      })),
      ...families.map((group) => ({
        id: `family-${group.id}`,
        action: 'FAMILY_CREATED',
        title: 'Создана семья',
        detail: group.name,
        entityType: 'family',
        entityId: group.id,
        createdAt: group.createdAt.toISOString()
      })),
      ...consents.map((consent) => ({
        id: `oauth-${consent.id}`,
        action: 'OAUTH_CONSENT',
        title: 'Разрешён доступ приложению',
        detail: consent.client.name,
        entityType: 'oauth_consent',
        entityId: consent.id,
        createdAt: consent.createdAt.toISOString()
      }))
    ];

    const deduped = [...new Map(items.map((item) => [item.id, item])).values()]
      .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
      .filter((item) => {
        if (!searchTerm) return true;
        const haystack = `${item.title} ${item.detail ?? ''} ${item.action}`.toLowerCase();
        return haystack.includes(searchTerm);
      });

    const skip = Math.max(options.offset ?? 0, 0);
    return {
      ...this.retention.insightsMeta(range),
      total: deduped.length,
      items: deduped.slice(skip, skip + take)
    };
  }

  async listUserChatRooms(userId: string, options: AdminInsightsQueryOptions = {}) {
    await this.ensureHumanUser(userId);
    const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
    const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
    const skip = Math.max(options.offset ?? 0, 0);
    const searchTerm = options.search?.trim();
    const createdAt = this.retention.createdAtFilter(range);

    const memberships = await this.prisma.chatRoomMember.findMany({
      where: {
        userId,
        room: {
          ...MODERATABLE_ROOM_FILTER,
          messages: {
            some: {
              senderId: userId,
              createdAt
            }
          },
          ...(searchTerm
            ? {
                OR: [
                  { name: { contains: searchTerm, mode: 'insensitive' } },
                  { group: { name: { contains: searchTerm, mode: 'insensitive' } } }
                ]
              }
            : {})
        }
      },
      include: {
        room: {
          include: {
            group: true,
            members: {
              include: {
                user: { select: { id: true, displayName: true } }
              }
            },
            messages: {
              where: { deletedAt: null, createdAt },
              orderBy: { createdAt: 'desc' },
              take: 1,
              include: { sender: { select: { displayName: true } } }
            },
            _count: {
              select: {
                messages: {
                  where: { senderId: userId, createdAt }
                }
              }
            }
          }
        }
      },
      orderBy: { joinedAt: 'desc' }
    });

    const rooms = memberships.map((membership) => {
      const room = membership.room;
      const lastMessage = room.messages[0];
      return {
        id: room.id,
        type: room.type,
        name: room.name,
        groupId: room.groupId,
        groupName: room.group.name,
        memberCount: room.members.length,
        messageCount: room._count.messages,
        members: room.members.map((member) => ({
          id: member.user.id,
          displayName: member.user.displayName
        })),
        lastMessage: lastMessage
          ? {
              id: lastMessage.id,
              senderName: lastMessage.sender.displayName,
              preview: this.formatMessagePreview(lastMessage.type, lastMessage.content, lastMessage.isEncrypted),
              createdAt: lastMessage.createdAt.toISOString()
            }
          : undefined,
        joinedAt: membership.joinedAt.toISOString()
      };
    });

    return {
      ...this.retention.insightsMeta(range),
      total: rooms.length,
      rooms: rooms.slice(skip, skip + take)
    };
  }

  async listRoomMessages(userId: string, roomId: string, options: AdminInsightsQueryOptions = {}) {
    await this.ensureHumanUser(userId);
    const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
    const room = await this.getModeratableRoom(roomId);
    const member = await this.prisma.chatRoomMember.findUnique({
      where: { roomId_userId: { roomId, userId } }
    });
    if (!member) {
      throw new ForbiddenException('Пользователь не состоит в этом чате');
    }

    const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
    const searchTerm = options.search?.trim();
    const createdAt = this.retention.createdAtFilter(range);

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

    const messages = await this.prisma.chatMessage.findMany({
      where: {
        roomId,
        createdAt,
        ...(cursorDate ? { createdAt: { lt: cursorDate, gte: range.from, lte: range.to } } : {}),
        ...(searchTerm
          ? {
              OR: [
                { content: { contains: searchTerm, mode: 'insensitive' } },
                { sender: { displayName: { contains: searchTerm, mode: 'insensitive' } } }
              ]
            }
          : {})
      },
      include: { sender: { select: { id: true, displayName: true } } },
      orderBy: { createdAt: 'desc' },
      take
    });

    return {
      ...this.retention.insightsMeta(range),
      room: {
        id: room.id,
        name: room.name,
        type: room.type,
        groupName: room.group.name
      },
      messages: messages.reverse().map((message) =>
        this.toAdminMessage(message, { roomName: room.name, groupName: room.group.name })
      )
    };
  }

  async searchUserChatMessages(userId: string, options: AdminInsightsQueryOptions) {
    await this.ensureHumanUser(userId);
    const searchTerm = options.search?.trim();
    if (!searchTerm) {
      throw new BadRequestException('Укажите текст для поиска по сообщениям');
    }

    const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
    const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
    const skip = Math.max(options.offset ?? 0, 0);
    const createdAt = this.retention.createdAtFilter(range);

    const where = {
      room: {
        members: { some: { userId } },
        ...MODERATABLE_ROOM_FILTER
      },
      deletedAt: null,
      createdAt,
      OR: [
        { content: { contains: searchTerm, mode: 'insensitive' as const } },
        { sender: { displayName: { contains: searchTerm, mode: 'insensitive' as const } } }
      ]
    };

    const [messages, total] = await Promise.all([
      this.prisma.chatMessage.findMany({
        where,
        include: {
          sender: { select: { id: true, displayName: true } },
          room: { include: { group: true } }
        },
        orderBy: { createdAt: 'desc' },
        take,
        skip
      }),
      this.prisma.chatMessage.count({ where })
    ]);

    return {
      ...this.retention.insightsMeta(range),
      total,
      messages: messages.map((message) => ({
        ...this.toAdminMessage(message, {
          roomName: message.room.name,
          groupName: message.room.group.name
        })
      }))
    };
  }

  async adminDeleteMessage(actorId: string, messageId: string) {
    const message = await this.prisma.chatMessage.findUnique({
      where: { id: messageId },
      include: { room: { include: { group: true } }, sender: true }
    });
    if (!message) {
      throw new NotFoundException('Сообщение не найдено');
    }
    if (message.room.isE2E || message.room.type === 'E2E') {
      throw new ForbiddenException('E2E-сообщения недоступны для модерации');
    }
    if (message.room.type === 'BOT') {
      throw new ForbiddenException('Сообщения бота модерируются отдельно');
    }

    await this.prisma.chatMessage.update({
      where: { id: messageId },
      data: {
        deletedAt: new Date(),
        content: null,
        storageKey: null,
        mimeType: null,
        metadata: { moderated: true, moderatedBy: actorId }
      }
    });

    await this.prisma.userActivityLog.create({
      data: {
        userId: message.senderId,
        action: 'ADMIN_MESSAGE_DELETED',
        title: 'Сообщение удалено модератором',
        detail: `Чат «${message.room.name}» (${message.room.group.name})`,
        entityType: 'chat_message',
        entityId: message.id,
        actorId
      }
    });

    return { success: true };
  }

  private async getModeratableRoom(roomId: string) {
    const room = await this.prisma.chatRoom.findUnique({
      where: { id: roomId },
      include: { group: true }
    });
    if (!room) {
      throw new NotFoundException('Чат не найден');
    }
    if (room.isE2E || room.type === 'E2E' || room.type === 'BOT') {
      throw new ForbiddenException('Этот чат недоступен для модерации');
    }
    return room;
  }

  private formatMessagePreview(type: string, content: string | null, isEncrypted: boolean) {
    if (isEncrypted) {
      return '[Зашифрованное сообщение]';
    }
    if (type === 'IMAGE') return '[Изображение]';
    if (type === 'AUDIO' || type === 'VOICE') return '[Аудио]';
    if (type === 'VIDEO') return '[Видео]';
    if (type === 'VIDEO_NOTE') return '[Кружок]';
    if (type === 'FILE') return '[Файл]';
    if (type === 'POLL') return '[Опрос]';
    if (type === 'SYSTEM') return '[Системное сообщение]';
    const text = content?.trim();
    if (!text) return '[Пустое сообщение]';
    return text.length > 160 ? `${text.slice(0, 157)}…` : text;
  }

  private toAdminMessage(
    message: {
      id: string;
      roomId: string;
      senderId: string;
      type: string;
      content: string | null;
      storageKey: string | null;
      mimeType: string | null;
      metadata: unknown;
      isEncrypted: boolean;
      deletedAt: Date | null;
      createdAt: Date;
      editedAt: Date | null;
      sender: { id: string; displayName: string };
    },
    context?: { roomName?: string; groupName?: string }
  ) {
    return {
      id: message.id,
      senderId: message.senderId,
      senderName: message.sender.displayName,
      type: message.type,
      content: message.isEncrypted ? null : message.content,
      preview: this.formatMessagePreview(message.type, message.content, message.isEncrypted),
      isEncrypted: message.isEncrypted,
      isDeleted: Boolean(message.deletedAt),
      createdAt: message.createdAt.toISOString(),
      editedAt: message.editedAt?.toISOString(),
      roomId: message.roomId,
      roomName: context?.roomName,
      groupName: context?.groupName,
      storageKey: message.deletedAt || message.isEncrypted ? undefined : message.storageKey ?? undefined,
      mimeType: message.deletedAt || message.isEncrypted ? undefined : message.mimeType ?? undefined,
      metadataJson:
        message.deletedAt || message.isEncrypted
          ? undefined
          : message.metadata
            ? JSON.stringify(message.metadata)
            : undefined
    };
  }
}
