import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';

@Injectable()
export class EventsService {
  private readonly logger = new Logger(EventsService.name);

  constructor(private prisma: PrismaService) {}

  async create(createEventDto: CreateEventDto) {
    this.logger.log(`Creating event: ${createEventDto.title}`);

    // Validate VIPs exist
    if (createEventDto.vipIds && createEventDto.vipIds.length > 0) {
      const vips = await this.prisma.vIP.findMany({
        where: {
          id: { in: createEventDto.vipIds },
          deletedAt: null,
        },
      });

      if (vips.length !== createEventDto.vipIds.length) {
        throw new BadRequestException('One or more VIPs not found');
      }
    }

    // Check vehicle capacity if vehicle is assigned
    if (createEventDto.vehicleId && createEventDto.vipIds) {
      await this.checkVehicleCapacity(
        createEventDto.vehicleId,
        createEventDto.vipIds.length,
      );
    }

    // Check for conflicts if driver is assigned
    if (createEventDto.driverId) {
      const conflicts = await this.checkConflicts(
        createEventDto.driverId,
        new Date(createEventDto.startTime),
        new Date(createEventDto.endTime),
      );

      if (conflicts.length > 0) {
        this.logger.warn(
          `Conflict detected for driver ${createEventDto.driverId}`,
        );
        throw new BadRequestException({
          message: 'Driver has conflicting events',
          conflicts: conflicts.map((e) => ({
            id: e.id,
            title: e.title,
            startTime: e.startTime,
            endTime: e.endTime,
          })),
        });
      }
    }

    const event = await this.prisma.scheduleEvent.create({
      data: {
        ...createEventDto,
        startTime: new Date(createEventDto.startTime),
        endTime: new Date(createEventDto.endTime),
      },
      include: {
        driver: true,
        vehicle: true,
      },
    });

    return this.enrichEventWithVips(event);
  }

  async findAll() {
    const events = await this.prisma.scheduleEvent.findMany({
      where: { deletedAt: null },
      include: {
        driver: true,
        vehicle: true,
      },
      orderBy: { startTime: 'asc' },
    });

    return Promise.all(events.map((event) => this.enrichEventWithVips(event)));
  }

  async findOne(id: string) {
    const event = await this.prisma.scheduleEvent.findFirst({
      where: { id, deletedAt: null },
      include: {
        driver: true,
        vehicle: true,
      },
    });

    if (!event) {
      throw new NotFoundException(`Event with ID ${id} not found`);
    }

    return this.enrichEventWithVips(event);
  }

  async update(id: string, updateEventDto: UpdateEventDto) {
    const event = await this.findOne(id);

    // Validate VIPs if being updated
    if (updateEventDto.vipIds && updateEventDto.vipIds.length > 0) {
      const vips = await this.prisma.vIP.findMany({
        where: {
          id: { in: updateEventDto.vipIds },
          deletedAt: null,
        },
      });

      if (vips.length !== updateEventDto.vipIds.length) {
        throw new BadRequestException('One or more VIPs not found');
      }
    }

    // Check vehicle capacity if vehicle or VIPs are being updated
    const vehicleId = updateEventDto.vehicleId || event.vehicleId;
    const vipCount = updateEventDto.vipIds
      ? updateEventDto.vipIds.length
      : event.vipIds.length;

    if (vehicleId && vipCount > 0 && !updateEventDto.forceAssign) {
      await this.checkVehicleCapacity(vehicleId, vipCount);
    }

    // Check for conflicts if driver or times are being updated (unless forceAssign is true)
    if (
      !updateEventDto.forceAssign &&
      (updateEventDto.driverId ||
        updateEventDto.startTime ||
        updateEventDto.endTime)
    ) {
      const driverId = updateEventDto.driverId || event.driverId;
      const startTime = updateEventDto.startTime
        ? new Date(updateEventDto.startTime)
        : event.startTime;
      const endTime = updateEventDto.endTime
        ? new Date(updateEventDto.endTime)
        : event.endTime;

      if (driverId) {
        const conflicts = await this.checkConflicts(
          driverId,
          startTime,
          endTime,
          event.id, // Exclude current event from conflict check
        );

        if (conflicts.length > 0) {
          this.logger.warn(`Conflict detected for driver ${driverId}`);
          throw new BadRequestException({
            message: 'Driver has conflicting events',
            conflicts: conflicts.map((e) => ({
              id: e.id,
              title: e.title,
              startTime: e.startTime,
              endTime: e.endTime,
            })),
          });
        }
      }
    }

    this.logger.log(`Updating event ${id}: ${event.title}`);

    const updateData: any = { ...updateEventDto };
    if (updateEventDto.startTime) {
      updateData.startTime = new Date(updateEventDto.startTime);
    }
    if (updateEventDto.endTime) {
      updateData.endTime = new Date(updateEventDto.endTime);
    }

    // Remove forceAssign from data as it's not a database field
    delete updateData.forceAssign;

    const updatedEvent = await this.prisma.scheduleEvent.update({
      where: { id: event.id },
      data: updateData,
      include: {
        driver: true,
        vehicle: true,
      },
    });

    return this.enrichEventWithVips(updatedEvent);
  }

