import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import { BookingEventsService } from '../core/booking-events.service';
import {
  CreateGroupEventDto,
  UpdateGroupEventDto,
  RepeatGroupEventDto,
  FindGroupEventsQueryDto,
} from '../dto/create-group-event.dto';
import { randomUUID } from 'crypto';

const EVENT_INCLUDE = {
  service: { select: { id: true, name: true, color: true, duration: true } },
  staff: { select: { id: true, firstName: true, lastName: true, avatarUrl: true } },
  room: { select: { id: true, name: true } },
  _count: { select: { records: true } },
};

@Injectable()
export class GroupEventsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly events: BookingEventsService,
  ) {}

  // ─── List ───────────────────────────────────────────────────

  async findAll(clubId: string, query: FindGroupEventsQueryDto, roleFilter: Record<string, any>) {
    const where: Record<string, any> = {
      clubId,
      ...roleFilter,
      ...(query.serviceId ? { serviceId: query.serviceId } : {}),
      ...(query.staffId ? { staffId: query.staffId } : {}),
      ...(query.includeCancelled ? {} : { isCancelled: false }),
    };

    if (query.from || query.to) {
      where.startAt = {};
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      if (query.from) where.startAt.gte = new Date(query.from);
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      if (query.to) where.startAt.lte = new Date(query.to + 'T23:59:59');
    }

    return this.prisma.bookingGroupEvent.findMany({
      where,
      include: EVENT_INCLUDE,
      orderBy: { startAt: 'asc' },
    });
  }

  // ─── Get by ID ──────────────────────────────────────────────

  async findById(id: string) {
    const event = await this.prisma.bookingGroupEvent.findUnique({
      where: { id },
      include: {
        ...EVENT_INCLUDE,
        records: {
          where: { status: { notIn: ['CANCELLED'] } },
          select: {
            id: true,
            clientName: true,
            clientPhone: true,
            status: true,
            places: true,
          },
          orderBy: { createdAt: 'asc' },
        },
      },
    });
    if (!event) throw new NotFoundException(`Group event ${id} not found`);
    return event;
  }

  // ─── Create ─────────────────────────────────────────────────

  async create(clubId: string, dto: CreateGroupEventDto, userId?: string) {
    const event = await this.prisma.bookingGroupEvent.create({
      data: {
        clubId,
        serviceId: dto.serviceId,
        staffId: dto.staffId,
        roomId: dto.roomId,
        title: dto.title,
        startAt: new Date(dto.startAt),
        endAt: new Date(dto.endAt),
        capacity: dto.capacity,
      },
      include: EVENT_INCLUDE,
    });

    await this.events.emit('booking.group_event.created', {
      clubId,
      data: { event },
      userId,
    });

    return event;
  }

  // ─── Update ─────────────────────────────────────────────────

  async update(id: string, dto: UpdateGroupEventDto) {
    const existing = await this.findById(id);
    if (existing.isCancelled) {
      throw new BadRequestException('Cannot update a cancelled event');
    }

    if (dto.capacity !== undefined && dto.capacity < existing.bookedCount) {
      throw new BadRequestException(
        `Cannot reduce capacity below current bookings (${existing.bookedCount})`,
      );
    }

    const event = await this.prisma.bookingGroupEvent.update({
      where: { id },
      data: {
        ...(dto.staffId !== undefined ? { staffId: dto.staffId } : {}),
        ...(dto.roomId !== undefined ? { roomId: dto.roomId } : {}),
        ...(dto.title !== undefined ? { title: dto.title } : {}),
        ...(dto.startAt !== undefined ? { startAt: new Date(dto.startAt) } : {}),
        ...(dto.endAt !== undefined ? { endAt: new Date(dto.endAt) } : {}),
        ...(dto.capacity !== undefined ? { capacity: dto.capacity } : {}),
      },
      include: EVENT_INCLUDE,
    });

    await this.events.emit('booking.group_event.updated', {
      clubId: event.clubId,
      data: { event, changes: dto },
    });

    return event;
  }

  // ─── Cancel ─────────────────────────────────────────────────

  async cancel(id: string, userId?: string) {
    const existing = await this.findById(id);
    if (existing.isCancelled) {
      throw new BadRequestException('Event is already cancelled');
    }

    // Cancel all active records
    const affectedRecords = await this.prisma.bookingRecord.updateMany({
      where: {
        groupEventId: id,
        status: { notIn: ['CANCELLED', 'NO_SHOW', 'COMPLETED'] },
      },
      data: {
        status: 'CANCELLED',
        cancelledAt: new Date(),
        cancelReason: 'Group event cancelled',
      },
    });

    const event = await this.prisma.bookingGroupEvent.update({
      where: { id },
      data: { isCancelled: true, bookedCount: 0 },
      include: EVENT_INCLUDE,
    });

    await this.events.emit('booking.group_event.cancelled', {
      clubId: event.clubId,
      data: { event, affectedRecordsCount: affectedRecords.count },
      userId,
    });

    return event;
  }

  // ─── Repeat (series) ───────────────────────────────────────

  async repeat(id: string, dto: RepeatGroupEventDto, _userId?: string) {
    const source = await this.findById(id);
    if (source.isCancelled) {
      throw new BadRequestException('Cannot repeat a cancelled event');
    }

    const seriesId = source.seriesId ?? randomUUID();
    const repeatUntil = new Date(dto.repeatUntil);
    const events: { startAt: Date; endAt: Date }[] = [];

    // Mark the source with seriesId if not yet
    if (!source.seriesId) {
      await this.prisma.bookingGroupEvent.update({
        where: { id },
        data: { seriesId },
      });
    }

    // Calculate time from source event
    const sourceStart = new Date(source.startAt);
    const sourceEnd = new Date(source.endAt);
    const startHour = sourceStart.getUTCHours();
    const startMin = sourceStart.getUTCMinutes();
    const durationMs = sourceEnd.getTime() - sourceStart.getTime();

    // Generate events for each week
    const current = new Date(sourceStart);
    current.setDate(current.getDate() + 1); // start from next day

    while (current <= repeatUntil) {
      const currentDow = current.getDay() === 0 ? 6 : current.getDay() - 1; // Mon=0..Sun=6

      if (dto.daysOfWeek.includes(currentDow)) {
        const eventStart = new Date(current);
        eventStart.setUTCHours(startHour, startMin, 0, 0);
        const eventEnd = new Date(eventStart.getTime() + durationMs);

        // Check if event already exists for this date/time
        const exists = await this.prisma.bookingGroupEvent.findFirst({
          where: {
            clubId: source.clubId,
            serviceId: source.serviceId,
            staffId: source.staffId,
            startAt: eventStart,
            isCancelled: false,
          },
        });

        if (!exists) {
          const created = await this.prisma.bookingGroupEvent.create({
            data: {
              clubId: source.clubId,
              serviceId: source.serviceId,
              staffId: source.staffId,
              roomId: source.roomId,
              title: source.title,
              startAt: eventStart,
              endAt: eventEnd,
              capacity: source.capacity,
              seriesId,
            },
          });
          events.push(created);
        }
      }

      current.setDate(current.getDate() + 1);
    }

    return { seriesId, created: events.length, events };
  }

  // ─── Cancel series ──────────────────────────────────────────

  async cancelSeries(seriesId: string, fromDate: Date, _userId?: string) {
    const result = await this.prisma.bookingGroupEvent.updateMany({
      where: {
        seriesId,
        startAt: { gte: fromDate },
        isCancelled: false,
      },
      data: { isCancelled: true },
    });

    return { cancelled: result.count };
  }
}
