/**
 * NotificationService - Core notification management service
 * Handles multi-channel notification dispatch and management
 */

import {
  Notification,
  NotificationPayload,
  NotificationChannel,
  NotificationPriority,
  NotificationRecipient,
  NotificationStatus,
  NotificationType,
  UserNotificationPreferences,
  InAppNotification,
  NotificationStats,
  NotificationConfig
} from './types';

import { EmailChannel } from './channels/email';
import { PushChannel } from './channels/push';
import { InAppChannel } from './channels/inApp';

export class NotificationService {
  private static instance: NotificationService;
  private notifications: Map<string, Notification> = new Map();
  private userPreferences: Map<string, UserNotificationPreferences> = new Map();
  private stats: NotificationStats;
  private config: NotificationConfig;

  private emailChannel: EmailChannel;
  private pushChannel: PushChannel;
  private inAppChannel: InAppChannel;

  private constructor() {
    this.config = this.loadConfig();
    this.stats = this.initializeStats();

    this.emailChannel = new EmailChannel(this.config.email);
    this.pushChannel = new PushChannel(this.config.push);
    this.inAppChannel = new InAppChannel();
  }

  static getInstance(): NotificationService {
    if (!NotificationService.instance) {
      NotificationService.instance = new NotificationService();
    }
    return NotificationService.instance;
  }

  /**
   * Send a notification to a recipient
   */
  async send(
    recipient: NotificationRecipient,
    payload: NotificationPayload,
    options?: {
      channels?: NotificationChannel[];
      priority?: NotificationPriority;
    }
  ): Promise<Notification> {
    const notification = this.createNotification(recipient, payload, options);

    // Store notification
    this.notifications.set(notification.id, notification);

    // Check user preferences and quiet hours
    const preferences = this.getUserPreferences(recipient.userId);
    const channels = this.filterChannelsByPreferences(
      notification.channels,
      preferences,
      payload.type
    );

    if (channels.length === 0) {
      notification.status = 'delivered';
      notification.deliveredAt = new Date().toISOString();
      return notification;
    }

    // Check quiet hours
    if (this.isInQuietHours(preferences)) {
      // Queue for later if not urgent
      if (notification.priority !== 'urgent') {
        console.log(`[NotificationService] Notification ${notification.id} queued for after quiet hours`);
        return notification;
      }
    }

    // Send through each channel
    await this.dispatchNotification(notification, recipient, channels);

    return notification;
  }

