import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
import { NotificationPublisherService } from '../infra/notification-publisher.service';

@Injectable()
export class NotificationsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly publisher: NotificationPublisherService
  ) {}

  async create(
    userId: string,
    type: string,
    title: string,
    message: string,
    payload?: Record<string, unknown>,
    options?: { skipPublish?: boolean }
  ) {
    const notification = await this.prisma.notification.create({
      data: {
        userId,
        type,
        title,
        message,
        payload: (payload ?? undefined) as never
      }
    });

    if (!options?.skipPublish) {
      await this.publisher.publish({
        userId,
        type,
        title,
        message,
        payload: {
          notificationId: notification.id,
          ...(payload ?? {})
        }
      });
    }

    return this.toResponse(notification);
  }

  async publishRealtime(userId: string, type: string, title: string, message: string, payload?: Record<string, unknown>) {
    await this.publisher.publish({ userId, type, title, message, payload });
  }

  async list(userId: string, limit = 30, unreadOnly = false) {
    const notifications = await this.prisma.notification.findMany({
      where: {
        userId,
        ...(unreadOnly ? { isRead: false } : {})
      },
      orderBy: { createdAt: 'desc' },
      take: Math.min(Math.max(limit, 1), 100)
    });

    const inviteIds = notifications
      .filter((item) => item.type === 'family_invite')
      .map((item) => (item.payload as { inviteId?: string } | null)?.inviteId)
      .filter((inviteId): inviteId is string => Boolean(inviteId));

    const pendingInviteIds = new Set<string>();
    if (inviteIds.length) {
      const invites = await this.prisma.familyInvite.findMany({
        where: { id: { in: inviteIds }, status: 'PENDING' }
      });
      invites.forEach((invite) => pendingInviteIds.add(invite.id));
    }

    const staleIds: string[] = [];
    const filtered = notifications.filter((item) => {
      if (item.type !== 'family_invite') return true;
      const inviteId = (item.payload as { inviteId?: string } | null)?.inviteId;
      if (!inviteId || !pendingInviteIds.has(inviteId)) {
        staleIds.push(item.id);
        return false;
      }
      return true;
    });

    if (staleIds.length) {
      await this.prisma.notification.deleteMany({
        where: { id: { in: staleIds }, userId }
      });
    }

    return { notifications: filtered.map((item) => this.toResponse(item)) };
  }

  async unreadCount(userId: string) {
    const count = await this.prisma.notification.count({
      where: { userId, isRead: false }
    });
    return { count };
  }

  async markRead(userId: string, notificationId: string) {
    return this.delete(userId, notificationId);
  }

  async markAllRead(userId: string) {
    return this.deleteAll(userId);
  }

  async delete(userId: string, notificationId: string) {
    const existing = await this.prisma.notification.findFirst({
      where: { id: notificationId, userId }
    });
    if (!existing) {
      throw new NotFoundException('Уведомление не найдено');
    }
    await this.prisma.notification.delete({ where: { id: notificationId } });
    return { count: 1 };
  }

  async deleteAll(userId: string) {
    const result = await this.prisma.notification.deleteMany({ where: { userId } });
    return { count: result.count };
  }

  async dismissOtpCodeNotifications(userId: string) {
    const result = await this.prisma.notification.deleteMany({
      where: { userId, type: 'otp_code' }
    });
    return { count: result.count };
  }

  async dismissFamilyInvite(userId: string, inviteId: string) {
    const items = await this.prisma.notification.findMany({
      where: { userId, type: 'family_invite' }
    });
    const ids = items
      .filter((item) => {
        const payload = item.payload as { inviteId?: string } | null;
        return payload?.inviteId === inviteId;
      })
      .map((item) => item.id);

    if (!ids.length) {
      return { count: 0 };
    }

    await this.prisma.notification.deleteMany({
      where: { id: { in: ids }, userId }
    });
    return { count: ids.length };
  }

  private toResponse(notification: {
    id: string;
    userId: string;
    type: string;
    title: string;
    message: string;
    payload: unknown;
    isRead: boolean;
    createdAt: Date;
  }) {
    return {
      id: notification.id,
      userId: notification.userId,
      type: notification.type,
      title: notification.title,
      message: notification.message,
      payloadJson: notification.payload ? JSON.stringify(notification.payload) : '',
      isRead: notification.isRead,
      createdAt: notification.createdAt.toISOString()
    };
  }
}
