import { EntityManager, Repository } from 'typeorm';
import { AppDataSource } from '../index';
import { Booking, BookingStatus } from '../entities/Booking.entity';

export class BookingRepository {
  private repo: Repository<Booking>;

  constructor() {
    this.repo = AppDataSource.getRepository(Booking);
  }

  private getRepo(manager?: EntityManager): Repository<Booking> {
    return manager ? manager.getRepository(Booking) : this.repo;
  }

  findById(id: string): Promise<Booking | null> {
    return this.repo.findOne({
      where: { id },
      relations: ['eventType', 'user'],
    });
  }

  findByIdempotencyKey(key: string): Promise<Booking | null> {
    return this.repo.findOne({ where: { idempotencyKey: key } });
  }

  findOverlapping(
    userId: string,
    start: Date,
    end: Date,
    manager?: EntityManager,
    excludeBookingId?: string,
  ): Promise<Booking[]> {
    const qb = this.getRepo(manager)
      .createQueryBuilder('b')
      .where('b.user_id = :userId', { userId })
      .andWhere('b.status IN (:...statuses)', { statuses: ['pending', 'confirmed'] })
      .andWhere('b.start_time < :end', { end })
      .andWhere('b.end_time > :start', { start });

    if (excludeBookingId) {
      qb.andWhere('b.id != :excludeBookingId', { excludeBookingId });
    }

    if (manager) {
      qb.setLock('pessimistic_write');
    }

    return qb.getMany();
  }

  findByUserInRange(
    userId: string,
    from?: Date,
    to?: Date,
    status?: BookingStatus,
    page = 1,
    limit = 20,
  ): Promise<[Booking[], number]> {
    const qb = this.repo
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.eventType', 'et')
      .where('b.user_id = :userId', { userId });

    if (status) qb.andWhere('b.status = :status', { status });
    if (from) qb.andWhere('b.start_time >= :from', { from });
    if (to) qb.andWhere('b.start_time <= :to', { to });

    qb.orderBy('b.startTime', 'ASC');
    qb.skip((page - 1) * limit).take(limit);

    return qb.getManyAndCount();
  }

  findConfirmedInRange(userId: string, from: Date, to: Date): Promise<Booking[]> {
    return this.repo
      .createQueryBuilder('b')
      .where('b.user_id = :userId', { userId })
      .andWhere('b.status IN (:...statuses)', { statuses: ['pending', 'confirmed'] })
      .andWhere('b.start_time < :to', { to })
      .andWhere('b.end_time > :from', { from })
      .getMany();
  }

  create(data: Partial<Booking>, manager?: EntityManager): Promise<Booking> {
    return this.getRepo(manager).save(this.getRepo(manager).create(data));
  }

  async update(id: string, data: Partial<Booking>, manager?: EntityManager): Promise<Booking> {
    await this.getRepo(manager).update(id, data);
    const booking = await this.getRepo(manager).findOne({
      where: { id },
      relations: ['eventType', 'user'],
    });
    if (!booking) throw new Error('Booking not found');
    return booking;
  }

  findUpcomingForReminders(from: Date, to: Date): Promise<Booking[]> {
    return this.repo
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.eventType', 'et')
      .leftJoinAndSelect('b.user', 'u')
      .where('b.status = :status', { status: 'confirmed' })
      .andWhere('b.start_time >= :from', { from })
      .andWhere('b.start_time <= :to', { to })
      .getMany();
  }

  findForReminderWindow(
    hoursBefore: 24 | 1,
    windowMinutes = 30,
  ): Promise<Booking[]> {
    const target = new Date(Date.now() + hoursBefore * 60 * 60 * 1000);
    const from = new Date(target.getTime() - windowMinutes * 60 * 1000);
    const to = new Date(target.getTime() + windowMinutes * 60 * 1000);
    const sentColumn = hoursBefore === 24 ? 'b.reminder_24h_sent_at' : 'b.reminder_1h_sent_at';

    return this.repo
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.eventType', 'et')
      .leftJoinAndSelect('b.user', 'u')
      .where('b.status = :status', { status: 'confirmed' })
      .andWhere('b.start_time >= :from', { from })
      .andWhere('b.start_time <= :to', { to })
      .andWhere(`${sentColumn} IS NULL`)
      .getMany();
  }

  findPastMeetingsNeedingSurvey(hoursAfterEnd = 1, windowMinutes = 30): Promise<Booking[]> {
    const target = new Date(Date.now() - hoursAfterEnd * 60 * 60 * 1000);
    const from = new Date(target.getTime() - windowMinutes * 60 * 1000);
    const to = new Date(target.getTime() + windowMinutes * 60 * 1000);

    return this.repo
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.eventType', 'et')
      .leftJoinAndSelect('b.user', 'u')
      .where('b.status = :status', { status: 'confirmed' })
      .andWhere('b.end_time >= :from', { from })
      .andWhere('b.end_time <= :to', { to })
      .andWhere('b.post_meeting_survey_sent_at IS NULL')
      .getMany();
  }

  findPastMeetingsNeedingFollowUp(daysAfter = 1): Promise<Booking[]> {
    const cutoff = new Date(Date.now() - daysAfter * 24 * 60 * 60 * 1000);

    return this.repo
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.eventType', 'et')
      .leftJoinAndSelect('b.user', 'u')
      .where('b.status = :status', { status: 'confirmed' })
      .andWhere('b.end_time <= :cutoff', { cutoff })
      .andWhere('b.follow_up_sent_at IS NULL')
      .andWhere('b.guest_rating IS NULL')
      .andWhere('(b.meeting_outcome IS NULL OR b.meeting_outcome != :noShow)', { noShow: 'no_show' })
      .getMany();
  }
}
