/**
 * Notification Emails Service
 *
 * Central service for sending notification emails based on user preferences.
 * Checks user preferences before sending any notification email.
 *
 * @module lib/services/notification-emails.service
 */

import { prisma } from '@/lib/db';
import { sendEmail } from '@/lib/email';
import { logger } from '@/lib/logger';
import { notificationTemplatesService } from './notification-templates.service';
import type {
  NotificationType,
  NotificationResult,
  EmailNotificationPreferences,
  NotificationLanguage,
  PreferencesConfirmationData,
  BookingNotificationData,
  ReminderNotificationData,
  PaymentReceiptData,
  SecurityAlertData,
  WeeklyReportData,
  PreferenceChange,
} from '@/lib/types/notification.types';

/**
 * Default notification preferences
 */
const DEFAULT_PREFERENCES: EmailNotificationPreferences = {
  bookings: true,
  reminders: true,
  payments: true,
  marketing: false,
  security: true,
  weeklyReport: false,
};

/**
 * User context data retrieved from database
 */
interface UserContext {
  email: string;
  name?: string;
  language: NotificationLanguage;
  timezone: string;
}

/**
 * Template creator function type
 */
type TemplateCreator<T> = (data: T & UserContext) => {
  subject: string;
  html: string;
  text?: string;
};

/**
 * Notification type configuration
 */
interface NotificationTypeConfig<T = any> {
  type: NotificationType;
  templateCreator: (data: T & UserContext) => ReturnType<TemplateCreator<T>>;
  logMessage: string;
  checkPreferences: boolean;
  logDataExtractor?: (data: T) => Record<string, any>;
}

/**
 * Notification type registry
 * Maps each notification type to its configuration for extensibility
 */
const NOTIFICATION_REGISTRY: Record<string, NotificationTypeConfig> = {
  bookings: {
    type: 'bookings',
    templateCreator: (data: BookingNotificationData & UserContext) =>
      notificationTemplatesService.createBookingNotificationEmail(data),
    logMessage: 'Booking notification',
    checkPreferences: true,
    logDataExtractor: (data: BookingNotificationData) => ({ bookingId: data.bookingId }),
  },
  reminders: {
    type: 'reminders',
    templateCreator: (data: ReminderNotificationData & UserContext) =>
      notificationTemplatesService.createReminderEmail(data),
    logMessage: 'Reminder notification',
    checkPreferences: true,
    logDataExtractor: (data: ReminderNotificationData) => ({
      bookingId: data.bookingId,
      hoursUntil: data.hoursUntil,
    }),
  },
  payments: {
    type: 'payments',
    templateCreator: (data: PaymentReceiptData & UserContext) =>
      notificationTemplatesService.createPaymentReceiptEmail(data),
    logMessage: 'Payment receipt',
    checkPreferences: true,
    logDataExtractor: (data: PaymentReceiptData) => ({ paymentId: data.paymentId }),
  },
  security: {
    type: 'security',
    templateCreator: (data: SecurityAlertData & UserContext) =>
      notificationTemplatesService.createSecurityAlertEmail(data),
    logMessage: 'Security alert',
    checkPreferences: true,
    logDataExtractor: (data: SecurityAlertData) => ({ alertType: data.alertType }),
  },
  weeklyReport: {
    type: 'weeklyReport',
    templateCreator: (data: WeeklyReportData & UserContext) =>
      notificationTemplatesService.createWeeklyReportEmail(data),
    logMessage: 'Weekly report',
    checkPreferences: true,
    logDataExtractor: (data: WeeklyReportData) => ({
      period: `${data.periodStart.toISOString()} - ${data.periodEnd.toISOString()}`,
    }),
  },
};

/**
 * Notification Emails Service Class
 */
class NotificationEmailsService {
  /**
   * Get user preferences from database
   *
   * @param userId - User ID
   * @returns User's email notification preferences and language
   */
  async getUserPreferences(userId: string): Promise<{
    preferences: EmailNotificationPreferences;
    language: NotificationLanguage;
    timezone: string;
    email: string;
    name: string | null;
  } | null> {
    try {
      const user = await prisma.users.findUnique({
        where: { id: userId },
        include: { user_preferences: true },
      });

      if (!user) {
        logger.warn('[NotificationEmails] User not found', { userId });
        return null;
      }

      const prefs = user.user_preferences;
      const emailNotifications = prefs?.emailNotifications as EmailNotificationPreferences | undefined;

      return {
        preferences: emailNotifications || DEFAULT_PREFERENCES,
        language: (prefs?.language as NotificationLanguage) || 'ar',
        timezone: prefs?.timezone || 'Asia/Qatar',
        email: user.email,
        name: user.name,
      };
    } catch (error) {
      logger.error('[NotificationEmails] Failed to get user preferences', { userId, error });
      return null;
    }
  }

