import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan, MoreThan } from 'typeorm';
import {
  Activity,
  ActivityType,
} from '../../database/entities/activity.entity';
import { Child } from '../../database/entities/child.entity';
import {
  Notification,
  NotificationType,
  NotificationStatus,
  NotificationPriority,
} from '../../database/entities/notification.entity';
import { AuditService } from '../../common/services/audit.service';
import { EntityType } from '../../database/entities';

export interface NotificationSuggestion {
  type: 'feeding' | 'sleep' | 'diaper' | 'medication';
  childId: string;
  childName: string;
  message: string;
  urgency: 'low' | 'medium' | 'high';
  estimatedTime?: Date;
  reason: string;
}

interface ActivityPattern {
  averageInterval: number; // in milliseconds
  lastActivityTime: Date;
  activityCount: number;
}

@Injectable()
export class NotificationsService {
  private readonly logger = new Logger('NotificationsService');

  // Time thresholds for notifications (in hours)
  private readonly FEEDING_INTERVAL = 3; // 3 hours
  private readonly DIAPER_INTERVAL = 3; // 3 hours
  private readonly SLEEP_NAP_SUGGESTION = 2; // 2 hours awake

  constructor(
    @InjectRepository(Activity)
    private activityRepository: Repository<Activity>,
    @InjectRepository(Child)
    private childRepository: Repository<Child>,
    @InjectRepository(Notification)
    private notificationRepository: Repository<Notification>,
    private auditService: AuditService,
  ) {}

  /**
   * Get smart notification suggestions for a user
   */
  async getSmartNotifications(
    userId: string,
  ): Promise<NotificationSuggestion[]> {
    const suggestions: NotificationSuggestion[] = [];

    // Get user's children
    const children = await this.childRepository.find({
      where: { familyId: userId },
    });

    for (const child of children) {
      // Analyze patterns for each activity type
      const feedingSuggestion = await this.analyzeFeedingPattern(child);
      if (feedingSuggestion) suggestions.push(feedingSuggestion);

      const diaperSuggestion = await this.analyzeDiaperPattern(child);
      if (diaperSuggestion) suggestions.push(diaperSuggestion);

      const sleepSuggestion = await this.analyzeSleepPattern(child);
      if (sleepSuggestion) suggestions.push(sleepSuggestion);
    }

    // Sort by urgency
    return suggestions.sort((a, b) => {
      const urgencyOrder = { high: 3, medium: 2, low: 1 };
      return urgencyOrder[b.urgency] - urgencyOrder[a.urgency];
    });
  }

  /**
   * Analyze feeding patterns and suggest next feeding
   */
  private async analyzeFeedingPattern(
    child: Child,
  ): Promise<NotificationSuggestion | null> {
    const pattern = await this.getActivityPattern(
      child.id,
      ActivityType.FEEDING,
      7, // Look back 7 days
    );

    if (!pattern || pattern.activityCount < 3) {
      return null; // Not enough data
    }

    const timeSinceLastFeeding =
      Date.now() - pattern.lastActivityTime.getTime();
    const hoursElapsed = timeSinceLastFeeding / (1000 * 60 * 60);

    // If it's been longer than expected, suggest feeding
    const expectedIntervalHours = pattern.averageInterval / (1000 * 60 * 60);

    if (hoursElapsed >= expectedIntervalHours * 0.9) {
      const urgency =
        hoursElapsed >= expectedIntervalHours * 1.2
          ? 'high'
          : hoursElapsed >= expectedIntervalHours
            ? 'medium'
            : 'low';

      return {
        type: 'feeding',
        childId: child.id,
        childName: child.name,
        message: `${child.name} usually feeds every ${Math.round(expectedIntervalHours)} hours. Last feeding was ${Math.round(hoursElapsed)} hours ago.`,
        urgency,
        estimatedTime: new Date(
          pattern.lastActivityTime.getTime() + pattern.averageInterval,
        ),
        reason: `Based on ${pattern.activityCount} feeding activities over the past week`,
      };
    }

    return null;
  }

  /**
   * Analyze diaper change patterns
   */
  private async analyzeDiaperPattern(
    child: Child,
  ): Promise<NotificationSuggestion | null> {
    const pattern = await this.getActivityPattern(
      child.id,
      ActivityType.DIAPER,
      3, // Look back 3 days
    );

    if (!pattern || pattern.activityCount < 3) {
      return null;
    }

    const timeSinceLastChange = Date.now() - pattern.lastActivityTime.getTime();
    const hoursElapsed = timeSinceLastChange / (1000 * 60 * 60);

    if (hoursElapsed >= this.DIAPER_INTERVAL) {
      const urgency =
        hoursElapsed >= this.DIAPER_INTERVAL * 1.5 ? 'high' : 'medium';

      return {
        type: 'diaper',
        childId: child.id,
        childName: child.name,
        message: `Time to check ${child.name}'s diaper. Last change was ${Math.round(hoursElapsed)} hours ago.`,
        urgency,
        reason: `Recommended diaper check interval: ${this.DIAPER_INTERVAL} hours`,
      };
    }

    return null;
  }

