import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan, MoreThan } from 'typeorm';
import { CsvBooking } from '@domain/entities/csv-booking.entity';
import { CsvBookingRepositoryPort } from '@domain/ports/out/csv-booking.repository';
import { CsvBookingOrmEntity } from '../entities/csv-booking.orm-entity';
import { CsvBookingMapper } from '../mappers/csv-booking.mapper';

/**
 * TypeORM CSV Booking Repository
 *
 * Implementation of CsvBookingRepositoryPort using TypeORM
 */
@Injectable()
export class TypeOrmCsvBookingRepository implements CsvBookingRepositoryPort {
  private readonly logger = new Logger(TypeOrmCsvBookingRepository.name);

  constructor(
    @InjectRepository(CsvBookingOrmEntity)
    private readonly repository: Repository<CsvBookingOrmEntity>
  ) {}

  async create(booking: CsvBooking): Promise<CsvBooking> {
    this.logger.log(`Creating CSV booking: ${booking.id}`);

    const ormEntity = CsvBookingMapper.toOrmCreate(booking);
    const saved = await this.repository.save(ormEntity);

    this.logger.log(`CSV booking created successfully: ${saved.id}`);
    return CsvBookingMapper.toDomain(saved);
  }

  async findById(id: string): Promise<CsvBooking | null> {
    this.logger.log(`Finding CSV booking by ID: ${id}`);

    const ormEntity = await this.repository.findOne({
      where: { id },
    });

    if (!ormEntity) {
      this.logger.log(`CSV booking not found: ${id}`);
      return null;
    }

    return CsvBookingMapper.toDomain(ormEntity);
  }

  async findByToken(token: string): Promise<CsvBooking | null> {
    this.logger.log(`Finding CSV booking by token: ${token}`);

    const ormEntity = await this.repository.findOne({
      where: { confirmationToken: token },
    });

    if (!ormEntity) {
      this.logger.log(`CSV booking not found for token: ${token}`);
      return null;
    }

    return CsvBookingMapper.toDomain(ormEntity);
  }

  async findAll(): Promise<CsvBooking[]> {
    this.logger.log(`Finding ALL CSV bookings from database`);

    const ormEntities = await this.repository.find({
      order: { requestedAt: 'DESC' },
    });

    this.logger.log(`Found ${ormEntities.length} CSV bookings in total`);
    return CsvBookingMapper.toDomainArray(ormEntities);
  }

  async findByUserId(userId: string): Promise<CsvBooking[]> {
    this.logger.log(`Finding CSV bookings for user: ${userId}`);

    const ormEntities = await this.repository.find({
      where: { userId },
      order: { requestedAt: 'DESC' },
    });

    this.logger.log(`Found ${ormEntities.length} CSV bookings for user: ${userId}`);
    return CsvBookingMapper.toDomainArray(ormEntities);
  }

  async findByOrganizationId(organizationId: string): Promise<CsvBooking[]> {
    this.logger.log(`Finding CSV bookings for organization: ${organizationId}`);

    const ormEntities = await this.repository.find({
      where: { organizationId },
      order: { requestedAt: 'DESC' },
    });

    this.logger.log(`Found ${ormEntities.length} CSV bookings for organization: ${organizationId}`);
    return CsvBookingMapper.toDomainArray(ormEntities);
  }

  async findByStatus(status: string): Promise<CsvBooking[]> {
    this.logger.log(`Finding CSV bookings with status: ${status}`);

    const ormEntities = await this.repository.find({
      where: { status: status as any },
      order: { requestedAt: 'DESC' },
    });

    this.logger.log(`Found ${ormEntities.length} CSV bookings with status: ${status}`);
    return CsvBookingMapper.toDomainArray(ormEntities);
  }

  async findExpiringSoon(daysUntilExpiration: number): Promise<CsvBooking[]> {
    this.logger.log(`Finding CSV bookings expiring in ${daysUntilExpiration} days`);

    const expirationDate = new Date();
    expirationDate.setDate(expirationDate.getDate() + daysUntilExpiration);

    // Find pending bookings requested between 7 days ago and (7 - daysUntilExpiration) days ago
    const minRequestDate = new Date();
    minRequestDate.setDate(minRequestDate.getDate() - 7);

    const maxRequestDate = new Date();
    maxRequestDate.setDate(maxRequestDate.getDate() - (7 - daysUntilExpiration));

    const ormEntities = await this.repository.find({
      where: {
        status: 'PENDING',
        requestedAt: LessThan(maxRequestDate) && MoreThan(minRequestDate),
      },
      order: { requestedAt: 'ASC' },
    });

    this.logger.log(
      `Found ${ormEntities.length} CSV bookings expiring in ${daysUntilExpiration} days`
    );
    return CsvBookingMapper.toDomainArray(ormEntities);
  }

  async update(booking: CsvBooking): Promise<CsvBooking> {
    this.logger.log(`Updating CSV booking: ${booking.id}`);

    const existing = await this.repository.findOne({
      where: { id: booking.id },
    });

    if (!existing) {
      throw new Error(`CSV booking not found: ${booking.id}`);
    }

    const updates = CsvBookingMapper.toOrmUpdate(booking);
    Object.assign(existing, updates);

    const saved = await this.repository.save(existing);

    this.logger.log(`CSV booking updated successfully: ${saved.id}`);
    return CsvBookingMapper.toDomain(saved);
  }

  async delete(id: string): Promise<void> {
    this.logger.log(`Deleting CSV booking: ${id}`);

    const result = await this.repository.delete({ id });

    if (result.affected === 0) {
      throw new Error(`CSV booking not found: ${id}`);
    }

    this.logger.log(`CSV booking deleted successfully: ${id}`);
  }

  async countByStatusForUser(userId: string): Promise<Record<string, number>> {
    this.logger.log(`Counting CSV bookings by status for user: ${userId}`);

    const results = await this.repository
      .createQueryBuilder('booking')
      .select('booking.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('booking.userId = :userId', { userId })
      .groupBy('booking.status')
      .getRawMany();

    const counts: Record<string, number> = {
      PENDING: 0,
      ACCEPTED: 0,
      REJECTED: 0,
      CANCELLED: 0,
    };

    results.forEach(result => {
      counts[result.status] = parseInt(result.count, 10);
    });

    this.logger.log(`Counted CSV bookings by status for user ${userId}:`, counts);
    return counts;
  }

  async countByStatusForOrganization(organizationId: string): Promise<Record<string, number>> {
    this.logger.log(`Counting CSV bookings by status for organization: ${organizationId}`);

    const results = await this.repository
      .createQueryBuilder('booking')
      .select('booking.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('booking.organizationId = :organizationId', { organizationId })
      .groupBy('booking.status')
      .getRawMany();

    const counts: Record<string, number> = {
      PENDING: 0,
      ACCEPTED: 0,
      REJECTED: 0,
      CANCELLED: 0,
    };

    results.forEach(result => {
      counts[result.status] = parseInt(result.count, 10);
    });

    this.logger.log(`Counted CSV bookings by status for organization ${organizationId}:`, counts);
    return counts;
  }
}
