import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import type { Prisma } from '@/generated/prisma/client';
import { PrismaService } from '@/prisma/prisma.service';
import { GetSystemNotificationsQueryDto } from './dto/get-system-notifications-query.dto';

/** Mobile visibility only: never expire shared MIS records in the database. */
@Injectable()
export class MobileSystemNotificationsService {
  constructor(private readonly prisma: PrismaService) {}

  private getRetentionCutoff(): Date {
    return new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
  }
  async findAllForCurrentUser(
    userId: number,
    query: GetSystemNotificationsQueryDto,
  ) {
    const cutoff = this.getRetentionCutoff();
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const where = await this.buildCurrentUserWhere(
      userId,
      {
        type: query.type,
        isRead: query.isRead,
      },
      cutoff,
    );

    const unreadWhere = await this.buildCurrentUserWhere(
      userId,
      {
        isRead: false,
      },
      cutoff,
    );

    const [items, total, unreadCount] = await this.prisma.$transaction([
      this.prisma.systemNotification.findMany({
        where,
        include: {
          senderUser: {
            select: {
              name: true,
              avatar: true,
            },
          },
        },
        orderBy: {
          createdAt: 'desc',
        },
        skip,
        take: limit,
      }),

      this.prisma.systemNotification.count({
        where,
      }),

      this.prisma.systemNotification.count({
        where: unreadWhere,
      }),
    ]);

    return {
      data: items,
      meta: {
        page,
        limit,
        total,
        unreadCount,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  async unreadCountForCurrentUser(userId: number) {
    const where = await this.buildCurrentUserWhere(userId, {
      isRead: false,
    });

    const count = await this.prisma.systemNotification.count({
      where,
    });

    return {
      count,
    };
  }

  async findOneForCurrentUser(id: number, userId: number) {
    const notification = await this.findVisibleNotification(id, userId);
    // A repeated detail view must not restart the seven-day timer.
    // ON CONFLICT DO NOTHING also handles simultaneous first clicks safely.
    const [, view] = await this.prisma.$transaction([
      this.prisma.mobileNotificationView.createMany({
        data: [{ notificationId: id, userId, viewedAt: new Date(Date.now()) }],
        skipDuplicates: true,
      }),
      this.prisma.mobileNotificationView.findUnique({
        where: { notificationId_userId: { notificationId: id, userId } },
      }),
    ]);
    if (!view || view.viewedAt <= this.getRetentionCutoff()) {
      throw new NotFoundException('Notification not found.');
    }
    return notification;
  }

  private async findVisibleNotification(id: number, userId: number) {
    const notificationScope = await this.buildCurrentUserWhere(userId, {});

    const notification = await this.prisma.systemNotification.findFirst({
      where: {
        AND: [
          notificationScope,
          {
            id,
          },
        ],
      },
    });

    if (!notification) {
      throw new NotFoundException('Notification not found.');
    }

    return notification;
  }

  async markAsReadForCurrentUser(id: number, userId: number) {
    const notification = await this.findVisibleNotification(id, userId);

    return this.prisma.systemNotification.update({
      where: {
        id: notification.id,
      },
      data: {
        isRead: true,
        readAt: new Date(),
      },
    });
  }

  async markAllAsReadForCurrentUser(userId: number) {
    const where = await this.buildCurrentUserWhere(userId, {
      isRead: false,
    });

    const result = await this.prisma.systemNotification.updateMany({
      where,
      data: {
        isRead: true,
        readAt: new Date(),
      },
    });

    return {
      updated: result.count,
    };
  }

  async softDeleteForCurrentUser(id: number, userId: number) {
    const notification = await this.findVisibleNotification(id, userId);

    return this.prisma.systemNotification.update({
      where: {
        id: notification.id,
      },
      data: {
        deletedAt: new Date(),
      },
    });
  }

  async softDeleteAllForCurrentUser(userId: number) {
    const where = await this.buildCurrentUserWhere(userId, {});
    const result = await this.prisma.systemNotification.updateMany({
      where,
      data: { deletedAt: new Date() },
    });

    return { deleted: result.count };
  }

  async buildCurrentUserWhere(
    userId: number,
    filters: Pick<GetSystemNotificationsQueryDto, 'type' | 'isRead'>,
    cutoff: Date = this.getRetentionCutoff(),
  ): Promise<Prisma.SystemNotificationWhereInput> {
    const validUserId = Number(userId);

    if (!Number.isInteger(validUserId) || validUserId <= 0) {
      throw new BadRequestException('Invalid authenticated user ID.');
    }

    const stakeholderLinks = await this.prisma.stakeholderUser.findMany({
      where: {
        userId: validUserId,
      },
      select: {
        stakeholderId: true,
      },
    });

    const stakeholderIds = Array.from(
      new Set(
        stakeholderLinks
          .map((link) => Number(link.stakeholderId))
          .filter((id) => Number.isInteger(id) && id > 0),
      ),
    );

    const receiverFilters: Prisma.SystemNotificationWhereInput[] = [
      {
        receiverUserId: validUserId,
      },
    ];

    if (stakeholderIds.length > 0) {
      receiverFilters.push({
        receiverStakeholderId: {
          in: stakeholderIds,
        },
      });
    }

    const whereFilters: Prisma.SystemNotificationWhereInput[] = [
      {
        deletedAt: null,
        mobileViews: {
          none: { userId: validUserId, viewedAt: { lte: cutoff } },
        },
      },
      {
        OR: receiverFilters,
      },
    ];

    if (filters.type?.trim()) {
      whereFilters.push({
        type: filters.type.trim(),
      });
    }

    if (typeof filters.isRead === 'boolean') {
      whereFilters.push({
        isRead: filters.isRead,
      });
    }

    return {
      AND: whereFilters,
    };
  }
}
