import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Between } from 'typeorm';
import {
  Activity,
  ActivityType,
} from '../../database/entities/activity.entity';
import { Child } from '../../database/entities/child.entity';

export interface SleepPrediction {
  nextNapTime: Date | null;
  nextNapConfidence: number; // 0-1
  nextBedtime: Date | null;
  bedtimeConfidence: number;
  optimalWakeWindows: number[]; // in minutes
  reasoning: string;
}

export interface FeedingPrediction {
  nextFeedingTime: Date | null;
  confidence: number;
  expectedInterval: number; // in hours
  reasoning: string;
}

export interface PredictionInsights {
  sleep: SleepPrediction;
  feeding: FeedingPrediction;
  generatedAt: Date;
}

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

  // Target confidence threshold (Huckleberry's SweetSpot® uses 85%)
  private readonly CONFIDENCE_THRESHOLD = 0.85;

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

  /**
   * Generate predictions for a child
   */
  async generatePredictions(childId: string): Promise<PredictionInsights> {
    const child = await this.childRepository.findOne({
      where: { id: childId },
    });
    if (!child) {
      throw new Error('Child not found');
    }

    // Get last 14 days of activities for better pattern detection
    const cutoffDate = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000);
    const activities = await this.activityRepository.find({
      where: {
        childId,
        startedAt: Between(cutoffDate, new Date()),
      },
      order: { startedAt: 'ASC' },
    });

    const sleepPrediction = await this.predictNextSleep(activities, child);
    const feedingPrediction = await this.predictNextFeeding(activities, child);

    return {
      sleep: sleepPrediction,
      feeding: feedingPrediction,
      generatedAt: new Date(),
    };
  }

  /**
   * Predict next sleep time (Huckleberry SweetSpot® inspired algorithm)
   */
  private async predictNextSleep(
    activities: Activity[],
    child: Child,
  ): Promise<SleepPrediction> {
    const sleepActivities = activities.filter(
      (a) => a.type === ActivityType.SLEEP && a.endedAt,
    );

    if (sleepActivities.length < 5) {
      return {
        nextNapTime: null,
        nextNapConfidence: 0,
        nextBedtime: null,
        bedtimeConfidence: 0,
        optimalWakeWindows: this.getDefaultWakeWindows(child),
        reasoning: 'Insufficient data for accurate predictions',
      };
    }

    // Find the last sleep session
    const lastSleep = sleepActivities[sleepActivities.length - 1];
    const timeSinceWake = Date.now() - lastSleep.endedAt!.getTime();

    // Calculate age-appropriate wake windows
    const optimalWakeWindows = this.getAgeAppropriateWakeWindows(child);

    // Analyze historical wake windows
    const historicalWakeWindows: number[] = [];
    for (let i = 1; i < sleepActivities.length; i++) {
      const wakeWindow =
        (sleepActivities[i].startedAt.getTime() -
          sleepActivities[i - 1].endedAt!.getTime()) /
        (1000 * 60); // minutes
      historicalWakeWindows.push(wakeWindow);
    }

    // Calculate average wake window
    const avgWakeWindow =
      historicalWakeWindows.reduce((a, b) => a + b, 0) /
      historicalWakeWindows.length;

    // Calculate standard deviation for confidence
    const stdDev = this.calculateStdDev(historicalWakeWindows);
    const consistency = Math.max(0, 1 - stdDev / avgWakeWindow);

    // Predict next nap based on wake window
    const nextNapTime = new Date(
      lastSleep.endedAt!.getTime() + avgWakeWindow * 60 * 1000,
    );
    const nextNapConfidence = Math.min(
      consistency * 0.9 + (historicalWakeWindows.length / 20) * 0.1,
      0.95,
    );

    // Predict bedtime (analyze historical bedtimes)
    const nightSleeps = sleepActivities.filter((a) => {
      const hour = a.startedAt.getHours();
      return hour >= 18 || hour <= 6;
    });

    let nextBedtime: Date | null = null;
    let bedtimeConfidence = 0;

    if (nightSleeps.length >= 3) {
      const bedtimes = nightSleeps.map((a) => a.startedAt);
      const avgBedtimeMinutes = this.calculateAverageTimeInMinutes(bedtimes);

      const today = new Date();
      const predictedBedtime = new Date(today);
      predictedBedtime.setHours(
        Math.floor(avgBedtimeMinutes / 60),
        avgBedtimeMinutes % 60,
        0,
        0,
      );

      // If predicted bedtime is in the past, move to tomorrow
      if (predictedBedtime.getTime() < Date.now()) {
        predictedBedtime.setDate(predictedBedtime.getDate() + 1);
      }

      nextBedtime = predictedBedtime;

      // Calculate bedtime consistency
      const bedtimeMinutes = bedtimes.map(
        (d) => d.getHours() * 60 + d.getMinutes(),
      );
      const bedtimeStdDev = this.calculateStdDev(bedtimeMinutes);
      bedtimeConfidence = Math.max(0, Math.min(1 - bedtimeStdDev / 60, 0.95)); // Normalize by 1 hour
    }

    const reasoning = this.generateSleepReasoning(
      avgWakeWindow,
      consistency,
      historicalWakeWindows.length,
      nextNapConfidence,
    );

    return {
      nextNapTime,
      nextNapConfidence: Math.round(nextNapConfidence * 100) / 100,
      nextBedtime,
      bedtimeConfidence: Math.round(bedtimeConfidence * 100) / 100,
      optimalWakeWindows,
      reasoning,
    };
  }

  /**
   * Predict next feeding time
   */
  private async predictNextFeeding(
    activities: Activity[],
    child: Child,
  ): Promise<FeedingPrediction> {
    const feedingActivities = activities.filter(
      (a) => a.type === ActivityType.FEEDING,
    );

    if (feedingActivities.length < 3) {
      return {
        nextFeedingTime: null,
        confidence: 0,
        expectedInterval: this.getDefaultFeedingInterval(child),
        reasoning: 'Insufficient data for accurate predictions',
      };
    }

    // Calculate intervals between feedings
    const intervals: number[] = [];
    for (let i = 1; i < feedingActivities.length; i++) {
      const interval =
        (feedingActivities[i].startedAt.getTime() -
          feedingActivities[i - 1].startedAt.getTime()) /
        (1000 * 60 * 60); // hours
      intervals.push(interval);
    }

    // Calculate average interval
    const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;

    // Calculate consistency for confidence
    const stdDev = this.calculateStdDev(intervals);
    const consistency = Math.max(0, 1 - stdDev / avgInterval);

    // Predict next feeding
    const lastFeeding = feedingActivities[feedingActivities.length - 1];
    const nextFeedingTime = new Date(
      lastFeeding.startedAt.getTime() + avgInterval * 60 * 60 * 1000,
    );

    const confidence = Math.min(
      consistency * 0.9 + (intervals.length / 20) * 0.1,
      0.95,
    );

    const reasoning = this.generateFeedingReasoning(
      avgInterval,
      consistency,
      intervals.length,
      confidence,
    );

    return {
      nextFeedingTime,
      confidence: Math.round(confidence * 100) / 100,
      expectedInterval: Math.round(avgInterval * 10) / 10,
      reasoning,
    };
  }

  /**
   * Get age-appropriate wake windows (based on pediatric research)
   */
  private getAgeAppropriateWakeWindows(child: Child): number[] {
    const ageInMonths = this.calculateAgeInMonths(child.birthDate);

    // Wake windows in minutes based on age
    if (ageInMonths < 1) return [45, 60, 75]; // 0-1 month
    if (ageInMonths < 3) return [60, 75, 90]; // 1-3 months
    if (ageInMonths < 6) return [75, 90, 120]; // 3-6 months
    if (ageInMonths < 9) return [120, 150, 180]; // 6-9 months
    if (ageInMonths < 12) return [150, 180, 240]; // 9-12 months
    return [180, 240, 300]; // 12+ months
  }

  /**
   * Get default wake windows for infants
   */
  private getDefaultWakeWindows(child: Child): number[] {
    return this.getAgeAppropriateWakeWindows(child);
  }

  /**
   * Get default feeding interval based on age
   */
  private getDefaultFeedingInterval(child: Child): number {
    const ageInMonths = this.calculateAgeInMonths(child.birthDate);

    if (ageInMonths < 1) return 2.5; // Every 2-3 hours
    if (ageInMonths < 3) return 3; // Every 3 hours
    if (ageInMonths < 6) return 3.5; // Every 3-4 hours
    return 4; // Every 4 hours
  }

  /**
   * Generate reasoning for sleep prediction
   */
  private generateSleepReasoning(
    avgWakeWindow: number,
    consistency: number,
    dataPoints: number,
    confidence: number,
  ): string {
    const hours = Math.floor(avgWakeWindow / 60);
    const minutes = Math.round(avgWakeWindow % 60);

    if (confidence >= this.CONFIDENCE_THRESHOLD) {
      return `High confidence prediction based on ${dataPoints} sleep sessions. Average wake window: ${hours}h ${minutes}m. Pattern consistency: ${Math.round(consistency * 100)}%.`;
    } else if (confidence >= 0.6) {
      return `Moderate confidence prediction. Building pattern data from ${dataPoints} sleep sessions.`;
    } else {
      return `Low confidence prediction. More data needed for accurate predictions. Current data: ${dataPoints} sleep sessions.`;
    }
  }

  /**
   * Generate reasoning for feeding prediction
   */
  private generateFeedingReasoning(
    avgInterval: number,
    consistency: number,
    dataPoints: number,
    confidence: number,
  ): string {
    if (confidence >= this.CONFIDENCE_THRESHOLD) {
      return `High confidence prediction based on ${dataPoints} feedings. Average interval: ${Math.round(avgInterval * 10) / 10} hours. Pattern consistency: ${Math.round(consistency * 100)}%.`;
    } else if (confidence >= 0.6) {
      return `Moderate confidence prediction. Building pattern data from ${dataPoints} feedings.`;
    } else {
      return `Low confidence prediction. More data needed for accurate predictions. Current data: ${dataPoints} feedings.`;
    }
  }

  /**
   * Calculate average time in minutes from midnight
   */
  private calculateAverageTimeInMinutes(dates: Date[]): number {
    const minutes = dates.map((d) => d.getHours() * 60 + d.getMinutes());
    return Math.round(minutes.reduce((sum, m) => sum + m, 0) / minutes.length);
  }

  /**
   * Calculate standard deviation
   */
  private calculateStdDev(values: number[]): number {
    const avg = values.reduce((sum, v) => sum + v, 0) / values.length;
    const squareDiffs = values.map((v) => Math.pow(v - avg, 2));
    const avgSquareDiff =
      squareDiffs.reduce((sum, d) => sum + d, 0) / values.length;
    return Math.sqrt(avgSquareDiff);
  }

  /**
   * Calculate age in months
   */
  private calculateAgeInMonths(birthDate: Date): number {
    const now = new Date();
    const months =
      (now.getFullYear() - birthDate.getFullYear()) * 12 +
      (now.getMonth() - birthDate.getMonth());
    return months;
  }
}
