import webpush from 'web-push';
import logger from '@utils/logger';
import { Subscription } from '@models/Subscription';
import { Notification } from '@models/Notification';

type PushSubscription = any; // Web Push protocol JSON

interface NotificationPayload {
  title: string;
  body: string;
  requestId?: string;
  requestNumber?: string;
  url?: string;
  type?: string;
  priority?: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
  actionRequired?: boolean;
  metadata?: any;
}

class NotificationService {
  private userIdToSubscriptions: Map<string, PushSubscription[]> = new Map();

  configure(vapidPublicKey?: string, vapidPrivateKey?: string, mailto?: string) {
    const pub = vapidPublicKey || process.env.VAPID_PUBLIC_KEY || '';
    const priv = vapidPrivateKey || process.env.VAPID_PRIVATE_KEY || '';
    const contact = mailto || process.env.VAPID_CONTACT || 'mailto:admin@example.com';
    if (!pub || !priv) {
      logger.warn('VAPID keys are not configured. Push notifications are disabled.');
      return;
    }
    webpush.setVapidDetails(contact, pub, priv);
    logger.info('Web Push configured');
  }

  async addSubscription(userId: string, subscription: PushSubscription, userAgent?: string) {
    // Persist to DB (upsert by endpoint)
    try {
      const endpoint: string = subscription?.endpoint || '';
      const keys = subscription?.keys || {};
      if (!endpoint || !keys?.p256dh || !keys?.auth) throw new Error('Invalid subscription payload');
      await Subscription.upsert({
        userId,
        endpoint,
        p256dh: keys.p256dh,
        auth: keys.auth,
        userAgent: userAgent || null,
      } as any);
    } catch (e) {
      logger.error('Failed to persist subscription', e);
    }
    const list = this.userIdToSubscriptions.get(userId) || [];
    const already = list.find((s) => JSON.stringify(s) === JSON.stringify(subscription));
    if (!already) {
      list.push(subscription);
      this.userIdToSubscriptions.set(userId, list);
    }
    logger.info(`Subscription stored for user ${userId}. Total: ${list.length}`);
  }

  /**
   * Remove expired/invalid subscription from database and memory cache
   */
  private async removeExpiredSubscription(userId: string, endpoint: string) {
    try {
      // Remove from database
      await Subscription.destroy({ where: { endpoint } });
      logger.info(`[Notification] Removed expired subscription from DB for user ${userId}, endpoint: ${endpoint.substring(0, 50)}...`);

      // Remove from memory cache
      const list = this.userIdToSubscriptions.get(userId) || [];
      const filtered = list.filter((s) => s.endpoint !== endpoint);
      if (filtered.length !== list.length) {
        this.userIdToSubscriptions.set(userId, filtered);
        logger.info(`[Notification] Removed expired subscription from memory cache for user ${userId}`);
      }
    } catch (error) {
      logger.error(`[Notification] Failed to remove expired subscription for user ${userId}:`, error);
    }
  }

  /**
   * Check if error indicates expired/invalid subscription
   * webpush returns status codes: 410 (Gone), 404 (Not Found), 403 (Forbidden)
   */
  private isExpiredSubscriptionError(err: any): boolean {
    const statusCode = err?.statusCode || err?.status || err?.response?.statusCode;
    // 410 Gone = subscription expired
    // 404 Not Found = subscription doesn't exist
    // 403 Forbidden = subscription invalid
    return statusCode === 410 || statusCode === 404 || statusCode === 403;
  }

  /**
   * Send notification to users - saves to DB and sends via push/socket
   */
  async sendToUsers(userIds: string[], payload: NotificationPayload) {
    const message = JSON.stringify(payload);
    const sentVia: string[] = ['IN_APP']; // Always save to DB for in-app display
    
    for (const userId of userIds) {
      try {
        // 1. Save notification to database for in-app display
        const notification = await Notification.create({
          userId,
          requestId: payload.requestId,
          notificationType: payload.type || 'general',
          title: payload.title,
          message: payload.body,
          isRead: false,
          priority: payload.priority || 'MEDIUM',
          actionUrl: payload.url,
          actionRequired: payload.actionRequired || false,
          metadata: {
            requestNumber: payload.requestNumber,
            ...payload.metadata
          },
          sentVia,
          emailSent: false,
          smsSent: false,
          pushSent: false
        } as any);

        logger.info(`[Notification] Created in-app notification for user ${userId}: ${payload.title}`);

        // 2. Emit real-time socket event for immediate delivery
        try {
          const { emitToUser } = require('../realtime/socket');
          if (emitToUser) {
            emitToUser(userId, 'notification:new', {
              notification: notification.toJSON(),
              ...payload
            });
            logger.info(`[Notification] Emitted socket event to user ${userId}`);
          }
        } catch (socketError) {
          logger.warn(`[Notification] Socket emit failed (not critical):`, socketError);
        }

        // 3. Send push notification (if user has subscriptions)
        let subs = this.userIdToSubscriptions.get(userId) || [];
        // Load from DB if memory empty
        if (subs.length === 0) {
          try {
            const rows = await Subscription.findAll({ where: { userId } });
            subs = rows.map((r: any) => ({ endpoint: r.endpoint, keys: { p256dh: r.p256dh, auth: r.auth } }));
          } catch {}
        }
        
        if (subs.length > 0) {
          for (const sub of subs) {
            try {
              await webpush.sendNotification(sub, message);
              await notification.update({ pushSent: true });
              logger.info(`[Notification] Push sent to user ${userId}`);
            } catch (err: any) {
              // Check if subscription is expired/invalid
              if (this.isExpiredSubscriptionError(err)) {
                logger.warn(`[Notification] Expired subscription detected for user ${userId}, removing...`);
                await this.removeExpiredSubscription(userId, sub.endpoint);
              } else {
                logger.error(`[Notification] Failed to send push to user ${userId}:`, err);
              }
            }
          }
        }
      } catch (error) {
        logger.error(`[Notification] Failed to create notification for user ${userId}:`, error);
        // Continue to next user even if one fails
      }
    }
  }
}

export const notificationService = new NotificationService();
notificationService.configure();



