import { Injectable, ForbiddenException } 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';
import { FamilyMember } from '../../database/entities/family-member.entity';
import {
  ComparisonMetric,
  ComparisonResult,
  ComparisonChildData,
  SleepComparisonData,
  FeedingComparisonData,
  DiapeChangesComparisonData,
  ActivitiesComparisonData,
} from './dto/comparison.dto';
import {
  startOfDay,
  endOfDay,
  eachDayOfInterval,
  differenceInMonths,
  format,
  isWithinInterval,
  parseISO,
} from 'date-fns';

@Injectable()
export class ComparisonService {
  constructor(
    @InjectRepository(Activity)
    private activityRepository: Repository<Activity>,
    @InjectRepository(Child)
    private childRepository: Repository<Child>,
    @InjectRepository(FamilyMember)
    private familyMemberRepository: Repository<FamilyMember>,
  ) {}

  async compareChildren(
    userId: string,
    childIds: string[],
    metric: ComparisonMetric,
    startDate: Date,
    endDate: Date,
  ): Promise<ComparisonResult> {
    // Validate at least 2 children
    if (childIds.length < 2) {
      throw new ForbiddenException('At least 2 children required for comparison');
    }

    // Verify all children exist and user has access
    const children = await this.childRepository
      .createQueryBuilder('child')
      .where('child.id IN (:...childIds)', { childIds })
      .andWhere('child.deletedAt IS NULL')
      .getMany();

    if (children.length !== childIds.length) {
      throw new ForbiddenException('One or more children not found');
    }

    // Verify user has access to all children
    const familyIds = [...new Set(children.map((c) => c.familyId))];
    for (const familyId of familyIds) {
      const membership = await this.familyMemberRepository.findOne({
        where: { userId, familyId },
      });

      if (!membership) {
        throw new ForbiddenException(
          'You do not have access to one or more children',
        );
      }
    }

    // Route to appropriate comparison method
    switch (metric) {
      case ComparisonMetric.SLEEP_PATTERNS:
        return this.compareSleepPatterns(children, startDate, endDate);
      case ComparisonMetric.FEEDING_FREQUENCY:
        return this.compareFeedingFrequency(children, startDate, endDate);
      case ComparisonMetric.DIAPER_CHANGES:
        return this.compareDiaperChanges(children, startDate, endDate);
      case ComparisonMetric.ACTIVITIES:
        return this.compareActivities(children, startDate, endDate);
      default:
        throw new ForbiddenException(`Unsupported metric: ${metric}`);
    }
  }

  private async compareSleepPatterns(
    children: Child[],
    startDate: Date,
    endDate: Date,
  ): Promise<ComparisonResult> {
    const childrenData: ComparisonChildData[] = [];

    for (const child of children) {
      const sleepActivities = await this.activityRepository.find({
        where: {
          childId: child.id,
          type: ActivityType.SLEEP,
          startedAt: Between(startOfDay(startDate), endOfDay(endDate)),
        },
        order: { startedAt: 'ASC' },
      });

      const data = this.calculateSleepData(sleepActivities, startDate, endDate);
      const age = differenceInMonths(new Date(), new Date(child.birthDate));

      childrenData.push({
        childId: child.id,
        childName: child.name,
        age,
        data,
        summary: {
          averageHoursPerDay: data.averageHoursPerDay,
          totalSessions: data.totalSleepSessions,
          nightSleepPercentage: data.nightSleepPercentage,
        },
      });
    }

    // Calculate overall summary
    const overallSummary = {
      averageHoursAcrossChildren:
        childrenData.reduce((sum, c) => sum + c.data.averageHoursPerDay, 0) /
        childrenData.length,
      childWithMostSleep: childrenData.reduce((prev, current) =>
        prev.data.averageHoursPerDay > current.data.averageHoursPerDay
          ? prev
          : current,
      ).childName,
      childWithLeastSleep: childrenData.reduce((prev, current) =>
        prev.data.averageHoursPerDay < current.data.averageHoursPerDay
          ? prev
          : current,
      ).childName,
    };

    return {
      metric: ComparisonMetric.SLEEP_PATTERNS,
      dateRange: { start: startDate, end: endDate },
      children: childrenData,
      overallSummary,
    };
  }