  /**
   * Analyze sleep patterns and suggest nap time
   */
  private async analyzeSleepPattern(
    child: Child,
  ): Promise<NotificationSuggestion | null> {
    const pattern = await this.getActivityPattern(
      child.id,
      ActivityType.SLEEP,
      7, // Look back 7 days
    );

    if (!pattern || pattern.activityCount < 3) {
      return null;
    }

    const timeSinceLastSleep = Date.now() - pattern.lastActivityTime.getTime();
    const hoursAwake = timeSinceLastSleep / (1000 * 60 * 60);

    const expectedSleepIntervalHours =
      pattern.averageInterval / (1000 * 60 * 60);

    if (hoursAwake >= expectedSleepIntervalHours * 0.8) {
      const urgency =
        hoursAwake >= expectedSleepIntervalHours ? 'medium' : 'low';

      return {
        type: 'sleep',
        childId: child.id,
        childName: child.name,
        message: `${child.name} has been awake for ${Math.round(hoursAwake)} hours. Consider putting them down for a nap.`,
        urgency,
        estimatedTime: new Date(
          pattern.lastActivityTime.getTime() + pattern.averageInterval,
        ),
        reason: `Based on typical sleep pattern (every ${Math.round(expectedSleepIntervalHours)} hours)`,
      };
    }

    return null;
  }

  /**
   * Get activity pattern statistics
   */
  private async getActivityPattern(
    childId: string,
    activityType: ActivityType,
    lookbackDays: number,
  ): Promise<ActivityPattern | null> {
    const cutoffDate = new Date(
      Date.now() - lookbackDays * 24 * 60 * 60 * 1000,
    );

    const activities = await this.activityRepository.find({
      where: {
        childId,
        type: activityType,
        startedAt: MoreThan(cutoffDate),
      },
      order: {
        startedAt: 'DESC',
      },
    });

    if (activities.length < 2) {
      return null; // Not enough data
    }

    // Calculate average interval between activities
    let totalInterval = 0;
    for (let i = 0; i < activities.length - 1; i++) {
      const interval =
        activities[i].startedAt.getTime() -
        activities[i + 1].startedAt.getTime();
      totalInterval += interval;
    }

    const averageInterval = totalInterval / (activities.length - 1);

    return {
      averageInterval,
      lastActivityTime: activities[0].startedAt,
      activityCount: activities.length,
    };
  }

  /**
   * Get medication reminders
   */
  async getMedicationReminders(
    userId: string,
  ): Promise<NotificationSuggestion[]> {
    const children = await this.childRepository.find({
      where: { familyId: userId },
    });

    const reminders: NotificationSuggestion[] = [];

    for (const child of children) {
      // Get recent medication activities with schedules
      const medications = await this.activityRepository.find({
        where: {
          childId: child.id,
          type: ActivityType.MEDICATION,
        },
        order: {
          startedAt: 'DESC',
        },
        take: 10,
      });

      for (const medication of medications) {
        // Check if medication has a schedule in metadata
        const schedule = medication.metadata?.schedule;
        if (schedule) {
          const timeSinceLastDose = Date.now() - medication.startedAt.getTime();
          const hoursElapsed = timeSinceLastDose / (1000 * 60 * 60);

          if (hoursElapsed >= schedule.intervalHours) {
            reminders.push({
              type: 'medication',
              childId: child.id,
              childName: child.name,
              message: `Time for ${child.name}'s ${medication.metadata.name || 'medication'}`,
              urgency: 'high',
              estimatedTime: new Date(
                medication.startedAt.getTime() +
                  schedule.intervalHours * 60 * 60 * 1000,
              ),
              reason: `Scheduled every ${schedule.intervalHours} hours`,
            });
          }
        }
      }
    }

    return reminders;
  }

  /**
   * Create and persist a notification
   */
  async createNotification(
    userId: string,
    type: NotificationType,
    title: string,
    message: string,
    options?: {
      childId?: string;
      priority?: NotificationPriority;
      metadata?: any;
      scheduledFor?: Date;
      deviceToken?: string;
    },
  ): Promise<Notification> {
    const notification = this.notificationRepository.create({
      userId,
      childId: options?.childId || null,
      type,
      title,
      message,
      priority: options?.priority || NotificationPriority.MEDIUM,
      metadata: options?.metadata || null,
      scheduledFor: options?.scheduledFor || null,
      deviceToken: options?.deviceToken || null,
      status: NotificationStatus.PENDING,
    });

    const saved = await this.notificationRepository.save(notification);

    // Audit log
    await this.auditService.logCreate(
      EntityType.NOTIFICATION,
      saved.id,
      { type, title, priority: saved.priority },
      userId,
    );

    this.logger.debug(`Created notification ${saved.id} for user ${userId}`);

    return saved;
  }

