import { EventEmitter } from 'events';

export interface ScheduledNotification {
  id: string;
  campaignId: string;
  title: string;
  message: string;
  scheduledFor: Date;
  timezone?: string;
  recurring?: {
    pattern: 'daily' | 'weekly' | 'monthly';
    endDate?: Date;
    daysOfWeek?: number[]; // 0-6, 0 = Sunday
  };
  status: 'scheduled' | 'sent' | 'cancelled' | 'failed';
  sentAt?: Date;
  failureReason?: string;
  createdAt: Date;
  updatedAt: Date;
}

export interface CalendarEvent {
  id: string;
  date: Date;
  notifications: ScheduledNotification[];
  totalScheduled: number;
}

export interface TimeSlot {
  hour: number;
  minute: number;
  available: boolean;
  conflictCount: number;
}

class NotificationSchedulingService extends EventEmitter {
  private scheduledNotifications: Map<string, ScheduledNotification> = new Map();
  private calendar: Map<string, CalendarEvent> = new Map();
  private maxNotificationsPerDay = 10;
  private maxNotificationsPerHour = 3;

  constructor() {
    super();
  }

  /**
   * Schedule a notification
   */
  scheduleNotification(
    campaignId: string,
    title: string,
    message: string,
    scheduledFor: Date,
    options?: {
      timezone?: string;
      recurring?: {
        pattern: 'daily' | 'weekly' | 'monthly';
        endDate?: Date;
        daysOfWeek?: number[];
      };
    }
  ): ScheduledNotification {
    const id = `scheduled-${Date.now()}`;

    // Check for conflicts
    const conflicts = this.checkSchedulingConflicts(scheduledFor);
    if (conflicts.length > 0) {
      this.emit('scheduling_conflict', { scheduledFor, conflicts });
    }

    const notification: ScheduledNotification = {
      id,
      campaignId,
      title,
      message,
      scheduledFor,
      timezone: options?.timezone,
      recurring: options?.recurring,
      status: 'scheduled',
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    this.scheduledNotifications.set(id, notification);
    this.updateCalendar(notification);
    this.emit('notification_scheduled', notification);

    return notification;
  }

  /**
   * Check for scheduling conflicts
   */
  checkSchedulingConflicts(scheduledFor: Date): ScheduledNotification[] {
    const dateKey = this.getDateKey(scheduledFor);
    const calendarEvent = this.calendar.get(dateKey);

    if (!calendarEvent) {
      return [];
    }

    const hour = scheduledFor.getHours();
    const notificationsInHour = calendarEvent.notifications.filter(
      (n) => n.scheduledFor.getHours() === hour && n.status === 'scheduled'
    );

    return notificationsInHour.length >= this.maxNotificationsPerHour
      ? notificationsInHour
      : [];
  }

  /**
   * Get available time slots for a date
   */
  getAvailableTimeSlots(date: Date): TimeSlot[] {
    const slots: TimeSlot[] = [];
    const dateKey = this.getDateKey(date);
    const calendarEvent = this.calendar.get(dateKey);

    for (let hour = 0; hour < 24; hour++) {
      for (let minute = 0; minute < 60; minute += 30) {
        const slotTime = new Date(date);
        slotTime.setHours(hour, minute, 0, 0);

        let conflictCount = 0;
        if (calendarEvent) {
          conflictCount = calendarEvent.notifications.filter(
            (n) => n.scheduledFor.getHours() === hour && n.status === 'scheduled'
          ).length;
        }

        const available = conflictCount < this.maxNotificationsPerHour;

        slots.push({
          hour,
          minute,
          available,
          conflictCount,
        });
      }
    }

    return slots;
  }

  /**
   * Reschedule notification
   */
  rescheduleNotification(notificationId: string, newScheduledFor: Date): ScheduledNotification | null {
    const notification = this.scheduledNotifications.get(notificationId);
    if (!notification) {
      return null;
    }

    if (notification.status !== 'scheduled') {
      throw new Error('Only scheduled notifications can be rescheduled');
    }

    notification.scheduledFor = newScheduledFor;
    notification.updatedAt = new Date();

    this.updateCalendar(notification);
    this.emit('notification_rescheduled', notification);

    return notification;
  }

  /**
   * Cancel scheduled notification
   */
  cancelNotification(notificationId: string): ScheduledNotification | null {
    const notification = this.scheduledNotifications.get(notificationId);
    if (!notification) {
      return null;
    }

    if (notification.status !== 'scheduled') {
      throw new Error('Only scheduled notifications can be cancelled');
    }

    notification.status = 'cancelled';
    notification.updatedAt = new Date();

    this.updateCalendar(notification);
    this.emit('notification_cancelled', notification);

    return notification;
  }

  /**
   * Mark notification as sent
   */
  markAsSent(notificationId: string): ScheduledNotification | null {
    const notification = this.scheduledNotifications.get(notificationId);
    if (!notification) {
      return null;
    }

    notification.status = 'sent';
    notification.sentAt = new Date();
    notification.updatedAt = new Date();

    this.updateCalendar(notification);
    this.emit('notification_sent', notification);

    return notification;
  }

  /**
   * Mark notification as failed
   */
  markAsFailed(notificationId: string, reason: string): ScheduledNotification | null {
    const notification = this.scheduledNotifications.get(notificationId);
    if (!notification) {
      return null;
    }

    notification.status = 'failed';
    notification.failureReason = reason;
    notification.updatedAt = new Date();

    this.updateCalendar(notification);
    this.emit('notification_failed', notification);

    return notification;
  }

  /**
   * Get calendar events for a date range
   */
  getCalendarEvents(startDate: Date, endDate: Date): CalendarEvent[] {
    const events: CalendarEvent[] = [];
    const currentDate = new Date(startDate);

    while (currentDate <= endDate) {
      const dateKey = this.getDateKey(currentDate);
      const event = this.calendar.get(dateKey);

      if (event) {
        events.push(event);
      }

      currentDate.setDate(currentDate.getDate() + 1);
    }

    return events;
  }

  /**
   * Get notifications for a specific date
   */
  getNotificationsForDate(date: Date): ScheduledNotification[] {
    const dateKey = this.getDateKey(date);
    const event = this.calendar.get(dateKey);
    return event ? event.notifications : [];
  }

  /**
   * Get upcoming notifications
   */
  getUpcomingNotifications(limit: number = 10): ScheduledNotification[] {
    const now = new Date();
    return Array.from(this.scheduledNotifications.values())
      .filter((n) => n.status === 'scheduled' && n.scheduledFor > now)
      .sort((a, b) => a.scheduledFor.getTime() - b.scheduledFor.getTime())
      .slice(0, limit);
  }

  /**
   * Get notifications by campaign
   */
  getNotificationsByCampaign(campaignId: string): ScheduledNotification[] {
    return Array.from(this.scheduledNotifications.values())
      .filter((n) => n.campaignId === campaignId)
      .sort((a, b) => b.scheduledFor.getTime() - a.scheduledFor.getTime());
  }

  /**
   * Get scheduling statistics
   */
  getSchedulingStatistics(startDate?: Date, endDate?: Date): {
    totalScheduled: number;
    totalSent: number;
    totalFailed: number;
    totalCancelled: number;
    averageNotificationsPerDay: number;
    peakHour: number;
  } {
    const start = startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
    const end = endDate || new Date();

    const notifications = Array.from(this.scheduledNotifications.values()).filter(
      (n) => n.createdAt >= start && n.createdAt <= end
    );

    const scheduled = notifications.filter((n) => n.status === 'scheduled').length;
    const sent = notifications.filter((n) => n.status === 'sent').length;
    const failed = notifications.filter((n) => n.status === 'failed').length;
    const cancelled = notifications.filter((n) => n.status === 'cancelled').length;

    // Calculate peak hour
    const hourCounts: Record<number, number> = {};
    for (const n of notifications) {
      const hour = n.scheduledFor.getHours();
      hourCounts[hour] = (hourCounts[hour] || 0) + 1;
    }

    const peakHour = Object.entries(hourCounts).reduce(
      (max, [hour, count]) => (count > (hourCounts[max] || 0) ? parseInt(hour) : max),
      0
    );

    const daysDiff = Math.ceil((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
    const avgPerDay = daysDiff > 0 ? notifications.length / daysDiff : 0;

    return {
      totalScheduled: scheduled,
      totalSent: sent,
      totalFailed: failed,
      totalCancelled: cancelled,
      averageNotificationsPerDay: avgPerDay,
      peakHour,
    };
  }

  /**
   * Get optimal send times
   */
  getOptimalSendTimes(): { hour: number; score: number }[] {
    const hourScores: Record<number, number> = {};

    for (const notification of this.scheduledNotifications.values()) {
      if (notification.status === 'sent') {
        const hour = notification.scheduledFor.getHours();
        hourScores[hour] = (hourScores[hour] || 0) + 1;
      }
    }

    return Object.entries(hourScores)
      .map(([hour, score]) => ({ hour: parseInt(hour), score }))
      .sort((a, b) => b.score - a.score)
      .slice(0, 5);
  }

  /**
   * Update calendar
   */
  private updateCalendar(notification: ScheduledNotification): void {
    const dateKey = this.getDateKey(notification.scheduledFor);
    let event = this.calendar.get(dateKey);

    if (!event) {
      event = {
        id: `event-${dateKey}`,
        date: notification.scheduledFor,
        notifications: [],
        totalScheduled: 0,
      };
      this.calendar.set(dateKey, event);
    }

    // Remove old reference if exists
    const existingIndex = event.notifications.findIndex((n) => n.id === notification.id);
    if (existingIndex !== -1) {
      event.notifications.splice(existingIndex, 1);
    }

    // Add updated notification
    event.notifications.push(notification);
    event.totalScheduled = event.notifications.filter((n) => n.status === 'scheduled').length;
  }

  /**
   * Get date key for calendar
   */
  private getDateKey(date: Date): string {
    return date.toISOString().split('T')[0];
  }
}

// Export singleton instance
export const notificationSchedulingService = new NotificationSchedulingService();

export default NotificationSchedulingService;