  /**
   * Check if user has enabled a specific notification type
   *
   * @param userId - User ID
   * @param notificationType - Type of notification to check
   * @returns Whether the notification is enabled
   */
  async isNotificationEnabled(
    userId: string,
    notificationType: NotificationType
  ): Promise<boolean> {
    const userData = await this.getUserPreferences(userId);

    if (!userData) {
      // Default to sending security alerts only if user not found
      return notificationType === 'security';
    }

    return userData.preferences[notificationType] ?? DEFAULT_PREFERENCES[notificationType];
  }

  /**
   * Extract user context from user preferences data
   *
   * @private
   * @param userData - User preferences data from database
   * @returns User context for email templates
   */
  private extractUserContext(userData: {
    email: string;
    name: string | null;
    language: NotificationLanguage;
    timezone: string;
  }): UserContext {
    return {
      email: userData.email,
      name: userData.name || undefined,
      language: userData.language,
      timezone: userData.timezone,
    };
  }

  /**
   * Send an email and log the result
   *
   * @private
   * @param email - Recipient email address
   * @param template - Email template with subject and body
   * @returns Whether the email was sent successfully
   */
  private async sendEmailWithLogging(
    email: string,
    template: { subject: string; html: string; text?: string }
  ): Promise<boolean> {
    return await sendEmail(email, template);
  }

  /**
   * Generic method to send a typed notification email
   *
   * @private
   * @param config - Notification type configuration
   * @param data - Notification data (without user context)
   * @param userId - User ID to send notification to
   * @returns Result of the notification attempt
   */
  private async sendTypedNotification<T extends { userId: string }>(
    config: NotificationTypeConfig<T>,
    data: Omit<T, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    try {
      const typedData = data as T;
      const userId = typedData.userId;

      // Check user preferences if required
      if (config.checkPreferences) {
        const isEnabled = await this.isNotificationEnabled(userId, config.type);

        if (!isEnabled) {
          logger.info(`[NotificationEmails] ${config.logMessage} skipped (disabled)`, {
            userId,
          });
          return { success: true, skipped: true, skipReason: 'Notification disabled by user' };
        }
      }

      // Get user data
      const userData = await this.getUserPreferences(userId);

      if (!userData) {
        return { success: false, error: 'User not found' };
      }

      // Create user context and merge with notification data
      const userContext = this.extractUserContext(userData);
      const fullData = { ...data, ...userContext } as T & UserContext;

      // Generate email template
      const template = config.templateCreator(fullData);

      // Send email
      const sent = await this.sendEmailWithLogging(userData.email, template);

      if (sent) {
        const logData: Record<string, any> = { userId };
        if (config.logDataExtractor) {
          Object.assign(logData, config.logDataExtractor(typedData));
        }

        logger.info(`[NotificationEmails] ${config.logMessage} sent`, logData);
        return { success: true };
      } else {
        return { success: false, error: 'Failed to send email' };
      }
    } catch (error) {
      logger.error(`[NotificationEmails] Failed to send ${config.logMessage}`, { error });
      return { success: false, error: 'Internal error' };
    }
  }

  /**
   * Send preferences confirmation email
   *
   * Note: Preferences confirmation emails are always sent and not subject to user preferences.
   *
   * @param data - Preferences confirmation data including userId, changes, and timestamp
   * @returns Result of the notification attempt
   */
  async sendPreferencesConfirmationEmail(
    data: Omit<PreferencesConfirmationData, 'language' | 'email' | 'name' | 'timezone'> & {
      userId: string;
    }
  ): Promise<NotificationResult> {
    try {
      const userData = await this.getUserPreferences(data.userId);

      if (!userData) {
        return { success: false, error: 'User not found' };
      }

      // Preferences confirmation emails always get sent (not subject to preferences)
      const userContext = this.extractUserContext(userData);
      const template = notificationTemplatesService.createPreferencesConfirmationEmail({
        ...data,
        ...userContext,
      });

      const sent = await this.sendEmailWithLogging(userData.email, template);

      if (sent) {
        logger.info('[NotificationEmails] Preferences confirmation sent', {
          userId: data.userId,
          email: userData.email,
        });
        return { success: true };
      } else {
        return { success: false, error: 'Failed to send email' };
      }
    } catch (error) {
      logger.error('[NotificationEmails] Failed to send preferences confirmation', { error });
      return { success: false, error: 'Internal error' };
    }
  }

  /**
   * Send new booking notification email
   *
   * Checks user preferences before sending. Will be skipped if user has disabled booking notifications.
   *
   * @param data - Booking notification data including userId, bookingId, and appointment details
   * @returns Result of the notification attempt
   */
  async sendBookingNotificationEmail(
    data: Omit<BookingNotificationData, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    return this.sendTypedNotification<BookingNotificationData>(
      NOTIFICATION_REGISTRY.bookings,
      data
    );
  }

