import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { SendNotificationDto } from './dto/send-notification.dto';
import { FindNotificationsDto } from './dto/find-notifications.dto';
import { RegisterPushTokenDto } from './dto/register-push-token.dto';
import { NotificationSenderService } from './notification-sender.service';
import { Prisma } from '@prisma/client';

export interface PaginatedResult<T> {
  data: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

@Injectable()
export class NotificationsService {
  private readonly logger = new Logger(NotificationsService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly senderService: NotificationSenderService,
  ) {}

  /**
   * Find all notifications for a user with cursor-based pagination.
   * Optionally filter by read/unread status.
   */
  async findAll(userId: string, params: FindNotificationsDto): Promise<PaginatedResult<unknown>> {
    const { cursor, limit = 20, isRead } = params;

    const where: Prisma.NotificationWhereInput = {
      userId,
      ...(isRead !== undefined ? { isRead } : {}),
    };

    const notifications = await this.prisma.notification.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
    });

    const hasMore = notifications.length > limit;
    const data = hasMore ? notifications.slice(0, -1) : notifications;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  /**
   * Mark a single notification as read.
   */
  async markAsRead(userId: string, notificationId: string) {
    const notification = await this.prisma.notification.findFirst({
      where: { id: notificationId, userId },
    });

    if (!notification) {
      throw new NotFoundException(`Notification with id ${notificationId} not found`);
    }

    return this.prisma.notification.update({
      where: { id: notificationId },
      data: { isRead: true },
    });
  }

  /**
   * Mark all unread notifications as read for a user.
   */
  async markAllAsRead(userId: string) {
    const result = await this.prisma.notification.updateMany({
      where: { userId, isRead: false },
      data: { isRead: true },
    });

    return { updatedCount: result.count };
  }

  /**
   * Get the count of unread notifications for a user.
   */
  async getUnreadCount(userId: string): Promise<{ count: number }> {
    const count = await this.prisma.notification.count({
      where: { userId, isRead: false },
    });

    return { count };
  }

  /**
   * Create a notification in DB and dispatch to push delivery.
   */
  async create(clubId: string, dto: SendNotificationDto) {
    const notification = await this.prisma.notification.create({
      data: {
        clubId,
        userId: dto.userId,
        type: dto.type,
        title: dto.title,
        body: dto.body,
        data: dto.data ?? Prisma.JsonNull,
        isRead: false,
        sentAt: new Date(),
      },
    });

    this.logger.log(`Notification created: type=${dto.type}, userId=${dto.userId}`);

    // Dispatch push notification (fire-and-forget)
    this.senderService
      .sendPush(dto.userId, notification)
      .catch((err: unknown) =>
        this.logger.error(
          `Failed to send push: ${err instanceof Error ? err.message : String(err)}`,
          err instanceof Error ? err.stack : undefined,
        ),
      );

    return notification;
  }

  // ─── Notification Settings ──────────────────────────────────────

  private static readonly NOTIFICATION_LABELS: Record<
    string,
    { name: string; description: string }
  > = {
    CLIENT_ASSIGNED: {
      name: 'Назначение клиента',
      description: 'Уведомление при назначении нового клиента тренеру',
    },
    SERVICE_BOOKED: {
      name: 'Запись на услугу',
      description: 'Уведомление при записи клиента на тренировку',
    },
    SERVICE_PAID: { name: 'Оплата услуги', description: 'Уведомление об оплате услуги клиентом' },
    PACKAGE_TOPUP: {
      name: 'Пополнение пакета',
      description: 'Уведомление о пополнении пакета тренировок',
    },
    BOOKING_CANCELLED: {
      name: 'Отмена записи',
      description: 'Уведомление при отмене записи клиентом',
    },
    TRAINING_DEDUCTED: {
      name: 'Списание тренировки',
      description: 'Уведомление о списании тренировки из пакета',
    },
    TRAININGS_ENDING: {
      name: 'Заканчиваются тренировки',
      description: 'Уведомление когда у клиента заканчивается пакет',
    },
    CLIENT_RETURNED: {
      name: 'Возврат клиента',
      description: 'Уведомление о возвращении неактивного клиента',
    },
    CLIENT_BIRTHDAY: {
      name: 'День рождения клиента',
      description: 'Напоминание о дне рождения клиента',
    },
  };

  async getSettings(clubId: string) {
    const settings = await this.prisma.notificationSetting.findMany({
      where: { clubId },
      orderBy: { notificationType: 'asc' },
    });

    return settings.map((s) => {
      const label = NotificationsService.NOTIFICATION_LABELS[s.notificationType];
      return {
        id: s.id,
        name: label?.name ?? s.notificationType,
        description: label?.description ?? '',
        enabled: s.enabled,
        customizable: true,
        template: s.customText ?? '',
      };
    });
  }

  async updateSettings(
    clubId: string,
    notifications: { id: string; enabled: boolean; template?: string }[],
  ) {
    const ops = notifications.map((n) =>
      this.prisma.notificationSetting.update({
        where: { id: n.id },
        data: {
          enabled: n.enabled,
          ...(n.template !== undefined ? { customText: n.template } : {}),
        },
      }),
    );
    await this.prisma.$transaction(ops);
    return { success: true };
  }

  /**
   * Register (upsert) a push token for a user's device.
   * If the same deviceId exists, update the token. Otherwise create new.
   */
  async registerPushToken(userId: string, clubId: string, dto: RegisterPushTokenDto) {
    if (dto.deviceId) {
      // Upsert by deviceId: deactivate old, create or update
      const existing = await this.prisma.pushToken.findFirst({
        where: { userId, deviceId: dto.deviceId },
      });

      if (existing) {
        return this.prisma.pushToken.update({
          where: { id: existing.id },
          data: {
            token: dto.token,
            platform: dto.platform,
            isActive: true,
          },
        });
      }
    }

    return this.prisma.pushToken.create({
      data: {
        clubId,
        userId,
        token: dto.token,
        platform: dto.platform,
        deviceId: dto.deviceId,
        isActive: true,
      },
    });
  }

  /**
   * Deactivate a push token by deviceId.
   */
  async removePushToken(userId: string, deviceId: string) {
    const token = await this.prisma.pushToken.findFirst({
      where: { userId, deviceId },
    });

    if (!token) {
      throw new NotFoundException(`Push token for device ${deviceId} not found`);
    }

    return this.prisma.pushToken.update({
      where: { id: token.id },
      data: { isActive: false },
    });
  }
}