  /**
   * Get all notifications for a user
   */
  async getUserNotifications(
    userId: string,
    options?: {
      status?: NotificationStatus;
      limit?: number;
      includeRead?: boolean;
    },
  ): Promise<Notification[]> {
    const query: any = { userId };

    if (options?.status) {
      query.status = options.status;
    } else if (!options?.includeRead) {
      // By default, exclude read and dismissed notifications
      query.status = NotificationStatus.PENDING;
    }

    return this.notificationRepository.find({
      where: query,
      order: { createdAt: 'DESC' },
      take: options?.limit || 50,
      relations: ['child'],
    });
  }

  /**
   * Mark notification as sent
   */
  async markAsSent(
    notificationId: string,
    deviceToken?: string,
  ): Promise<void> {
    await this.notificationRepository.update(notificationId, {
      status: NotificationStatus.SENT,
      sentAt: new Date(),
      deviceToken: deviceToken || undefined,
    });

    this.logger.debug(`Notification ${notificationId} marked as sent`);
  }

  /**
   * Mark notification as read
   */
  async markAsRead(notificationId: string, userId: string): Promise<void> {
    const notification = await this.notificationRepository.findOne({
      where: { id: notificationId, userId },
    });

    if (!notification) {
      throw new Error('Notification not found');
    }

    await this.notificationRepository.update(notificationId, {
      status: NotificationStatus.READ,
      readAt: new Date(),
    });

    // Audit log
    await this.auditService.logRead(
      EntityType.NOTIFICATION,
      notificationId,
      userId,
    );

    this.logger.debug(`Notification ${notificationId} marked as read`);
  }

  /**
   * Mark notification as dismissed
   */
  async dismiss(notificationId: string, userId: string): Promise<void> {
    const notification = await this.notificationRepository.findOne({
      where: { id: notificationId, userId },
    });

    if (!notification) {
      throw new Error('Notification not found');
    }

    await this.notificationRepository.update(notificationId, {
      status: NotificationStatus.DISMISSED,
      dismissedAt: new Date(),
    });

    this.logger.debug(`Notification ${notificationId} dismissed`);
  }

  /**
   * Mark notification as failed
   */
  async markAsFailed(
    notificationId: string,
    errorMessage: string,
  ): Promise<void> {
    await this.notificationRepository.update(notificationId, {
      status: NotificationStatus.FAILED,
      errorMessage,
    });

    this.logger.error(`Notification ${notificationId} failed: ${errorMessage}`);
  }

  /**
   * Detect milestone achievements based on child age
   */
  async detectMilestones(childId: string): Promise<NotificationSuggestion[]> {
    const child = await this.childRepository.findOne({
      where: { id: childId },
    });

    if (!child) {
      return [];
    }

    const ageInMonths = this.calculateAgeInMonths(child.birthDate);
    const milestones: NotificationSuggestion[] = [];

    // Define milestone checkpoints
    const milestoneMap = [
      {
        months: 2,
        message: 'First social smiles usually appear around 2 months',
      },
      {
        months: 4,
        message: 'Tummy time and head control milestones around 4 months',
      },
      {
        months: 6,
        message: 'Sitting up and solid foods typically start around 6 months',
      },
      {
        months: 9,
        message: 'Crawling and separation anxiety common around 9 months',
      },
      {
        months: 12,
        message: 'First steps and first words often happen around 12 months',
      },
      {
        months: 18,
        message: 'Increased vocabulary and pretend play around 18 months',
      },
      {
        months: 24,
        message: 'Two-word sentences and running around 24 months',
      },
      {
        months: 36,
        message: 'Potty training readiness and imaginative play around 3 years',
      },
    ];

    // Check if child is approaching or at a milestone age
    const currentMilestone = milestoneMap.find(
      (m) => ageInMonths >= m.months - 0.5 && ageInMonths <= m.months + 0.5,
    );

    if (currentMilestone) {
      milestones.push({
        type: 'sleep', // Reusing enum, would need 'milestone' type
        childId: child.id,
        childName: child.name,
        message: `${child.name} is around ${currentMilestone.months} months old! ${currentMilestone.message}`,
        urgency: 'low',
        reason: 'Developmental milestone tracking',
      });
    }

    return milestones;
  }