  /**
   * Send appointment reminder email
   *
   * Checks user preferences before sending. Will be skipped if user has disabled reminder notifications.
   *
   * @param data - Reminder notification data including userId, bookingId, and hours until appointment
   * @returns Result of the notification attempt
   */
  async sendReminderEmail(
    data: Omit<ReminderNotificationData, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    return this.sendTypedNotification<ReminderNotificationData>(
      NOTIFICATION_REGISTRY.reminders,
      data
    );
  }

  /**
   * Send payment receipt email
   *
   * Checks user preferences before sending. Will be skipped if user has disabled payment notifications.
   *
   * @param data - Payment receipt data including userId, paymentId, and transaction details
   * @returns Result of the notification attempt
   */
  async sendPaymentReceiptEmail(
    data: Omit<PaymentReceiptData, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    return this.sendTypedNotification<PaymentReceiptData>(
      NOTIFICATION_REGISTRY.payments,
      data
    );
  }

  /**
   * Send security alert email
   *
   * Checks user preferences before sending. Security alerts are critical and will be sent
   * even if preferences are not found (fail-safe behavior). Only skipped if explicitly disabled.
   *
   * @param data - Security alert data including userId, alertType, and alert details
   * @returns Result of the notification attempt
   */
  async sendSecurityAlertEmail(
    data: Omit<SecurityAlertData, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    return this.sendTypedNotification<SecurityAlertData>(
      NOTIFICATION_REGISTRY.security,
      data
    );
  }

  /**
   * Send weekly report email
   *
   * Checks user preferences before sending. Will be skipped if user has disabled weekly report notifications.
   *
   * @param data - Weekly report data including userId, period dates, and activity summary
   * @returns Result of the notification attempt
   */
  async sendWeeklyReportEmail(
    data: Omit<WeeklyReportData, 'language' | 'email' | 'name' | 'timezone'>
  ): Promise<NotificationResult> {
    return this.sendTypedNotification<WeeklyReportData>(
      NOTIFICATION_REGISTRY.weeklyReport,
      data
    );
  }

  /**
   * Get all users who have a specific notification enabled
   *
   * Useful for bulk sending operations such as weekly reports or promotional emails.
   * Only returns active, non-suspended users with the specified notification type enabled.
   *
   * @param notificationType - Type of notification to filter by
   * @returns Array of user IDs with the notification enabled
   *
   * @example
   * const userIds = await notificationEmailsService.getUsersWithNotificationEnabled('weeklyReport');
   * for (const userId of userIds) {
   *   await notificationEmailsService.sendWeeklyReportEmail({ userId, ...reportData });
   * }
   */
  async getUsersWithNotificationEnabled(notificationType: NotificationType): Promise<string[]> {
    try {
      const users = await prisma.users.findMany({
        where: {
          isActive: true,
          isSuspended: false,
        },
        select: {
          id: true,
          user_preferences: {
            select: {
              emailNotifications: true,
            },
          },
        },
      });

      const enabledUserIds = users.filter((user) => {
        const prefs = user.user_preferences?.emailNotifications as EmailNotificationPreferences | undefined;
        const defaultEnabled = DEFAULT_PREFERENCES[notificationType];

        if (!prefs) {
          return defaultEnabled;
        }

        return prefs[notificationType] ?? defaultEnabled;
      }).map((user) => user.id);

      return enabledUserIds;
    } catch (error) {
      logger.error('[NotificationEmails] Failed to get users with notification enabled', { error });
      return [];
    }
  }

  /**
   * Calculate changes between old and new preferences
   *
   * Helper method to determine which preference settings have changed.
   * Used for generating preference confirmation emails.
   *
   * @param oldPreferences - Previous preference values (can be partial)
   * @param newPreferences - New preference values
   * @returns Array of preference changes with setting name, previous value, and new value
   *
   * @example
   * const changes = notificationEmailsService.calculatePreferenceChanges(
   *   { bookings: true, language: 'ar' },
   *   { bookings: false, reminders: true, payments: true, language: 'en' }
   * );
   * // Returns: [{ setting: 'bookings', previousValue: true, newValue: false }, ...]
   */
  calculatePreferenceChanges(
    oldPreferences: Partial<EmailNotificationPreferences> & { language?: string; timezone?: string },
    newPreferences: EmailNotificationPreferences & { language?: string; timezone?: string }
  ): PreferenceChange[] {
    const changes: PreferenceChange[] = [];
    const allKeys = [
      'bookings', 'reminders', 'payments', 'marketing', 'security', 'weeklyReport',
      'language', 'timezone'
    ] as const;

    for (const key of allKeys) {
      const oldValue = oldPreferences[key as keyof typeof oldPreferences];
      const newValue = newPreferences[key as keyof typeof newPreferences];

      if (oldValue !== undefined && oldValue !== newValue) {
        changes.push({
          setting: key as PreferenceChange['setting'],
          previousValue: oldValue as boolean | string,
          newValue: newValue as boolean | string,
        });
      }
    }

    return changes;
  }
}

/**
 * Singleton instance
 */
export const notificationEmailsService = new NotificationEmailsService();
