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 SleepPattern {
  averageDuration: number; // in minutes
  averageBedtime: string; // HH:MM format
  averageWakeTime: string;
  nightWakings: number;
  napCount: number;
  consistency: number; // 0-1 score
  trend: 'improving' | 'stable' | 'declining';
}

export interface FeedingPattern {
  averageInterval: number; // in hours
  averageDuration: number; // in minutes
  totalFeedings: number;
  feedingMethod: Record<string, number>; // e.g., { bottle: 5, nursing: 3 }
  consistency: number;
  trend: 'increasing' | 'stable' | 'decreasing';
}

export interface DiaperPattern {
  wetDiapersPerDay: number;
  dirtyDiapersPerDay: number;
  averageInterval: number; // in hours
  isHealthy: boolean;
  notes: string;
}

export interface PatternInsights {
  sleep: SleepPattern | null;
  feeding: FeedingPattern | null;
  diaper: DiaperPattern | null;
  recommendations: string[];
  concernsDetected: string[];
}

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

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

  /**
   * Analyze all patterns for a child
   */
  async analyzePatterns(
    childId: string,
    days: number = 7,
  ): Promise<PatternInsights> {
    const cutoffDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000);

    const activities = await this.activityRepository.find({
      where: {
        childId,
        startedAt: Between(cutoffDate, new Date()),
      },
      order: { startedAt: 'ASC' },
    });

    const child = await this.childRepository.findOne({
      where: { id: childId },
    });

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

    // Analyze patterns
    const sleepPattern = await this.analyzeSleepPatterns(activities, child);
    const feedingPattern = await this.analyzeFeedingPatterns(activities, child);
    const diaperPattern = await this.analyzeDiaperPatterns(activities, child);

    // Generate recommendations and detect concerns
    const recommendations = this.generateRecommendations(
      sleepPattern,
      feedingPattern,
      diaperPattern,
      child,
    );
    const concernsDetected = this.detectConcerns(
      sleepPattern,
      feedingPattern,
      diaperPattern,
      child,
    );

    return {
      sleep: sleepPattern,
      feeding: feedingPattern,
      diaper: diaperPattern,
      recommendations,
      concernsDetected,
    };
  }

  /**
   * Analyze sleep patterns
   */
  async analyzeSleepPatterns(
    activities: Activity[],
    child: Child,
  ): Promise<SleepPattern | null> {
    const sleepActivities = activities.filter(
      (a) => a.type === ActivityType.SLEEP && a.endedAt,
    );

    if (sleepActivities.length < 3) {
      return null;
    }

    // Calculate average duration
    const durations = sleepActivities.map(
      (a) => (a.endedAt!.getTime() - a.startedAt.getTime()) / (1000 * 60), // minutes
    );
    const averageDuration =
      durations.reduce((sum, d) => sum + d, 0) / durations.length;

    // Find bedtime pattern (sleep sessions starting between 6PM-11PM)
    const nightSleeps = sleepActivities.filter((a) => {
      const hour = a.startedAt.getHours();
      return hour >= 18 || hour <= 6;
    });

    const averageBedtime = this.calculateAverageTime(
      nightSleeps.map((a) => a.startedAt),
    );
    const averageWakeTime = this.calculateAverageTime(
      nightSleeps.map((a) => a.endedAt!),
    );

    // Count night wakings (sleep sessions during night hours)
    const nightWakings = nightSleeps.length > 0 ? nightSleeps.length - 1 : 0;

    // Count naps (sleep sessions during day hours)
    const naps = sleepActivities.filter((a) => {
      const hour = a.startedAt.getHours();
      return hour >= 7 && hour < 18;
    });
    const napCount = naps.length / (sleepActivities.length / 7); // average per day

    // Calculate consistency (standard deviation of sleep duration)
    const stdDev = this.calculateStdDev(durations);
    const consistency = Math.max(0, 1 - stdDev / averageDuration);

    // Determine trend
    const recentAvg = durations.slice(-3).reduce((a, b) => a + b, 0) / 3;
    const olderAvg =
      durations.slice(0, 3).reduce((a, b) => a + b, 0) /
      Math.min(3, durations.length);
    const trend =
      recentAvg > olderAvg * 1.1
        ? 'improving'
        : recentAvg < olderAvg * 0.9
          ? 'declining'
          : 'stable';

    return {
      averageDuration: Math.round(averageDuration),
      averageBedtime,
      averageWakeTime,
      nightWakings: Math.round(nightWakings),
      napCount: Math.round(napCount * 10) / 10,
      consistency: Math.round(consistency * 100) / 100,
      trend,
    };
  }

  /**
   * Analyze feeding patterns
   */
  async analyzeFeedingPatterns(
    activities: Activity[],
    child: Child,
  ): Promise<FeedingPattern | null> {
    const feedingActivities = activities.filter(
      (a) => a.type === ActivityType.FEEDING,
    );

    if (feedingActivities.length < 3) {
      return null;
    }

    // Calculate average interval 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);
    }
    const averageInterval =
      intervals.reduce((sum, i) => sum + i, 0) / intervals.length;

    // Calculate average duration (if available)
    const durationsInMinutes = feedingActivities
      .filter((a) => a.endedAt)
      .map((a) => (a.endedAt!.getTime() - a.startedAt.getTime()) / (1000 * 60));
    const averageDuration =
      durationsInMinutes.length > 0
        ? durationsInMinutes.reduce((sum, d) => sum + d, 0) /
          durationsInMinutes.length
        : 0;

    // Analyze feeding methods
    const feedingMethod: Record<string, number> = {};
    feedingActivities.forEach((a) => {
      const method = a.metadata?.method || 'unknown';
      feedingMethod[method] = (feedingMethod[method] || 0) + 1;
    });

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

    // Determine trend
    const recentCount = feedingActivities.filter(
      (a) => a.startedAt.getTime() > Date.now() - 3 * 24 * 60 * 60 * 1000,
    ).length;
    const olderCount = feedingActivities.filter(
      (a) =>
        a.startedAt.getTime() <= Date.now() - 3 * 24 * 60 * 60 * 1000 &&
        a.startedAt.getTime() > Date.now() - 6 * 24 * 60 * 60 * 1000,
    ).length;

    const trend =
      recentCount > olderCount * 1.2
        ? 'increasing'
        : recentCount < olderCount * 0.8
          ? 'decreasing'
          : 'stable';

    return {
      averageInterval: Math.round(averageInterval * 10) / 10,
      averageDuration: Math.round(averageDuration),
      totalFeedings: feedingActivities.length,
      feedingMethod,
      consistency: Math.round(consistency * 100) / 100,
      trend,
    };
  }

  /**
   * Analyze diaper patterns
   */
  async analyzeDiaperPatterns(
    activities: Activity[],
    child: Child,
  ): Promise<DiaperPattern | null> {
    const diaperActivities = activities.filter(
      (a) => a.type === ActivityType.DIAPER,
    );

    if (diaperActivities.length < 3) {
      return null;
    }

    const days = Math.max(
      1,
      (Date.now() - diaperActivities[0].startedAt.getTime()) /
        (1000 * 60 * 60 * 24),
    );

    // Count wet and dirty diapers
    const wetCount = diaperActivities.filter(
      (a) => a.metadata?.type === 'wet' || a.metadata?.type === 'both',
    ).length;
    const dirtyCount = diaperActivities.filter(
      (a) => a.metadata?.type === 'dirty' || a.metadata?.type === 'both',
    ).length;

    const wetDiapersPerDay = wetCount / days;
    const dirtyDiapersPerDay = dirtyCount / days;

    // Calculate average interval
    const intervals: number[] = [];
    for (let i = 1; i < diaperActivities.length; i++) {
      const interval =
        (diaperActivities[i].startedAt.getTime() -
          diaperActivities[i - 1].startedAt.getTime()) /
        (1000 * 60 * 60); // hours
      intervals.push(interval);
    }
    const averageInterval =
      intervals.reduce((sum, i) => sum + i, 0) / intervals.length;

    // Determine if pattern is healthy (age-appropriate)
    const ageInMonths = this.calculateAgeInMonths(child.birthDate);
    const isHealthy =
      wetDiapersPerDay >= 4 && // Minimum expected wet diapers
      dirtyDiapersPerDay >= (ageInMonths < 2 ? 2 : 1); // Newborns: 2+, older: 1+

    const notes = isHealthy
      ? 'Diaper output is within healthy range'
      : 'Diaper output may be below expected range - consult pediatrician if concerned';

    return {
      wetDiapersPerDay: Math.round(wetDiapersPerDay * 10) / 10,
      dirtyDiapersPerDay: Math.round(dirtyDiapersPerDay * 10) / 10,
      averageInterval: Math.round(averageInterval * 10) / 10,
      isHealthy,
      notes,
    };
  }

  /**
   * Generate recommendations based on patterns
   */
  private generateRecommendations(
    sleep: SleepPattern | null,
    feeding: FeedingPattern | null,
    diaper: DiaperPattern | null,
    child: Child,
  ): string[] {
    const recommendations: string[] = [];

    // Sleep recommendations
    if (sleep) {
      if (sleep.consistency < 0.7) {
        recommendations.push(
          'Try to maintain a consistent bedtime routine to improve sleep quality',
        );
      }
      if (sleep.nightWakings > 3) {
        recommendations.push(
          'Consider techniques to reduce night wakings, such as dream feeding',
        );
      }
      if (sleep.averageDuration < 600) {
        // Less than 10 hours
        recommendations.push(
          'Your child may benefit from earlier bedtimes or longer naps',
        );
      }
    }

    // Feeding recommendations
    if (feeding) {
      if (feeding.consistency < 0.6) {
        recommendations.push(
          'Try feeding on a more regular schedule to establish a routine',
        );
      }
      if (feeding.trend === 'decreasing') {
        recommendations.push(
          'Monitor feeding frequency - consult pediatrician if decline continues',
        );
      }
    }

    // Diaper recommendations
    if (diaper && !diaper.isHealthy) {
      recommendations.push(
        'Diaper output appears low - ensure adequate hydration and consult pediatrician',
      );
    }

    return recommendations;
  }

  /**
   * Detect concerns based on patterns
   */
  private detectConcerns(
    sleep: SleepPattern | null,
    feeding: FeedingPattern | null,
    diaper: DiaperPattern | null,
    child: Child,
  ): string[] {
    const concerns: string[] = [];

    // Sleep concerns
    if (sleep) {
      if (sleep.trend === 'declining') {
        concerns.push('Sleep duration has been decreasing');
      }
      if (sleep.nightWakings > 5) {
        concerns.push('Frequent night wakings detected');
      }
    }

    // Feeding concerns
    if (feeding) {
      if (feeding.trend === 'decreasing' && feeding.totalFeedings < 15) {
        concerns.push('Feeding frequency appears to be decreasing');
      }
    }

    // Diaper concerns
    if (diaper && !diaper.isHealthy) {
      concerns.push('Diaper output is below expected range');
    }

    return concerns;
  }

  /**
   * Calculate average time from array of dates
   */
  private calculateAverageTime(dates: Date[]): string {
    if (dates.length === 0) return '00:00';

    // Convert to minutes from midnight
    const minutes = dates.map((d) => d.getHours() * 60 + d.getMinutes());
    const avgMinutes = minutes.reduce((sum, m) => sum + m, 0) / minutes.length;

    const hours = Math.floor(avgMinutes / 60);
    const mins = Math.round(avgMinutes % 60);

    return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;
  }

  /**
   * 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;
  }
}
