import {
  BadRequestException,
  forwardRef,
  Inject,
  Injectable,
  Logger,
} from '@nestjs/common';
import { CreateBookingSlotDto } from './dto/create-booking-slot.dto';
import { UpdateBookingSlotDto } from './dto/update-booking-slot.dto';
import { AuthPayload } from 'src/auth/decorators/auth.payload';
import { PrismaService } from 'src/prisma/prisma.service';
import {
  SLOT_BOOKING_EXISTS,
  SLOT_BOOKING_FULL,
  SLOT_BOOKING_NOTFOUND,
} from 'src/share/eCode';
import { SlotService } from '../slot/slot.service';
import { HistoryBookingSlot } from './dto/search-booking.dto';

@Injectable()
export class BookingSlotService {
  private readonly LOG = new Logger(BookingSlotService.name);

  constructor(
    private prisma: PrismaService,
    @Inject(forwardRef(() => SlotService))
    private readonly slotService: SlotService,
  ) {}

  public async countBookingActiveByGroup(slots: number[]) {
    const now = new Date();
    const results = await this.prisma.booking.groupBy({
      by: ['slot_id'],
      _count: {
        _all: true,
      },
      where: {
        slot_id: {
          in: slots,
        },
        status: 'done',
        start_at: {
          lte: now,
        },
        end_at: {
          gte: now,
        },
      },
    });
    const mapIdToCount = {};
    for (const iterator of results) {
      const slot_id = iterator.slot_id;
      const all = iterator._count._all;
      mapIdToCount[slot_id] = all;
    }
    return mapIdToCount;
  }

  public async countBookingActive(slotId: number) {
    const now = new Date();
    const count = await this.prisma.booking.count({
      where: {
        slot_id: slotId,
        status: 'done',
        start_at: {
          lte: now,
        },
        end_at: {
          gte: now,
        },
        slot: {
          id: slotId,
          deleted_at: null,
        },
      },
    });
    this.LOG.debug(`${now}slot :${slotId} exits ${count} booking`);
    return count;
  }

  public async checkBookingSlot(bookingId: number, user_id?: number) {
    const query = { id: bookingId };
    if (user_id) query['user_id'] = user_id;
    const booking = await this.prisma.booking.findFirst({
      where: query,
    });
    if (!booking) {
      throw new BadRequestException(SLOT_BOOKING_EXISTS);
    }
    return booking;
  }

  async create(data: CreateBookingSlotDto, user: AuthPayload) {
    // Check slot
    const slot = await this.slotService.getSlot(+data.slotId);
    const result = await this.prisma.booking.create({
      data: {
        start_at: data.startAt,
        user_id: +user.id,
        slot_id: slot.id,
        contact: data.contact,
        end_at: data.endAt,
        owner: data.owner,
        license_plates: data.licensePlates,
        slot_type: data.slot_type,
      },
    });
    return result;
  }
  // isolation need
  async submitMyBooking(id: number, user: AuthPayload) {
    return this.prisma.$transaction(async (trans) => {
      const booking = await this.checkBookingSlot(id, +user.id);
      const slotBooking = await this.slotService.getSlot(booking.slot_id);
      const totalSlot = await this.countBookingActive(slotBooking.id);
      if (totalSlot >= slotBooking.total) {
        throw new BadRequestException(SLOT_BOOKING_FULL);
      }
      if (booking.status == 'pending') {
        await trans.booking.update({
          data: {
            status: 'done',
          },
          where: {
            id: booking.id,
          },
        });
      }
      return `This action submit a #${id} bookingSlot`;
    });
  }

  async outMyBooking(id: number, user: AuthPayload) {
    return this.prisma.$transaction(async (trans) => {
      const booking = await this.checkBookingSlot(id, +user.id);

      if (booking.status !== 'done') {
        await trans.booking.update({
          data: {
            status: 'out',
          },
          where: {
            id: booking.id,
          },
        });
        return `This action submit a #${id} bookingSlot`;
      }
      throw new BadRequestException();
    });
  }
  async findOne(id: number, user: AuthPayload) {
    const booking = await this.prisma.booking.findFirst({
      where: {
        id,
        user_id: +user.id,
      },
      include: {
        slot: true,
      },
    });
    if (!booking) {
      throw new BadRequestException(SLOT_BOOKING_NOTFOUND);
    }
    this.LOG.debug(`Find booking by id: ${id} ${JSON.stringify(booking)} `);
    return booking;
  }

  async update(
    id: number,
    updateBookingSlotDto: UpdateBookingSlotDto,
    user: AuthPayload,
  ) {
    const booking = await this.findOne(id, user);
    if (booking.status == 'done') {
      return `This action updates a #${id} block`;
    }
    const result = await this.prisma.$transaction([
      this.prisma.booking.update({
        where: {
          id,
        },
        data: {
          owner: updateBookingSlotDto.owner || booking.owner,
          start_at: updateBookingSlotDto.startAt || booking.start_at,
          end_at: updateBookingSlotDto.endAt || booking.end_at,
          license_plates:
            updateBookingSlotDto.licensePlates || booking.license_plates,
        },
      }),
    ]);

    this.LOG.debug(result);
    return `This action updates a #${id} bookingSlot`;
  }

  async cancel(id: number, user: AuthPayload) {
    return this.prisma.$transaction(async (trans) => {
      const booking = await this.checkBookingSlot(id, +user.id);

      if (booking.status !== 'done') {
        await trans.booking.update({
          data: {
            status: 'cancel',
          },
          where: {
            id: booking.id,
          },
        });
        return `This action cancel a #${id} bookingSlot`;
      } else {
        await trans.booking.update({
          data: {
            status: 'reject',
          },
          where: {
            id: booking.id,
          },
        });
        return `This action reject a #${id} bookingSlot`;
      }
    });
  }

  async test() {
    return this.countBookingActiveByGroup([1]);
  }

  async historyBooking(query: HistoryBookingSlot, user: AuthPayload) {
    const { pageSize, pageNumber, sort } = query;
    const take = pageSize;
    const skip = (pageNumber - 1) * pageSize;
    const condition = {
      where: {
        user_id: +user.id,
      },
      skip,
      take,
      orderBy: sort,
    };

    const [bookings, total] = await this.prisma.$transaction([
      this.prisma.booking.findMany(condition),
      this.prisma.booking.count({ where: condition.where }),
    ]);
    return { bookings, total };
  }
}