  private calculateSleepData(
    activities: Activity[],
    startDate: Date,
    endDate: Date,
  ): SleepComparisonData {
    const days = eachDayOfInterval({ start: startDate, end: endDate });
    const totalDays = days.length;

    let totalMinutes = 0;
    let nightSleepMinutes = 0;
    let longestSleepDuration = 0;

    const dailyBreakdown = days.map((day) => {
      const dayActivities = activities.filter((a) =>
        isWithinInterval(new Date(a.startedAt), {
          start: startOfDay(day),
          end: endOfDay(day),
        }),
      );

      let dayTotalMinutes = 0;
      dayActivities.forEach((activity) => {
        const start = new Date(activity.startedAt);
        const end = activity.endedAt
          ? new Date(activity.endedAt)
          : new Date();
        const duration = Math.floor((end.getTime() - start.getTime()) / 60000);

        dayTotalMinutes += duration;
        totalMinutes += duration;

        if (duration > longestSleepDuration) {
          longestSleepDuration = duration;
        }

        // Check if night sleep (8PM - 6AM)
        const hour = start.getHours();
        if (hour >= 20 || hour < 6) {
          nightSleepMinutes += duration;
        }
      });

      return {
        date: format(day, 'yyyy-MM-dd'),
        totalMinutes: dayTotalMinutes,
        sessionsCount: dayActivities.length,
      };
    });

    // Count naps (sleep sessions during 6AM - 8PM)
    const naps = activities.filter((a) => {
      const hour = new Date(a.startedAt).getHours();
      return hour >= 6 && hour < 20;
    });

    return {
      averageHoursPerDay: totalMinutes / totalDays / 60,
      totalSleepSessions: activities.length,
      longestSleepDuration,
      averageNapsPerDay: naps.length / totalDays,
      nightSleepPercentage:
        totalMinutes > 0 ? (nightSleepMinutes / totalMinutes) * 100 : 0,
      dailyBreakdown,
    };
  }

  private async compareFeedingFrequency(
    children: Child[],
    startDate: Date,
    endDate: Date,
  ): Promise<ComparisonResult> {
    const childrenData: ComparisonChildData[] = [];

    for (const child of children) {
      const feedingActivities = await this.activityRepository.find({
        where: {
          childId: child.id,
          type: ActivityType.FEEDING,
          startedAt: Between(startOfDay(startDate), endOfDay(endDate)),
        },
        order: { startedAt: 'ASC' },
      });

      const data = this.calculateFeedingData(
        feedingActivities,
        startDate,
        endDate,
      );
      const age = differenceInMonths(new Date(), new Date(child.birthDate));

      childrenData.push({
        childId: child.id,
        childName: child.name,
        age,
        data,
        summary: {
          totalFeedings: data.totalFeedings,
          averagePerDay: data.averageFeedingsPerDay,
        },
      });
    }

    const overallSummary = {
      averageFeedingsAcrossChildren:
        childrenData.reduce((sum, c) => sum + c.data.averageFeedingsPerDay, 0) /
        childrenData.length,
      childWithMostFeedings: childrenData.reduce((prev, current) =>
        prev.data.totalFeedings > current.data.totalFeedings ? prev : current,
      ).childName,
    };

    return {
      metric: ComparisonMetric.FEEDING_FREQUENCY,
      dateRange: { start: startDate, end: endDate },
      children: childrenData,
      overallSummary,
    };
  }

  private calculateFeedingData(
    activities: Activity[],
    startDate: Date,
    endDate: Date,
  ): FeedingComparisonData {
    const days = eachDayOfInterval({ start: startDate, end: endDate });
    const totalDays = days.length;

    const feedingMethods: Record<string, number> = {};
    let totalAmount = 0;
    let amountCount = 0;

    const dailyBreakdown = days.map((day) => {
      const dayActivities = activities.filter((a) =>
        isWithinInterval(new Date(a.startedAt), {
          start: startOfDay(day),
          end: endOfDay(day),
        }),
      );

      let dayTotalAmount = 0;
      dayActivities.forEach((activity) => {
        const method = activity.metadata?.method || 'unknown';
        feedingMethods[method] = (feedingMethods[method] || 0) + 1;

        if (activity.metadata?.amount) {
          const amount = parseFloat(activity.metadata.amount);
          totalAmount += amount;
          dayTotalAmount += amount;
          amountCount++;
        }
      });

      return {
        date: format(day, 'yyyy-MM-dd'),
        count: dayActivities.length,
        totalAmount: dayTotalAmount > 0 ? dayTotalAmount : undefined,
      };
    });

    return {
      totalFeedings: activities.length,
      averageFeedingsPerDay: activities.length / totalDays,
      feedingMethods,
      averageAmountPerFeeding:
        amountCount > 0 ? totalAmount / amountCount : undefined,
      dailyBreakdown,
    };
  }