  /**
   * Detect pattern anomalies (e.g., unusual sleep/feeding patterns)
   */
  async detectAnomalies(childId: string): Promise<NotificationSuggestion[]> {
    const anomalies: NotificationSuggestion[] = [];
    const child = await this.childRepository.findOne({
      where: { id: childId },
    });

    if (!child) {
      return [];
    }

    // Check for feeding pattern anomalies
    const recentFeedings = await this.activityRepository.find({
      where: {
        childId,
        type: ActivityType.FEEDING,
        startedAt: MoreThan(new Date(Date.now() - 24 * 60 * 60 * 1000)),
      },
      order: { startedAt: 'DESC' },
    });

    const historicalFeedings = await this.activityRepository.find({
      where: {
        childId,
        type: ActivityType.FEEDING,
        startedAt: MoreThan(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)),
      },
    });

    // Calculate average daily feedings
    const avgDailyFeedings = historicalFeedings.length / 7;
    const todayFeedings = recentFeedings.length;

    // Alert if today's feedings are significantly lower than average
    if (todayFeedings < avgDailyFeedings * 0.5 && avgDailyFeedings > 3) {
      anomalies.push({
        type: 'feeding',
        childId: child.id,
        childName: child.name,
        message: `${child.name} has had fewer feedings than usual today (${todayFeedings} vs avg ${Math.round(avgDailyFeedings)})`,
        urgency: 'medium',
        reason: 'Pattern anomaly detection',
      });
    }

    // Check for sleep pattern anomalies
    const recentSleep = await this.activityRepository.find({
      where: {
        childId,
        type: ActivityType.SLEEP,
        startedAt: MoreThan(new Date(Date.now() - 24 * 60 * 60 * 1000)),
      },
      order: { startedAt: 'DESC' },
    });

    const totalSleepMinutes = recentSleep.reduce((total, sleep) => {
      if (sleep.endedAt) {
        const duration =
          (sleep.endedAt.getTime() - sleep.startedAt.getTime()) / (1000 * 60);
        return total + duration;
      }
      return total;
    }, 0);

    const totalSleepHours = totalSleepMinutes / 60;

    // Alert if sleep is significantly reduced (< 8 hours for infants)
    if (totalSleepHours < 8) {
      anomalies.push({
        type: 'sleep',
        childId: child.id,
        childName: child.name,
        message: `${child.name} has slept only ${Math.round(totalSleepHours)} hours in the last 24 hours`,
        urgency: 'medium',
        reason: 'Insufficient sleep detected',
      });
    }

    return anomalies;
  }

  /**
   * Schedule growth tracking reminders
   */
  async scheduleGrowthReminder(
    userId: string,
    childId: string,
  ): Promise<Notification> {
    const child = await this.childRepository.findOne({
      where: { id: childId },
    });

    if (!child) {
      throw new Error('Child not found');
    }

    const ageInMonths = this.calculateAgeInMonths(child.birthDate);

    // Schedule growth tracking based on age
    let intervalMonths = 1; // Monthly for infants
    if (ageInMonths > 12) intervalMonths = 3; // Quarterly for toddlers
    if (ageInMonths > 24) intervalMonths = 6; // Semi-annually for older children

    const scheduledDate = new Date();
    scheduledDate.setMonth(scheduledDate.getMonth() + intervalMonths);

    return this.createNotification(
      userId,
      NotificationType.GROWTH_TRACKING,
      `Growth Tracking Reminder for ${child.name}`,
      `Time to record ${child.name}'s height and weight. Regular tracking helps monitor healthy development.`,
      {
        childId,
        priority: NotificationPriority.MEDIUM,
        scheduledFor: scheduledDate,
        metadata: {
          ageInMonths,
          nextCheckMonths: intervalMonths,
        },
      },
    );
  }

  /**
   * Calculate age in months from date of birth
   */
  private calculateAgeInMonths(dateOfBirth: Date): number {
    const today = new Date();
    const birthDate = new Date(dateOfBirth);
    const months =
      (today.getFullYear() - birthDate.getFullYear()) * 12 +
      (today.getMonth() - birthDate.getMonth());
    return months;
  }

  /**
   * Bulk mark notifications as read
   */
  async markAllAsRead(userId: string): Promise<void> {
    await this.notificationRepository.update(
      { userId, status: NotificationStatus.PENDING },
      { status: NotificationStatus.READ, readAt: new Date() },
    );

    this.logger.debug(`All notifications marked as read for user ${userId}`);
  }

  /**
   * Delete old notifications (cleanup)
   */
  async cleanupOldNotifications(daysOld: number = 30): Promise<number> {
    const cutoffDate = new Date(Date.now() - daysOld * 24 * 60 * 60 * 1000);

    const result = await this.notificationRepository.delete({
      createdAt: LessThan(cutoffDate),
      status: NotificationStatus.READ,
    });

    this.logger.log(
      `Cleaned up ${result.affected || 0} old notifications (older than ${daysOld} days)`,
    );

    return result.affected || 0;
  }
}
