import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { PrismaService } from '@chatapp/database';
import { RedisService } from '@chatapp/redis';

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly redis: RedisService,
  ) {}

  /**
   * Create a notification record in the audit_logs table (analytics schema).
   * Since there is no dedicated notifications table, we use audit_logs as the base
   * with action='notification' to store notification entries.
   */
  async createNotification(data: {
    workspaceId: string;
    userId: string;
    type: string;
    title: string;
    body?: string;
    entityId?: string;
    entityType?: string;
  }) {
    const notification = await this.prisma.auditLog.create({
      data: {
        workspaceId: data.workspaceId,
        actorId: data.userId,
        actorType: 'user',
        action: `notification:${data.type}`,
        entityType: data.entityType || 'notification',
        entityId: data.entityId || null,
        details: {
          title: data.title,
          body: data.body || null,
          type: data.type,
          read: false,
        } as any,
      },
    });

    return {
      id: notification.id,
      type: data.type,
      title: data.title,
      body: data.body,
      entity_type: notification.entityType,
      entity_id: notification.entityId,
      read: false,
      created_at: notification.createdAt,
    };
  }

  /**
   * List notifications for a user.
   * We query audit_logs where action starts with 'notification:' and actorId is the user.
   */
  async listNotifications(userId: string, wsId: string, query: any) {
    const limit = Math.min(parseInt(query.limit) || 50, 100);
    const offset = query.offset ? parseInt(query.offset) : 0;
    const unreadOnly = query.unread === 'true';

    const whereClause: any = {
      workspaceId: wsId,
      actorId: userId,
      action: { startsWith: 'notification:' },
    };

    if (unreadOnly) {
      whereClause.details = {
        path: ['read'],
        equals: false,
      };
    }

    const [notifications, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        orderBy: { createdAt: 'desc' },
        take: limit,
        skip: offset,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
    ]);

    return {
      notifications: notifications.map((n) => ({
        id: n.id,
        action: n.action,
        type: n.action.replace('notification:', ''),
        title: (n.details as any)?.title || '',
        body: (n.details as any)?.body || null,
        entity_type: n.entityType,
        entity_id: n.entityId,
        read: (n.details as any)?.read || false,
        created_at: n.createdAt,
      })),
      total,
      limit,
      offset,
    };
  }

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

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

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

    const updatedDetails = { ...(notification.details as any), read: true };

    await this.prisma.auditLog.update({
      where: { id: notificationId },
      data: { details: updatedDetails as any },
    });

    return { id: notificationId, read: true };
  }

  /**
   * Mark all notifications as read for a user.
   */
  async markAllAsRead(userId: string, wsId: string) {
    const notifications = await this.prisma.auditLog.findMany({
      where: {
        workspaceId: wsId,
        actorId: userId,
        action: { startsWith: 'notification:' },
      },
    });

    // Update each notification's details to mark as read
    const updatePromises = notifications.map((n) => {
      const details = n.details as any;
      if (details && !details.read) {
        return this.prisma.auditLog.update({
          where: { id: n.id },
          data: { details: { ...details, read: true } as any },
        });
      }
      return Promise.resolve();
    });

    await Promise.all(updatePromises);

    return { marked_all_read: true, count: notifications.length };
  }

  /**
   * Get notification preferences for a user in a workspace.
   */
  async getPreferences(userId: string, wsId: string) {
    let prefs = await this.prisma.notificationPreference.findFirst({
      where: { userId, workspaceId: wsId },
    });

    // Create default preferences if they don't exist
    if (!prefs) {
      prefs = await this.prisma.notificationPreference.create({
        data: {
          userId,
          workspaceId: wsId,
        },
      });
    }

    return {
      notify_mentions: prefs.notifyMentions,
      notify_all_messages: prefs.notifyAllMessages,
      notify_threads: prefs.notifyThreads,
      notify_dms: prefs.notifyDms,
      notify_huddles: prefs.notifyHuddles,
      push_enabled: prefs.pushEnabled,
      email_enabled: prefs.emailEnabled,
      email_frequency: prefs.emailFrequency,
      desktop_enabled: prefs.desktopEnabled,
      mobile_enabled: prefs.mobileEnabled,
      sound_enabled: prefs.soundEnabled,
      dnd_enabled: prefs.dndEnabled,
      dnd_start: prefs.dndStart,
      dnd_end: prefs.dndEnd,
      keywords: prefs.keywords,
    };
  }

  // ---------------------------------------------------------------------------
  // Push token management
  // ---------------------------------------------------------------------------

  async registerPushToken(userId: string, data: {
    platform: string;
    token: string;
    deviceId?: string;
    appVersion?: string;
  }) {
    // Check if token already exists for this user
    const existing = await this.prisma.pushToken.findFirst({
      where: { userId, token: data.token, isActive: true },
    });

    if (existing) {
      return existing;
    }

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

  async unregisterPushToken(userId: string, token: string) {
    const pushToken = await this.prisma.pushToken.findFirst({
      where: { userId, token, isActive: true },
    });

    if (!pushToken) {
      throw new NotFoundException('Push token not found');
    }

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

    return { unregistered: true };
  }

  async listPushTokens(userId: string) {
    return this.prisma.pushToken.findMany({
      where: { userId, isActive: true },
      select: {
        id: true,
        platform: true,
        deviceId: true,
        appVersion: true,
        createdAt: true,
      },
    });
  }

  /**
   * Send push notification to all active devices of a user.
   * Pushes the notification payload to a Redis queue for async processing.
   */
  async sendPushNotification(userId: string, payload: {
    title: string;
    body: string;
    data?: any;
  }) {
    const tokens = await this.prisma.pushToken.findMany({
      where: { userId, isActive: true },
    });

    if (tokens.length === 0) return { sent: 0 };

    // Queue each push via Redis for the workers to process
    for (const token of tokens) {
      await this.redis.client?.lpush('bull:push_notifications', JSON.stringify({
        platform: token.platform,
        token: token.token,
        title: payload.title,
        body: payload.body,
        data: payload.data || {},
      }));
    }

    this.logger.log(`Queued ${tokens.length} push notifications for user ${userId}`);
    return { sent: tokens.length };
  }
  /**
   * Update notification preferences for a user in a workspace.
   */
  async updatePreferences(userId: string, wsId: string, dto: any) {
    const data: any = {};
    if (dto.notify_mentions !== undefined) data.notifyMentions = dto.notify_mentions;
    if (dto.notify_all_messages !== undefined) data.notifyAllMessages = dto.notify_all_messages;
    if (dto.notify_threads !== undefined) data.notifyThreads = dto.notify_threads;
    if (dto.notify_dms !== undefined) data.notifyDms = dto.notify_dms;
    if (dto.notify_huddles !== undefined) data.notifyHuddles = dto.notify_huddles;
    if (dto.push_enabled !== undefined) data.pushEnabled = dto.push_enabled;
    if (dto.email_enabled !== undefined) data.emailEnabled = dto.email_enabled;
    if (dto.email_frequency !== undefined) data.emailFrequency = dto.email_frequency;
    if (dto.desktop_enabled !== undefined) data.desktopEnabled = dto.desktop_enabled;
    if (dto.mobile_enabled !== undefined) data.mobileEnabled = dto.mobile_enabled;
    if (dto.sound_enabled !== undefined) data.soundEnabled = dto.sound_enabled;
    if (dto.dnd_enabled !== undefined) data.dndEnabled = dto.dnd_enabled;
    if (dto.dnd_start !== undefined) data.dndStart = dto.dnd_start;
    if (dto.dnd_end !== undefined) data.dndEnd = dto.dnd_end;
    if (dto.keywords !== undefined) data.keywords = dto.keywords;

    let prefs = await this.prisma.notificationPreference.findFirst({
      where: { userId, workspaceId: wsId },
    });

    if (!prefs) {
      prefs = await this.prisma.notificationPreference.create({
        data: { userId, workspaceId: wsId, ...data },
      });
    } else {
      prefs = await this.prisma.notificationPreference.update({
        where: { id: prefs.id },
        data,
      });
    }

    return {
      notify_mentions: prefs.notifyMentions,
      notify_all_messages: prefs.notifyAllMessages,
      notify_threads: prefs.notifyThreads,
      notify_dms: prefs.notifyDms,
      notify_huddles: prefs.notifyHuddles,
      push_enabled: prefs.pushEnabled,
      email_enabled: prefs.emailEnabled,
      email_frequency: prefs.emailFrequency,
      desktop_enabled: prefs.desktopEnabled,
      mobile_enabled: prefs.mobileEnabled,
      sound_enabled: prefs.soundEnabled,
      dnd_enabled: prefs.dndEnabled,
      keywords: prefs.keywords,
    };
  }
}