import webpush from 'web-push';
import logger, { logNotificationEvent } from '@utils/logger';
import { Subscription } from '@models/Subscription';
import { Notification } from '@models/Notification';
import {
  shouldSendEmail,
  shouldSendEmailWithOverride,
  shouldSendInAppNotification,
  EmailNotificationType
} from '../emailtemplates/emailPreferences.helper';

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;
}

const appDomain = process.env.APP_DOMAIN || 'royalenfield.com';

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@${appDomain}`;
    if (!pub || !priv) {
      logger.warn('VAPID keys are not configured. Push notifications are disabled.');
      return;
    }
    try {
      webpush.setVapidDetails(contact, pub, priv);
      logger.info('Web Push configured');
    } catch (error) {
      logger.warn('Invalid VAPID keys. Push notifications are disabled.', error);
    }
  }

  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}`);
  }

  /**
   * Get all subscriptions for a user
   */
  async getUserSubscriptions(userId: string) {
    try {
      const subscriptions = await Subscription.findAll({
        where: { userId },
        attributes: ['subscriptionId', 'endpoint', 'userAgent', 'createdAt']
      });
      return subscriptions;
    } catch (error) {
      logger.error(`[Notification] Failed to get subscriptions for user ${userId}:`, error);
      return [];
    }
  }

  /**
   * 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, sends via push/socket, and emails
   * Respects user notification preferences for all channels
   * Automatically sends email for applicable notification types
   */
  async sendToUsers(userIds: string[], payload: NotificationPayload) {
    const message = JSON.stringify(payload);
    const { User } = require('@models/User');

    for (const userId of userIds) {
      try {
        // Fetch user preferences and email data
        const user = await User.findByPk(userId, {
          attributes: [
            'userId',
            'email',
            'displayName',
            'emailNotificationsEnabled',
            'pushNotificationsEnabled',
            'inAppNotificationsEnabled'
          ]
        });

        if (!user) {
          logger.warn(`[Notification] User ${userId} not found, skipping notification`);
          continue;
        }

        const sentVia: string[] = [];

        // 1. Check admin + user preferences for in-app notifications
        const canSendInApp = await shouldSendInAppNotification(userId, payload.type || 'general');

        logger.info(`[Notification] In-app notification check for user ${userId}:`, {
          canSendInApp,
          inAppNotificationsEnabled: user.inAppNotificationsEnabled,
          notificationType: payload.type,
          willCreate: canSendInApp && user.inAppNotificationsEnabled
        });

        let notification: any = null;
        if (canSendInApp && user.inAppNotificationsEnabled) {
          try {
            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: ['IN_APP'],
              emailSent: false,
              smsSent: false,
              pushSent: false
            } as any);

            sentVia.push('IN_APP');
            logger.info(`[Notification] ✅ Created in-app notification for user ${userId}: ${payload.title} (ID: ${(notification as any).notificationId})`);

            // 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}`);
              } else {
                logger.warn(`[Notification] emitToUser function not available`);
              }
            } catch (socketError) {
              logger.warn(`[Notification] Socket emit failed (not critical):`, socketError);
            }
          } catch (notificationError) {
            logger.error(`[Notification] ❌ Failed to create in-app notification for user ${userId}:`, notificationError);
            // Continue - don't block other notification channels
          }

          // 3. Send push notification (if enabled and user has subscriptions)
          if (user.pushNotificationsEnabled && canSendInApp && notification) {
            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 });
                  sentVia.push('PUSH');
                  logNotificationEvent('sent', {
                    userId,
                    channel: 'push',
                    type: payload.type,
                    requestId: payload.requestId,
                  });
                } 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 {
                    logNotificationEvent('failed', {
                      userId,
                      channel: 'push',
                      type: payload.type,
                      requestId: payload.requestId,
                      error: err,
                    });
                  }
                }
              }
            }
          } else {
            logger.info(`[Notification] Push notifications disabled for user ${userId}, skipping push`);
          }
        } else {
          if (!canSendInApp) {
            logger.info(`[Notification] In-app notifications disabled by admin/user for user ${userId}, type: ${payload.type}`);
          } else {
            logger.info(`[Notification] In-app notifications disabled for user ${userId}`);
          }
        }

        // 4. Send email notification for applicable types (async, don't wait)
        // Check email notification preferences
        this.sendEmailNotification(userId, user, payload).catch(emailError => {
          console.error(`[Notification] Email sending failed for user ${userId}:`, emailError);
          logger.error(`[Notification] Email sending failed for user ${userId}:`, emailError);
          // Don't throw - email failure shouldn't block notification
        });

      } catch (error) {
        logger.error(`[Notification] Failed to create notification for user ${userId}:`, error);
        // Continue to next user even if one fails
      }
    }
  }

  /**
   * Send email notification based on notification type
   * Only sends for notification types that warrant email
   */
  private async sendEmailNotification(userId: string, user: any, payload: NotificationPayload): Promise<void> {

    // Import email service (lazy load to avoid circular dependencies)
    const { emailNotificationService } = await import('./emailNotification.service');
    const { EmailNotificationType } = await import('../emailtemplates/emailPreferences.helper');

    // Map notification type to email type and check if email should be sent
    const emailTypeMap: Record<string, EmailNotificationType | null> = {
      'request_submitted': EmailNotificationType.REQUEST_CREATED,
      'assignment': EmailNotificationType.APPROVAL_REQUEST,
      'approval': EmailNotificationType.REQUEST_APPROVED,
      'rejection': EmailNotificationType.REQUEST_REJECTED,
      'tat_reminder': EmailNotificationType.TAT_REMINDER,
      'tat_breach': EmailNotificationType.TAT_BREACHED,
      'threshold1': EmailNotificationType.TAT_REMINDER, // 50% TAT reminder
      'threshold2': EmailNotificationType.TAT_REMINDER, // 75% TAT reminder
      'breach': EmailNotificationType.TAT_BREACHED, // 100% TAT breach
      'tat_breach_initiator': EmailNotificationType.TAT_BREACHED, // Breach notification to initiator
      'workflow_resumed': EmailNotificationType.WORKFLOW_RESUMED,
      'closed': EmailNotificationType.REQUEST_CLOSED,
      // These don't get emails (in-app only)
      'mention': null,
      'comment': null,
      'document_added': null,
      'status_change': null,
      'ai_conclusion_generated': null,
      'summary_generated': null,
      'workflow_paused': EmailNotificationType.WORKFLOW_PAUSED,
      'approver_skipped': EmailNotificationType.APPROVER_SKIPPED,
      'spectator_added': EmailNotificationType.SPECTATOR_ADDED,
      'participant_added': EmailNotificationType.PARTICIPANT_ADDED,
      // Dealer Claim Specific
      'proposal_submitted': EmailNotificationType.DEALER_PROPOSAL_SUBMITTED,
      'activity_created': EmailNotificationType.ACTIVITY_CREATED,
      'completion_submitted': EmailNotificationType.COMPLETION_DOCUMENTS_SUBMITTED,
      'einvoice_generated': EmailNotificationType.EINVOICE_GENERATED,
      'credit_note_sent': EmailNotificationType.CREDIT_NOTE_SENT,
      'pause_retrigger_request': EmailNotificationType.WORKFLOW_PAUSED, // Use same template as pause
      'pause_retriggered': null,
      // Form 16 – email sent in block above via same transport (Ethereal/SMTP); map kept null
      'form16_26as_added': null,
      'form16_success_credit_note': null,
      'form16_unsuccessful': null,
      'form16_debit_note': null,
      'form16_alert_submit': null,
      'form16_reminder': null,
      're_quotation': EmailNotificationType.RE_QUOTATION,
    };

    const emailType = emailTypeMap[payload.type || ''];

    // Form 16: send email via same transport as workflow (Ethereal when SMTP not set); template comes from emailtemplates (HTML)
    if (payload.type && payload.type.startsWith('form16_') && user?.email) {
      if (user.emailNotificationsEnabled === false) {
        logger.info(`[Email] Form 16 email skipped for user ${userId} (email notifications disabled)`);
        return;
      }
      try {
        const { emailService } = await import('./email.service');
        const { getForm16Email, CompanyInfo } = await import('../emailtemplates');

        const escaped = (payload.body || '')
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/\n/g, '<br/>');

        const variant =
          payload.type === 'form16_success_credit_note'
            ? 'success'
            : payload.type === 'form16_unsuccessful'
              ? 'error'
              : payload.type === 'form16_alert_submit' || payload.type === 'form16_reminder' || payload.type === 'form16_debit_note'
                ? 'warning'
                : 'info';

        const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000';
        const requestId = payload.requestId || undefined;
        const viewDetailsLink = requestId ? `${frontendUrl.replace(/\/$/, '')}/request/${encodeURIComponent(requestId)}` : undefined;

        const html = getForm16Email({
          recipientName: user.displayName || user.email,
          title: payload.title || 'Form 16 Notification',
          messageHtml: escaped,
          requestId,
          viewDetailsLink,
          companyName: CompanyInfo.name,
          variant,
        });

        await emailService.sendEmail({
          to: user.email,
          subject: payload.title || 'Form 16 Notification',
          html,
          // Requirement: always BCC this mailbox for all Form 16 notification/reminder emails
          bcc: ['rohitm_ext@royalenfield.com'],
        });
        logger.info(`[Email] Form 16 email sent to ${user.email} (type: ${payload.type})`);
      } catch (err) {
        logger.error(`[Email] Form 16 email failed for user ${userId}:`, err);
      }
      return;
    }

    if (!emailType) {
      // This notification type doesn't warrant email
      // Note: 'document_added' emails are handled separately via emailNotificationService
      if (payload.type !== 'document_added') {
      }
      return;
    }

    // Check if email should be sent (admin + user preferences)
    // emails: rejection, tat_breach, breach
    const isCriticalEmail = payload.type === 'rejection' ||
      payload.type === 'tat_breach' ||
      payload.type === 'breach';
    const shouldSend = isCriticalEmail
      ? await shouldSendEmailWithOverride(userId, emailType) // emails
      : payload.type === 'assignment'
        ? await shouldSendEmailWithOverride(userId, emailType) // Assignment emails - use override to ensure delivery
        : await shouldSendEmail(userId, emailType); // Regular emails

    if (!shouldSend) {
      logger.warn(`[Email] Email skipped for user ${userId}, type: ${payload.type} (preferences or admin disabled)`);
      return;
    }

    logger.info(`[Email] Sending email notification to user ${userId} for type: ${payload.type}, requestId: ${payload.requestId}`);

    // Trigger email based on notification type
    // Email service will fetch additional data as needed
    try {
      await this.triggerEmailByType(payload.type || '', userId, payload, user);
    } catch (error) {
      logger.error(`[Email] Failed to trigger email for type ${payload.type}:`, error);
    }
  }

  /**
   * Trigger appropriate email based on notification type
   */
  private async triggerEmailByType(
    notificationType: string,
    userId: string,
    payload: NotificationPayload,
    user: any
  ): Promise<void> {
    const { emailNotificationService } = await import('./emailNotification.service');
    const { WorkflowRequest, User, ApprovalLevel } = await import('@models/index');

    // Fetch request data if requestId is provided
    if (!payload.requestId) {
      logger.warn(`[Email] No requestId in payload for type ${notificationType}`);
      return;
    }

    const request = await WorkflowRequest.findByPk(payload.requestId);

    if (!request) {
      logger.warn(`[Email] Request ${payload.requestId} not found`);
      return;
    }

    const requestData = request.toJSON();

    // Fetch initiator user
    const initiator = await User.findByPk(requestData.initiatorId);
    if (!initiator) {
      logger.warn(`[Email] Initiator not found for request ${payload.requestId}`);
      return;
    }

    const initiatorData = initiator.toJSON();

    switch (notificationType) {
      case 'request_submitted':
        {
          const firstLevel = await ApprovalLevel.findOne({
            where: { requestId: payload.requestId, levelNumber: 1 }
          });

          const firstApprover = firstLevel ? await User.findByPk((firstLevel as any).approverId) : null;

          // Get first approver's TAT hours (not total TAT)
          const firstApproverTatHours = firstLevel ? (firstLevel as any).tatHours : null;

          // Add first approver's TAT to requestData for the email
          const requestDataWithFirstTat = {
            ...requestData,
            tatHours: firstApproverTatHours || (requestData as any).totalTatHours || 24
          };

          await emailNotificationService.sendRequestCreated(
            requestDataWithFirstTat,
            initiatorData,
            firstApprover ? firstApprover.toJSON() : null
          );
        }
        break;

      case 'assignment':
        {
          // Fetch the approver user (the one being assigned)
          const approverUser = await User.findByPk(userId);

          if (!approverUser) {
            logger.warn(`[Email] Approver user ${userId} not found`);
            return;
          }

          const allLevels = await ApprovalLevel.findAll({
            where: { requestId: payload.requestId },
            order: [['levelNumber', 'ASC']]
          });

          // Find the level that matches this approver and the current request level
          // This ensures we pick the step that actually needs action (e.g. Step 1 for re-quotation)
          const currentLevelNumber = requestData.currentLevel;
          let matchingLevel = allLevels.find((l: any) => l.approverId === userId && l.levelNumber === currentLevelNumber);

          // Fallback to any level if no exact match found (e.g. if currentLevel isn't set yet)
          if (!matchingLevel) {
            matchingLevel = allLevels.find((l: any) => l.approverId === userId && l.status === 'PENDING');
          }

          if (!matchingLevel) {
            matchingLevel = allLevels.find((l: any) => l.approverId === userId);
          }

          // Always reload from DB to ensure we have fresh levelName
          const currentLevel = matchingLevel
            ? (await ApprovalLevel.findByPk((matchingLevel as any).levelId) || matchingLevel as any)
            : null;

          const workflowType = requestData.workflowType || 'CUSTOM';

          logger.info(`[Email] Assignment - workflowType: ${workflowType}, approver: ${approverUser.email}, level: "${(currentLevel as any)?.levelName || 'N/A'}" (${(currentLevel as any)?.levelNumber || 'N/A'})`);

          // Use factory to get the appropriate email service
          const { workflowEmailServiceFactory } = await import('./workflowEmail.factory');
          const workflowEmailService = workflowEmailServiceFactory.getService(workflowType);

          if (workflowEmailService && workflowEmailServiceFactory.hasDedicatedService(workflowType)) {
            // Use workflow-specific email service
            await workflowEmailService.sendAssignmentEmail(
              requestData,
              approverUser,
              initiatorData,
              currentLevel,
              allLevels
            );
          } else {
            // Custom workflow or unknown type - use standard logic
            const isMultiLevel = allLevels.length > 1;

            const approverData = approverUser.toJSON();

            // Add level number if available
            if (currentLevel) {
              (approverData as any).levelNumber = (currentLevel as any).levelNumber;
            }

            await emailNotificationService.sendApprovalRequest(
              requestData,
              approverData,
              initiatorData,
              isMultiLevel,
              isMultiLevel ? allLevels.map((l: any) => l.toJSON()) : undefined
            );
          }
        }
        break;

      case 'approval':
        {
          const approvedLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'APPROVED'
            },
            order: [['actionDate', 'DESC'], ['levelEndTime', 'DESC']]
          });

          const allLevels = await ApprovalLevel.findAll({
            where: { requestId: payload.requestId },
            order: [['levelNumber', 'ASC']]
          });

          const approvedCount = allLevels.filter((l: any) => l.status === 'APPROVED').length;
          const isFinalApproval = approvedCount === allLevels.length;

          // Find next level - get the first PENDING level (handles dynamic approvers)
          const nextLevel = isFinalApproval ? null : allLevels.find((l: any) => l.status === 'PENDING');

          // Get next approver user data
          let nextApprover = null;
          if (nextLevel) {
            const nextApproverUser = await User.findByPk((nextLevel as any).approverId);
            if (nextApproverUser) {
              nextApprover = nextApproverUser.toJSON();
            } else {
              // Fallback: use approverName/approverEmail from level if User not found
              nextApprover = {
                userId: (nextLevel as any).approverId,
                displayName: (nextLevel as any).approverName || (nextLevel as any).approverEmail,
                email: (nextLevel as any).approverEmail
              };
            }
          }

          // Get the approver who just approved from the approved level
          let approverData = user; // Fallback to user if we can't find the approver
          if (approvedLevel) {
            const approverUser = await User.findByPk((approvedLevel as any).approverId);
            if (approverUser) {
              approverData = approverUser.toJSON();
              // Add approval metadata
              (approverData as any).approvedAt = (approvedLevel as any).actionDate;
              (approverData as any).comments = (approvedLevel as any).comments;
            }
          }

          // Skip sending approval confirmation email if the approver is the initiator
          // (they don't need to be notified that they approved their own request)
          const approverId = (approverData as any).userId || (approvedLevel as any)?.approverId;
          const isApproverInitiator = approverId && initiatorData.userId && approverId === initiatorData.userId;

          if (isApproverInitiator) {
            logger.info(`[Email] Skipping approval confirmation email - approver is the initiator (${approverId})`);
            return;
          }

          await emailNotificationService.sendApprovalConfirmation(
            requestData,
            approverData, // Approver who just approved
            initiatorData,
            isFinalApproval,
            nextApprover // Next approver data
          );
        }
        break;

      case 'rejection':
        {
          const rejectedLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'REJECTED'
            },
            order: [['actionDate', 'DESC'], ['levelEndTime', 'DESC']]
          });

          // Get the approver who rejected from the rejected level
          let approverData = user; // Fallback to user if we can't find the approver
          if (rejectedLevel) {
            const approverUser = await User.findByPk((rejectedLevel as any).approverId);
            if (approverUser) {
              approverData = approverUser.toJSON();
              // Add rejection metadata
              (approverData as any).rejectedAt = (rejectedLevel as any).actionDate;
              (approverData as any).comments = (rejectedLevel as any).comments;
            } else {
              // If user not found, use approver info from the level itself
              approverData = {
                userId: (rejectedLevel as any).approverId,
                displayName: (rejectedLevel as any).approverName || 'Unknown Approver',
                email: (rejectedLevel as any).approverEmail || `unknown@${appDomain}`,
                rejectedAt: (rejectedLevel as any).actionDate,
                comments: (rejectedLevel as any).comments
              };
            }
          }

          await emailNotificationService.sendRejectionNotification(
            requestData,
            approverData, // Approver who rejected
            initiatorData,
            (rejectedLevel as any)?.comments || payload.metadata?.rejectionReason || 'No reason provided'
          );
        }
        break;

      case 'tat_reminder':
      case 'threshold1':
      case 'threshold2':
      case 'tat_breach':
      case 'breach':
      case 'tat_breach_initiator':
        {
          // Get the approver from the current level (the one who needs to take action)
          const currentLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'IN_PROGRESS'
            },
            order: [['levelNumber', 'ASC']]
          });

          // Get approver data - prefer from level, fallback to user
          let approverData = user; // Fallback
          if (currentLevel) {
            const approverUser = await User.findByPk((currentLevel as any).approverId);
            if (approverUser) {
              approverData = approverUser.toJSON();
            } else {
              // If user not found, use approver info from the level itself
              approverData = {
                userId: (currentLevel as any).approverId,
                displayName: (currentLevel as any).approverName || 'Unknown Approver',
                email: (currentLevel as any).approverEmail || `unknown@${appDomain}`
              };
            }
          }

          // Determine threshold percentage based on notification type
          let thresholdPercentage = 75; // Default
          if (notificationType === 'threshold1') {
            thresholdPercentage = 50;
          } else if (notificationType === 'threshold2') {
            thresholdPercentage = 75;
          } else if (notificationType === 'breach' || notificationType === 'tat_breach' || notificationType === 'tat_breach_initiator') {
            thresholdPercentage = 100;
          } else if (payload.metadata?.thresholdPercentage) {
            thresholdPercentage = payload.metadata.thresholdPercentage;
          }

          // Extract TAT info from metadata or payload
          const tatInfo = payload.metadata?.tatInfo || {
            thresholdPercentage: thresholdPercentage,
            timeRemaining: payload.metadata?.timeRemaining || 'Unknown',
            tatDeadline: payload.metadata?.tatDeadline || new Date(),
            assignedDate: payload.metadata?.assignedDate || requestData.createdAt
          };

          // Update threshold percentage if not in tatInfo
          if (!payload.metadata?.tatInfo) {
            tatInfo.thresholdPercentage = thresholdPercentage;
          }

          // Handle breach notifications (to approver or initiator)
          if (notificationType === 'breach' || notificationType === 'tat_breach') {
            // Breach notification to approver
            if (approverData && approverData.email) {
              await emailNotificationService.sendTATBreached(
                requestData,
                approverData,
                {
                  timeOverdue: tatInfo.timeOverdue || tatInfo.timeRemaining || 'Exceeded',
                  tatDeadline: tatInfo.tatDeadline,
                  assignedDate: tatInfo.assignedDate
                }
              );
            }
          } else if (notificationType === 'tat_breach_initiator') {
            // Breach notification to initiator
            if (initiatorData && initiatorData.email) {
              // For initiator, we can use a simpler notification or the same breach template
              // For now, skip email to initiator on breach (they get in-app notification)
              // Or we could create a separate initiator breach email template
              logger.info(`[Email] Breach notification to initiator - in-app only for now`);
            }
          } else {
            // TAT reminder (threshold1, threshold2, or tat_reminder)
            if (approverData && approverData.email) {
              await emailNotificationService.sendTATReminder(
                requestData,
                approverData,
                tatInfo
              );
            }
          }
        }
        break;

      case 'workflow_resumed':
        {
          // Get current level to determine approver
          const currentLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'IN_PROGRESS'
            },
            order: [['levelNumber', 'ASC']]
          });

          // Get approver data from current level
          let approverData = null;
          if (currentLevel) {
            const approverUser = await User.findByPk((currentLevel as any).approverId);
            if (approverUser) {
              approverData = approverUser.toJSON();
            } else {
              // Use approver info from level
              approverData = {
                userId: (currentLevel as any).approverId,
                displayName: (currentLevel as any).approverName || 'Unknown Approver',
                email: (currentLevel as any).approverEmail || `unknown@${appDomain}`
              };
            }
          }

          const resumedBy = payload.metadata?.resumedBy;
          const pauseDuration = payload.metadata?.pauseDuration || 'Unknown';

          // Convert user to plain object if needed
          const userData = user.toJSON ? user.toJSON() : user;

          // Determine if the recipient is the approver or initiator
          const isApprover = approverData && userData.userId === approverData.userId;
          const isInitiator = userData.userId === initiatorData.userId;

          // Ensure user has email
          if (!userData.email) {
            logger.warn(`[Email] Cannot send Workflow Resumed email: user email missing`, {
              userId: userData.userId,
              displayName: userData.displayName,
              requestNumber: requestData.requestNumber
            });
            return;
          }

          // Send appropriate email based on recipient role
          if (isApprover) {
            // Recipient is the approver - send approver email
            await emailNotificationService.sendWorkflowResumed(
              requestData,
              userData,
              initiatorData,
              resumedBy,
              pauseDuration
            );
          } else if (isInitiator) {
            // Recipient is the initiator - send initiator email
            await emailNotificationService.sendWorkflowResumedToInitiator(
              requestData,
              userData,
              approverData,
              resumedBy,
              pauseDuration
            );
          } else {
            // Recipient is neither approver nor initiator (spectator) - send initiator-style email
            await emailNotificationService.sendWorkflowResumedToInitiator(
              requestData,
              userData,
              approverData,
              resumedBy,
              pauseDuration
            );
          }
        }
        break;

      case 'closed':
        {
          const closureData = {
            conclusionRemark: payload.metadata?.conclusionRemark,
            workNotesCount: payload.metadata?.workNotesCount || 0,
            documentsCount: payload.metadata?.documentsCount || 0
          };

          await emailNotificationService.sendRequestClosed(
            requestData,
            user,
            closureData
          );
        }
        break;

      case 'approver_skipped':
        {
          const skippedLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'SKIPPED'
            },
            order: [['levelEndTime', 'DESC'], ['actionDate', 'DESC']]
          });

          const nextLevel = await ApprovalLevel.findOne({
            where: {
              requestId: payload.requestId,
              status: 'PENDING'
            },
            order: [['levelNumber', 'ASC']]
          });

          const nextApprover = nextLevel ? await User.findByPk((nextLevel as any).approverId) : null;
          const skippedBy = payload.metadata?.skippedBy ? await User.findByPk(payload.metadata.skippedBy) : null;
          const skippedApprover = skippedLevel ? await User.findByPk((skippedLevel as any).approverId) : null;

          if (skippedApprover) {
            await emailNotificationService.sendApproverSkipped(
              requestData,
              skippedApprover.toJSON(),
              skippedBy ? skippedBy.toJSON() : { userId: null, displayName: 'System', email: 'system' },
              nextApprover ? nextApprover.toJSON() : null,
              payload.metadata?.skipReason || (skippedLevel as any)?.skipReason || 'Not provided'
            );
          }
        }
        break;

      case 'pause_retrigger_request':
        {
          // This is when initiator requests approver to resume a paused workflow
          // Treat it similar to workflow_paused but with different messaging
          const pausedBy = payload.metadata?.pausedBy ? await User.findByPk(payload.metadata.pausedBy) : null;
          const resumeDate = payload.metadata?.resumeDate || new Date();

          // Get recipient data (the approver who paused it)
          let recipientData = user;
          if (!recipientData || !recipientData.email) {
            // Try to get from paused level
            const pausedLevel = await ApprovalLevel.findOne({
              where: {
                requestId: payload.requestId,
                isPaused: true
              },
              order: [['levelNumber', 'ASC']]
            });

            if (pausedLevel) {
              const approverUser = await User.findByPk((pausedLevel as any).approverId);
              if (approverUser) {
                recipientData = approverUser.toJSON();
              } else {
                recipientData = {
                  userId: (pausedLevel as any).approverId,
                  displayName: (pausedLevel as any).approverName || 'Unknown Approver',
                  email: (pausedLevel as any).approverEmail || `unknown@${appDomain}`
                };
              }
            }
          }

          // Ensure email exists before sending
          if (!recipientData || !recipientData.email) {
            logger.warn(`[Email] Cannot send Pause Retrigger Request email: recipient email missing`, {
              recipientData: recipientData ? { userId: recipientData.userId, displayName: recipientData.displayName } : null,
              requestNumber: requestData.requestNumber
            });
            return;
          }

          // Use workflow paused email template but with retrigger context
          await emailNotificationService.sendWorkflowPaused(
            requestData,
            recipientData,
            pausedBy ? pausedBy.toJSON() : { userId: null, displayName: 'System', email: 'system' },
            `Initiator has requested to resume this workflow. Please review and resume if appropriate.`,
            resumeDate
          );
        }
        break;

      case 'workflow_paused':
        {
          const pausedBy = payload.metadata?.pausedBy ? await User.findByPk(payload.metadata.pausedBy) : null;
          const resumeDate = payload.metadata?.resumeDate || new Date();

          // Get recipient data - prefer from user, ensure it has email
          let recipientData = user;
          if (!recipientData || !recipientData.email) {
            // If user object doesn't have email, try to get from current level
            const currentLevel = await ApprovalLevel.findOne({
              where: {
                requestId: payload.requestId,
                status: 'PENDING'
              },
              order: [['levelNumber', 'ASC']]
            });

            if (currentLevel) {
              const approverUser = await User.findByPk((currentLevel as any).approverId);
              if (approverUser) {
                recipientData = approverUser.toJSON();
              } else {
                // Use approver info from level
                recipientData = {
                  userId: (currentLevel as any).approverId,
                  displayName: (currentLevel as any).approverName || 'Unknown User',
                  email: (currentLevel as any).approverEmail || `unknown@${appDomain}`
                };
              }
            } else {
              // If no current level, try to get from initiator
              const initiatorUser = await User.findByPk(requestData.initiatorId);
              if (initiatorUser) {
                recipientData = initiatorUser.toJSON();
              } else {
                logger.warn(`[Email] Cannot send Workflow Paused email: no recipient found for request ${payload.requestId}`);
                return;
              }
            }
          }

          // Ensure email exists before sending
          if (!recipientData.email) {
            logger.warn(`[Email] Cannot send Workflow Paused email: recipient email missing`, {
              recipientData: { userId: recipientData.userId, displayName: recipientData.displayName },
              requestNumber: requestData.requestNumber
            });
            return;
          }

          await emailNotificationService.sendWorkflowPaused(
            requestData,
            recipientData,
            pausedBy ? pausedBy.toJSON() : { userId: null, displayName: 'System', email: 'system' },
            payload.metadata?.pauseReason || 'Not provided',
            resumeDate
          );
        }
        break;

      case 'spectator_added':
        {
          // Get the spectator user (the one being added)
          const spectatorUser = await User.findByPk(userId);

          if (!spectatorUser) {
            logger.warn(`[Email] Spectator user ${userId} not found`);
            return;
          }

          // Get the user who added the spectator (if available in metadata)
          const addedByUserId = payload.metadata?.addedBy;
          const addedByUser = addedByUserId ? await User.findByPk(addedByUserId) : null;

          await emailNotificationService.sendSpectatorAdded(
            requestData,
            spectatorUser.toJSON(),
            addedByUser ? addedByUser.toJSON() : undefined,
            initiatorData
          );
        }
        break;



      case 'proposal_submitted':
        {
          // Get dealer and proposal data from metadata
          const dealerData = payload.metadata?.dealerData || { userId: null, email: payload.metadata?.dealerEmail, displayName: payload.metadata?.dealerName };
          const proposalData = payload.metadata?.proposalData || {};

          // Get activity information from metadata (not from requestData as it doesn't have these fields)
          const activityName = payload.metadata?.activityName || requestData.title;
          const activityType = payload.metadata?.activityType || 'N/A';

          // Add activity info to requestData for the email template
          const requestDataWithActivity = {
            ...requestData,
            activityName: activityName,
            activityType: activityType
          };

          // Get next approver if available
          const nextApproverId = payload.metadata?.nextApproverId;
          const nextApprover = nextApproverId ? await User.findByPk(nextApproverId) : null;

          // Check if next approver is the recipient (initiator)
          const isNextApproverInitiator = proposalData.nextApproverIsInitiator ||
            (nextApprover && nextApprover.userId === userId);

          await emailNotificationService.sendDealerProposalSubmitted(
            requestDataWithActivity,
            dealerData,
            user.toJSON(),
            {
              ...proposalData,
              nextApproverIsInitiator: isNextApproverInitiator
            },
            nextApprover && !isNextApproverInitiator ? nextApprover.toJSON() : undefined
          );
        }
        break;

      case 'activity_created':
        {
          // Get activity data from metadata (should be provided by processActivityCreation)
          const activityData = payload.metadata?.activityData || {
            activityName: requestData.title,
            activityType: 'N/A',
            activityDate: payload.metadata?.activityDate,
            location: payload.metadata?.location || 'Not specified',
            dealerName: payload.metadata?.dealerName || 'Dealer',
            dealerCode: payload.metadata?.dealerCode,
            initiatorName: initiatorData.displayName || initiatorData.email,
            departmentLeadName: payload.metadata?.departmentLeadName,
            ioNumber: payload.metadata?.ioNumber,
            nextSteps: payload.metadata?.nextSteps || 'IO confirmation to be made. Dealer will proceed with activity execution and submit completion documents.'
          };

          await emailNotificationService.sendActivityCreated(
            requestData,
            user.toJSON(),
            activityData
          );
        }
        break;

      case 'completion_submitted':
        {
          // Get dealer and completion data from metadata
          const dealerData = payload.metadata?.dealerData || { userId: null, email: payload.metadata?.dealerEmail, displayName: payload.metadata?.dealerName };
          const completionData = payload.metadata?.completionData || {};

          // Get next approver if available
          const nextApproverId = payload.metadata?.nextApproverId;
          const nextApprover = nextApproverId ? await User.findByPk(nextApproverId) : null;

          // Check if next approver is the recipient (initiator)
          const isNextApproverInitiator = completionData.nextApproverIsInitiator ||
            (nextApprover && nextApprover.userId === userId);

          await emailNotificationService.sendCompletionDocumentsSubmitted(
            requestData,
            dealerData,
            user.toJSON(),
            {
              ...completionData,
              nextApproverIsInitiator: isNextApproverInitiator
            },
            nextApprover && !isNextApproverInitiator ? nextApprover.toJSON() : undefined
          );
        }
        break;

      case 'einvoice_generated':
        {
          // Get invoice data from metadata
          const invoiceData = payload.metadata?.invoiceData || {
            invoiceNumber: payload.metadata?.invoiceNumber || payload.metadata?.eInvoiceNumber,
            invoiceDate: payload.metadata?.invoiceDate,
            dmsNumber: payload.metadata?.dmsNumber,
            amount: payload.metadata?.amount || payload.metadata?.invoiceAmount,
            dealerName: payload.metadata?.dealerName,
            dealerCode: payload.metadata?.dealerCode,
            ioNumber: payload.metadata?.ioNumber,
            generatedAt: payload.metadata?.generatedAt,
            downloadLink: payload.metadata?.downloadLink
          };

          await emailNotificationService.sendEInvoiceGenerated(
            requestData,
            user.toJSON(),
            invoiceData
          );
        }
        break;

      case 'credit_note_sent':
        {
          // Get credit note data from metadata
          const creditNoteData = payload.metadata?.creditNoteData || {
            creditNoteNumber: payload.metadata?.creditNoteNumber,
            creditNoteDate: payload.metadata?.creditNoteDate,
            creditNoteAmount: payload.metadata?.creditNoteAmount,
            dealerName: payload.metadata?.dealerName,
            dealerCode: payload.metadata?.dealerCode,
            dealerEmail: payload.metadata?.dealerEmail,
            reason: payload.metadata?.reason,
            invoiceNumber: payload.metadata?.invoiceNumber,
            sentAt: payload.metadata?.sentAt,
            downloadLink: payload.metadata?.downloadLink
          };

          await emailNotificationService.sendCreditNoteSent(
            requestData,
            user.toJSON(),
            creditNoteData
          );
        }
        break;

      case 're_quotation':
        {
          const dealerData = payload.metadata?.dealerData || { userId: null, email: payload.metadata?.dealerEmail, displayName: payload.metadata?.dealerName };
          const rejectionReason = payload.body || payload.metadata?.rejectionReason || 'Revised Quotation Requested';

          // Get claim info if available
          const { DealerClaimDetails } = await import('@models/index');
          const claim = await DealerClaimDetails.findOne({ where: { requestId: payload.requestId } });

          await emailNotificationService.sendReQuotationRequired(
            requestData,
            dealerData,
            initiatorData,
            claim ? claim.toJSON() : {},
            rejectionReason
          );
        }
        break;

      case 'participant_added':
        {
          // Get the participant user
          const participantUser = await User.findByPk(userId);

          if (!participantUser) {
            logger.warn(`[Email] Participant user ${userId} not found`);
            return;
          }

          // Get the user who added the participant
          const addedByUserId = payload.metadata?.addedBy;
          const addedByUser = addedByUserId ? await User.findByPk(addedByUserId) : null;

          await emailNotificationService.sendParticipantAdded(
            requestData,
            participantUser.toJSON(),
            addedByUser ? addedByUser.toJSON() : undefined,
            initiatorData
          );
        }
        break;

      default:
        logger.info(`[Email] No email configured for notification type: ${notificationType}`);
    }
  }
}

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