  private async compareDiaperChanges(
    children: Child[],
    startDate: Date,
    endDate: Date,
  ): Promise<ComparisonResult> {
    const childrenData: ComparisonChildData[] = [];

    for (const child of children) {
      const diaperActivities = await this.activityRepository.find({
        where: {
          childId: child.id,
          type: ActivityType.DIAPER,
          startedAt: Between(startOfDay(startDate), endOfDay(endDate)),
        },
        order: { startedAt: 'ASC' },
      });

      const data = this.calculateDiaperData(
        diaperActivities,
        startDate,
        endDate,
      );
      const age = differenceInMonths(new Date(), new Date(child.birthDate));

      childrenData.push({
        childId: child.id,
        childName: child.name,
        age,
        data,
        summary: {
          totalChanges: data.totalChanges,
          averagePerDay: data.averageChangesPerDay,
        },
      });
    }

    return {
      metric: ComparisonMetric.DIAPER_CHANGES,
      dateRange: { start: startDate, end: endDate },
      children: childrenData,
    };
  }

  private calculateDiaperData(
    activities: Activity[],
    startDate: Date,
    endDate: Date,
  ): DiapeChangesComparisonData {
    const days = eachDayOfInterval({ start: startDate, end: endDate });
    const totalDays = days.length;

    let wetDiapers = 0;
    let dirtyDiapers = 0;
    let bothDiapers = 0;

    const dailyBreakdown = days.map((day) => {
      const dayActivities = activities.filter((a) =>
        isWithinInterval(new Date(a.startedAt), {
          start: startOfDay(day),
          end: endOfDay(day),
        }),
      );

      let dayWet = 0;
      let dayDirty = 0;
      let dayBoth = 0;

      dayActivities.forEach((activity) => {
        const type = activity.metadata?.type || 'wet';
        if (type === 'wet') {
          wetDiapers++;
          dayWet++;
        } else if (type === 'dirty') {
          dirtyDiapers++;
          dayDirty++;
        } else if (type === 'both') {
          bothDiapers++;
          dayBoth++;
        }
      });

      return {
        date: format(day, 'yyyy-MM-dd'),
        wet: dayWet,
        dirty: dayDirty,
        both: dayBoth,
      };
    });

    return {
      totalChanges: activities.length,
      averageChangesPerDay: activities.length / totalDays,
      wetDiapers,
      dirtyDiapers,
      bothDiapers,
      dailyBreakdown,
    };
  }

  private async compareActivities(
    children: Child[],
    startDate: Date,
    endDate: Date,
  ): Promise<ComparisonResult> {
    const childrenData: ComparisonChildData[] = [];

    for (const child of children) {
      const activities = await this.activityRepository.find({
        where: {
          childId: child.id,
          type: ActivityType.ACTIVITY,
          startedAt: Between(startOfDay(startDate), endOfDay(endDate)),
        },
        order: { startedAt: 'ASC' },
      });

      const data = this.calculateActivitiesData(activities, startDate, endDate);
      const age = differenceInMonths(new Date(), new Date(child.birthDate));

      childrenData.push({
        childId: child.id,
        childName: child.name,
        age,
        data,
        summary: {
          totalActivities: data.totalActivities,
          averagePerDay: data.averageActivitiesPerDay,
          mostCommon: data.mostCommonActivity,
        },
      });
    }

    return {
      metric: ComparisonMetric.ACTIVITIES,
      dateRange: { start: startDate, end: endDate },
      children: childrenData,
    };
  }

  private calculateActivitiesData(
    activities: Activity[],
    startDate: Date,
    endDate: Date,
  ): ActivitiesComparisonData {
    const days = eachDayOfInterval({ start: startDate, end: endDate });
    const totalDays = days.length;

    const activitiesByType: Record<string, number> = {};

    const dailyBreakdown = days.map((day) => {
      const dayActivities = activities.filter((a) =>
        isWithinInterval(new Date(a.startedAt), {
          start: startOfDay(day),
          end: endOfDay(day),
        }),
      );

      const dayTypes: Record<string, number> = {};
      dayActivities.forEach((activity) => {
        const activityType =
          activity.metadata?.activityType || 'unspecified';
        activitiesByType[activityType] =
          (activitiesByType[activityType] || 0) + 1;
        dayTypes[activityType] = (dayTypes[activityType] || 0) + 1;
      });

      return {
        date: format(day, 'yyyy-MM-dd'),
        count: dayActivities.length,
        types: dayTypes,
      };
    });

    // Find most common activity
    const mostCommonActivity =
      Object.keys(activitiesByType).length > 0
        ? Object.entries(activitiesByType).reduce((prev, current) =>
            prev[1] > current[1] ? prev : current,
          )[0]
        : 'none';

    return {
      totalActivities: activities.length,
      averageActivitiesPerDay: activities.length / totalDays,
      activitiesByType,
      mostCommonActivity,
      dailyBreakdown,
    };
  }
}
