import { prisma } from '../../../config/database';
import { RecurrenceFrequency } from '@prisma/client';
import type { CreateShiftSeriesInput, UpdateShiftSeriesInput } from './shift-series.schemas';

const MAX_OCCURRENCES = 100;
const MAX_DAYS = 365;

export class ShiftSeriesService {
  /**
   * Generate occurrence dates based on recurrence rules
   */
  static generateOccurrences(input: CreateShiftSeriesInput): Date[] {
    const dates: Date[] = [];
    const startDate = new Date(input.startDate);
    const endDate = input.endDate ? new Date(input.endDate) : null;

    let currentDate = new Date(startDate);
    let count = 0;

    while (count < MAX_OCCURRENCES) {
      // Check end date
      if (endDate && currentDate > endDate) break;

      // Check max days
      const daysDiff = Math.floor((currentDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
      if (daysDiff > MAX_DAYS) break;

      // Check if date matches recurrence pattern
      let shouldInclude = false;

      if (input.frequency === RecurrenceFrequency.DAILY) {
        shouldInclude = true;
      } else if (input.frequency === RecurrenceFrequency.WEEKLY) {
        const dayOfWeek = currentDate.getDay(); // 0=Sun, 6=Sat
        shouldInclude = input.daysOfWeek?.includes(dayOfWeek) ?? false;
      } else if (input.frequency === RecurrenceFrequency.MONTHLY) {
        shouldInclude = currentDate.getDate() === startDate.getDate();
      }

      if (shouldInclude) {
        dates.push(new Date(currentDate));
        count++;
      }

      // Increment date
      if (input.frequency === RecurrenceFrequency.DAILY) {
        currentDate.setDate(currentDate.getDate() + 1);
      } else if (input.frequency === RecurrenceFrequency.WEEKLY) {
        currentDate.setDate(currentDate.getDate() + 1); // Check each day
      } else if (input.frequency === RecurrenceFrequency.MONTHLY) {
        currentDate.setMonth(currentDate.getMonth() + 1);
      }
    }

    return dates;
  }

  /**
   * Create a shift series and generate all individual shifts
   */
  static async createSeries(input: CreateShiftSeriesInput, createdBy?: string) {
    // Generate occurrence dates
    const dates = this.generateOccurrences(input);

    if (dates.length === 0) {
      throw new Error('No valid dates generated from recurrence rules');
    }

    // Create series and shifts in transaction
    const result = await prisma.$transaction(async (tx) => {
      // Create series
      const series = await tx.shiftSeries.create({
        data: {
          title: input.title,
          description: input.description,
          startTime: input.startTime,
          endTime: input.endTime,
          location: input.location,
          maxVolunteers: input.maxVolunteers,
          isPublic: input.isPublic ?? false,
          cutId: input.cutId,
          frequency: input.frequency,
          daysOfWeek: input.daysOfWeek as any,
          startDate: new Date(input.startDate),
          endDate: input.endDate ? new Date(input.endDate) : null,
          createdBy,
        },
      });

      // Create individual shifts
      const shifts = await Promise.all(
        dates.map((date) =>
          tx.shift.create({
            data: {
              title: input.title,
              description: input.description,
              date,
              startTime: input.startTime,
              endTime: input.endTime,
              location: input.location,
              maxVolunteers: input.maxVolunteers,
              isPublic: input.isPublic ?? false,
              cutId: input.cutId,
              kind: input.kind,
              seriesId: series.id,
              createdBy,
            },
          })
        )
      );

      return { series, shifts };
    });

    return {
      series: result.series,
      generatedShiftsCount: result.shifts.length,
    };
  }

  /**
   * Get count of non-exception shifts in a series
   */
  static async getShiftCount(seriesId: string): Promise<number> {
    return prisma.shift.count({
      where: { seriesId, isException: false },
    });
  }

  /**
   * Get series with all its shifts
   */
  static async getSeries(seriesId: string) {
    const series = await prisma.shiftSeries.findUnique({
      where: { id: seriesId },
      include: {
        cut: true,
        shifts: {
          orderBy: { date: 'asc' },
          include: { signups: true },
        },
      },
    });

    if (!series) {
      throw new Error('Shift series not found');
    }

    return series;
  }

  /**
   * Update series shifts based on edit mode
   */
  static async updateSeries(
    seriesId: string,
    input: UpdateShiftSeriesInput
  ) {
    const series = await prisma.shiftSeries.findUnique({
      where: { id: seriesId },
      include: { shifts: true },
    });

    if (!series) {
      throw new Error('Shift series not found');
    }

    const { editMode, fromDate, ...updates } = input;

    if (editMode === 'ALL') {
      // Update series + all shifts
      await prisma.$transaction(async (tx) => {
        await tx.shiftSeries.update({
          where: { id: seriesId },
          data: updates,
        });

        await tx.shift.updateMany({
          where: { seriesId, isException: false },
          data: updates,
        });
      });
    } else if (editMode === 'FUTURE' && fromDate) {
      // Update shifts from date onwards
      await prisma.shift.updateMany({
        where: {
          seriesId,
          date: { gte: new Date(fromDate) },
          isException: false,
        },
        data: updates,
      });
    }
    // Note: THIS mode is handled by regular shift update with isException flag

    return this.getSeries(seriesId);
  }

  /**
   * Delete series (cascades to shifts via onDelete: SetNull)
   */
  static async deleteSeries(seriesId: string) {
    // Delete all shifts in series first
    await prisma.shift.deleteMany({
      where: { seriesId },
    });

    // Then delete series
    await prisma.shiftSeries.delete({
      where: { id: seriesId },
    });
  }
}