  /**
   * Send notification to multiple recipients
   */
  async sendBulk(
    recipients: NotificationRecipient[],
    payload: NotificationPayload,
    options?: {
      channels?: NotificationChannel[];
      priority?: NotificationPriority;
    }
  ): Promise<Notification[]> {
    const notifications: Notification[] = [];

    // Process in batches
    const batchSize = this.config.defaults.batchSize;
    for (let i = 0; i < recipients.length; i += batchSize) {
      const batch = recipients.slice(i, i + batchSize);
      const batchPromises = batch.map(recipient =>
        this.send(recipient, payload, options)
      );
      const batchResults = await Promise.allSettled(batchPromises);

      batchResults.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          notifications.push(result.value);
        } else {
          console.error(`[NotificationService] Failed to send to ${batch[index].userId}:`, result.reason);
        }
      });
    }

    return notifications;
  }

  /**
   * Get notifications for a user
   */
  getUserNotifications(userId: string, options?: {
    unreadOnly?: boolean;
    type?: NotificationType;
    limit?: number;
    offset?: number;
  }): InAppNotification[] {
    return this.inAppChannel.getNotifications(userId, options);
  }

  /**
   * Mark notification as read
   */
  markAsRead(notificationId: string, userId: string): boolean {
    const notification = this.notifications.get(notificationId);
    if (notification && notification.recipientId === userId) {
      notification.readAt = new Date().toISOString();
      notification.status = 'read';
      this.inAppChannel.markAsRead(notificationId);
      return true;
    }
    return false;
  }

  /**
   * Mark all notifications as read for a user
   */
  markAllAsRead(userId: string): number {
    return this.inAppChannel.markAllAsRead(userId);
  }

  /**
   * Get unread count for a user
   */
  getUnreadCount(userId: string): number {
    return this.inAppChannel.getUnreadCount(userId);
  }

  /**
   * Update user notification preferences
   */
  updatePreferences(userId: string, preferences: Partial<UserNotificationPreferences>): UserNotificationPreferences {
    const current = this.getUserPreferences(userId);
    const updated: UserNotificationPreferences = {
      ...current,
      ...preferences,
      userId
    };
    this.userPreferences.set(userId, updated);
    return updated;
  }

  /**
   * Get user notification preferences
   */
  getUserPreferences(userId: string): UserNotificationPreferences {
    return this.userPreferences.get(userId) || this.getDefaultPreferences(userId);
  }

  /**
   * Get notification statistics
   */
  getStats(): NotificationStats {
    return { ...this.stats };
  }

  /**
   * Get a specific notification
   */
  getNotification(id: string): Notification | undefined {
    return this.notifications.get(id);
  }

  /**
   * Retry failed notification
   */
  async retry(notificationId: string): Promise<Notification | null> {
    const notification = this.notifications.get(notificationId);
    if (!notification || notification.status !== 'failed') {
      return null;
    }

    if (notification.retryCount >= this.config.defaults.retryAttempts) {
      console.log(`[NotificationService] Max retries reached for ${notificationId}`);
      return notification;
    }

    notification.retryCount++;
    notification.status = 'pending';
    notification.error = undefined;

    // Re-dispatch
    const recipient: NotificationRecipient = {
      userId: notification.recipientId
    };

    await this.dispatchNotification(notification, recipient, notification.channels);

    return notification;
  }

  /**
   * Delete notification
   */
  delete(notificationId: string, userId: string): boolean {
    const notification = this.notifications.get(notificationId);
    if (notification && notification.recipientId === userId) {
      this.notifications.delete(notificationId);
      this.inAppChannel.delete(notificationId);
      return true;
    }
    return false;
  }

  /**
   * Create notification object
   */
  private createNotification(
    recipient: NotificationRecipient,
    payload: NotificationPayload,
    options?: {
      channels?: NotificationChannel[];
      priority?: NotificationPriority;
    }
  ): Notification {
    const id = `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

    return {
      id,
      recipientId: recipient.userId,
      payload,
      channels: options?.channels || ['inApp', 'email'],
      priority: options?.priority || 'medium',
      status: 'pending',
      createdAt: new Date().toISOString(),
      retryCount: 0
    };
  }

  /**
   * Dispatch notification through channels
   */
  private async dispatchNotification(
    notification: Notification,
    recipient: NotificationRecipient,
    channels: NotificationChannel[]
  ): Promise<void> {
    const results: { channel: NotificationChannel; success: boolean; error?: string }[] = [];

    for (const channel of channels) {
      try {
        switch (channel) {
          case 'email':
            if (recipient.email) {
              await this.emailChannel.send({
                to: recipient.email,
                subject: notification.payload.title,
                html: await this.emailChannel.renderTemplate(notification.payload),
                text: notification.payload.message
              });
              results.push({ channel, success: true });
              this.stats.byChannel.email.sent++;
              this.stats.byChannel.email.delivered++;
            }
            break;

          case 'push':
            if (recipient.pushToken) {
              await this.pushChannel.send({
                token: recipient.pushToken,
                title: notification.payload.title,
                body: notification.payload.message,
                data: notification.payload.data
              });
              results.push({ channel, success: true });
              this.stats.byChannel.push.sent++;
              this.stats.byChannel.push.delivered++;
            }
            break;

          case 'inApp':
            await this.inAppChannel.send({
              id: notification.id,
              userId: recipient.userId,
              type: notification.payload.type,
              title: notification.payload.title,
              message: notification.payload.message,
              actionUrl: notification.payload.actionUrl,
              imageUrl: notification.payload.imageUrl,
              read: false,
              createdAt: notification.createdAt
            });
            results.push({ channel, success: true });
            this.stats.byChannel.inApp.sent++;
            this.stats.byChannel.inApp.delivered++;
            break;
        }
      } catch (error: any) {
        console.error(`[NotificationService] Failed to send via ${channel}:`, error.message);
        results.push({ channel, success: false, error: error.message });
        this.stats.byChannel[channel].failed++;
      }
    }

    // Update notification status
    const allSuccessful = results.every(r => r.success);
    const anySuccessful = results.some(r => r.success);

    if (allSuccessful) {
      notification.status = 'delivered';
      notification.deliveredAt = new Date().toISOString();
      this.stats.totalDelivered++;
    } else if (anySuccessful) {
      notification.status = 'delivered';
      notification.deliveredAt = new Date().toISOString();
      notification.error = results.filter(r => !r.success).map(r => `${r.channel}: ${r.error}`).join('; ');
      this.stats.totalDelivered++;
    } else {
      notification.status = 'failed';
      notification.error = results.map(r => `${r.channel}: ${r.error}`).join('; ');
      this.stats.totalFailed++;
    }

    notification.sentAt = new Date().toISOString();
    this.stats.totalSent++;
    this.stats.byType[notification.payload.type] = (this.stats.byType[notification.payload.type] || 0) + 1;
  }

  /**
   * Filter channels by user preferences
   */
  private filterChannelsByPreferences(
    channels: NotificationChannel[],
    preferences: UserNotificationPreferences,
    type: NotificationType
  ): NotificationChannel[] {
    return channels.filter(channel => {
      // Check channel preference
      if (channel === 'email' && !preferences.email) return false;
      if (channel === 'push' && !preferences.push) return false;
      if (channel === 'inApp' && !preferences.inApp) return false;

      // Check type-specific preference
      switch (type) {
        case 'plant_reminder':
          return preferences.plantReminders;
        case 'transport_alert':
          return preferences.transportAlerts;
        case 'farm_alert':
          return preferences.farmAlerts;
        case 'harvest_ready':
          return preferences.harvestAlerts;
        case 'demand_match':
          return preferences.demandMatches;
        case 'weekly_digest':
          return preferences.weeklyDigest;
        default:
          return true;
      }
    });
  }

  /**
   * Check if current time is within quiet hours
   */
  private isInQuietHours(preferences: UserNotificationPreferences): boolean {
    if (!preferences.quietHoursStart || !preferences.quietHoursEnd) {
      return false;
    }

    const now = new Date();
    const timezone = preferences.timezone || 'UTC';

    try {
      const formatter = new Intl.DateTimeFormat('en-US', {
        timeZone: timezone,
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      });

      const currentTime = formatter.format(now);
      const [startHour, startMin] = preferences.quietHoursStart.split(':').map(Number);
      const [endHour, endMin] = preferences.quietHoursEnd.split(':').map(Number);
      const [currentHour, currentMin] = currentTime.split(':').map(Number);

      const currentMinutes = currentHour * 60 + currentMin;
      const startMinutes = startHour * 60 + startMin;
      const endMinutes = endHour * 60 + endMin;

      if (startMinutes <= endMinutes) {
        return currentMinutes >= startMinutes && currentMinutes < endMinutes;
      } else {
        // Quiet hours span midnight
        return currentMinutes >= startMinutes || currentMinutes < endMinutes;
      }
    } catch {
      return false;
    }
  }

  /**
   * Get default preferences
   */
  private getDefaultPreferences(userId: string): UserNotificationPreferences {
    return {
      userId,
      email: true,
      push: true,
      inApp: true,
      plantReminders: true,
      transportAlerts: true,
      farmAlerts: true,
      harvestAlerts: true,
      demandMatches: true,
      weeklyDigest: true
    };
  }

  /**
   * Initialize stats
   */
  private initializeStats(): NotificationStats {
    return {
      totalSent: 0,
      totalDelivered: 0,
      totalFailed: 0,
      byChannel: {
        email: { sent: 0, delivered: 0, failed: 0 },
        push: { sent: 0, delivered: 0, failed: 0 },
        inApp: { sent: 0, delivered: 0, failed: 0 }
      },
      byType: {} as Record<NotificationType, number>
    };
  }

  /**
   * Load configuration
   */
  private loadConfig(): NotificationConfig {
    return {
      email: {
        provider: (process.env.EMAIL_PROVIDER as 'sendgrid' | 'nodemailer' | 'smtp') || 'nodemailer',
        apiKey: process.env.SENDGRID_API_KEY,
        from: process.env.EMAIL_FROM || 'noreply@localgreenchain.local',
        replyTo: process.env.EMAIL_REPLY_TO,
        smtp: {
          host: process.env.SMTP_HOST || 'localhost',
          port: parseInt(process.env.SMTP_PORT || '587'),
          secure: process.env.SMTP_SECURE === 'true',
          user: process.env.SMTP_USER || '',
          pass: process.env.SMTP_PASS || ''
        }
      },
      push: {
        vapidPublicKey: process.env.VAPID_PUBLIC_KEY || '',
        vapidPrivateKey: process.env.VAPID_PRIVATE_KEY || '',
        vapidSubject: process.env.VAPID_SUBJECT || 'mailto:admin@localgreenchain.local'
      },
      defaults: {
        retryAttempts: 3,
        retryDelayMs: 5000,
        batchSize: 50
      }
    };
  }
}

// Export singleton getter
export function getNotificationService(): NotificationService {
  return NotificationService.getInstance();
}