  async updateStatus(id: string, updateEventStatusDto: UpdateEventStatusDto) {
    const event = await this.findOne(id);

    this.logger.log(
      `Updating event status ${id}: ${event.title} -> ${updateEventStatusDto.status}`,
    );

    const updatedEvent = await this.prisma.scheduleEvent.update({
      where: { id: event.id },
      data: { status: updateEventStatusDto.status },
      include: {
        driver: true,
        vehicle: true,
      },
    });

    return this.enrichEventWithVips(updatedEvent);
  }

  async remove(id: string, hardDelete = false) {
    const event = await this.findOne(id);

    if (hardDelete) {
      this.logger.log(`Hard deleting event: ${event.title}`);
      return this.prisma.scheduleEvent.delete({
        where: { id: event.id },
      });
    }

    this.logger.log(`Soft deleting event: ${event.title}`);
    return this.prisma.scheduleEvent.update({
      where: { id: event.id },
      data: { deletedAt: new Date() },
    });
  }

  /**
   * Check vehicle capacity
   */
  private async checkVehicleCapacity(vehicleId: string, vipCount: number) {
    const vehicle = await this.prisma.vehicle.findFirst({
      where: { id: vehicleId, deletedAt: null },
    });

    if (!vehicle) {
      throw new NotFoundException('Vehicle not found');
    }

    if (vipCount > vehicle.seatCapacity) {
      this.logger.warn(
        `Vehicle capacity exceeded: ${vipCount} VIPs > ${vehicle.seatCapacity} seats`,
      );
      throw new BadRequestException({
        message: `Vehicle capacity exceeded: ${vipCount} VIPs require more than ${vehicle.seatCapacity} available seats`,
        capacity: vehicle.seatCapacity,
        requested: vipCount,
        exceeded: true,
      });
    }
  }

  /**
   * Check for conflicting events for a driver
   */
  private async checkConflicts(
    driverId: string,
    startTime: Date,
    endTime: Date,
    excludeEventId?: string,
  ) {
    return this.prisma.scheduleEvent.findMany({
      where: {
        driverId,
        deletedAt: null,
        id: excludeEventId ? { not: excludeEventId } : undefined,
        OR: [
          {
            // New event starts during existing event
            AND: [
              { startTime: { lte: startTime } },
              { endTime: { gt: startTime } },
            ],
          },
          {
            // New event ends during existing event
            AND: [
              { startTime: { lt: endTime } },
              { endTime: { gte: endTime } },
            ],
          },
          {
            // New event completely contains existing event
            AND: [
              { startTime: { gte: startTime } },
              { endTime: { lte: endTime } },
            ],
          },
        ],
      },
    });
  }

  /**
   * Enrich event with VIP details fetched separately
   * Returns both `vips` array and `vip` (first VIP) for backwards compatibility
   */
  private async enrichEventWithVips(event: any) {
    if (!event.vipIds || event.vipIds.length === 0) {
      return { ...event, vips: [], vip: null };
    }

    const vips = await this.prisma.vIP.findMany({
      where: {
        id: { in: event.vipIds },
        deletedAt: null,
      },
    });

    // Return both vips array and vip (first one) for backwards compatibility
    return { ...event, vips, vip: vips[0] || null };
  }
}
