import {
  Injectable,
  NotFoundException,
  Logger,
  ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DateIvt } from './entities/date-ivt.entity';
import { CreateDateIvtDto, UpdateDateIvtDto } from './dto';

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

  constructor(
    @InjectRepository(DateIvt)
    private readonly dateIvtRepository: Repository<DateIvt>,
  ) {}

  async create(createDateIvtDto: CreateDateIvtDto): Promise<DateIvt> {
    // Convert date string to Date object for comparison
    const newDate = new Date(createDateIvtDto.next_injection_date);
    newDate.setHours(0, 0, 0, 0);

    // Check if an entry already exists with the same combination AND same date
    const existingEntries = await this.dateIvtRepository.find({
      where: {
        questionnaire_id: createDateIvtDto.questionnaire_id,
        phone_number: createDateIvtDto.phone_number,
        first_name: createDateIvtDto.first_name,
      },
    });

    // Check if any existing entry has the same date
    const existingWithSameDate = existingEntries.find((entry) => {
      const entryDate = new Date(entry.next_injection_date);
      entryDate.setHours(0, 0, 0, 0);
      return entryDate.getTime() === newDate.getTime();
    });

    // If exists with same date, update it instead of creating a new one
    if (existingWithSameDate) {
      Object.assign(existingWithSameDate, createDateIvtDto);
      const updatedDateIvt =
        await this.dateIvtRepository.save(existingWithSameDate);

      this.logger.log(
        `Date IVT updated (same date) for phone: ${updatedDateIvt.phone_number}`,
      );
      return updatedDateIvt;
    }

    // Different date: try to create a new entry
    // This should work if the unique constraint includes next_injection_date
    // If it fails with 23505, the database constraint needs to be updated
    try {
      const dateIvt = this.dateIvtRepository.create(createDateIvtDto);
      const savedDateIvt = await this.dateIvtRepository.save(dateIvt);

      this.logger.log(
        `Date IVT created (new date: ${savedDateIvt.next_injection_date}) for phone: ${savedDateIvt.phone_number}`,
      );
      return savedDateIvt;
    } catch (error: unknown) {
      // If unique constraint violation and date is different,
      // it means the DB constraint doesn't include next_injection_date
      if (
        error &&
        typeof error === 'object' &&
        'code' in error &&
        error.code === '23505'
      ) {
        // Double-check: maybe the date was the same after all
        const recheckExisting = existingEntries.find((entry) => {
          const entryDate = new Date(entry.next_injection_date);
          entryDate.setHours(0, 0, 0, 0);
          return entryDate.getTime() === newDate.getTime();
        });

        if (recheckExisting) {
          // Same date, update instead
          Object.assign(recheckExisting, createDateIvtDto);
          return await this.dateIvtRepository.save(recheckExisting);
        }

        // Different date but constraint prevents creation
        // The database unique constraint needs to include next_injection_date
        this.logger.error(
          `Cannot create date IVT with different date due to database constraint. ` +
            `The unique constraint on (questionnaire_id, phone_number, first_name) needs to include next_injection_date.`,
        );
        throw new ConflictException(
          'Cannot create a new date IVT entry with a different date. ' +
            'The database constraint may need to be updated to allow multiple entries with different dates.',
        );
      }
      throw error;
    }
  }

  async findAll(): Promise<DateIvt[]> {
    return await this.dateIvtRepository.find({
      order: { created_at: 'DESC' },
    });
  }

  async findOne(id: number): Promise<DateIvt> {
    const dateIvt = await this.dateIvtRepository.findOne({
      where: { id },
    });

    if (!dateIvt) {
      throw new NotFoundException(`Date IVT with ID ${id} not found`);
    }

    return dateIvt;
  }

  async findByUuid(uuid: string): Promise<DateIvt> {
    const dateIvt = await this.dateIvtRepository.findOne({
      where: { uuid },
    });

    if (!dateIvt) {
      throw new NotFoundException(`Date IVT with UUID ${uuid} not found`);
    }

    return dateIvt;
  }

  async findByPhoneNumber(phoneNumber: string): Promise<DateIvt[]> {
    return await this.dateIvtRepository.find({
      where: { phone_number: phoneNumber },
      order: { created_at: 'DESC' },
    });
  }

  async findByQuestionnaireId(questionnaireId: number): Promise<DateIvt[]> {
    return await this.dateIvtRepository.find({
      where: { questionnaire_id: questionnaireId },
      order: { created_at: 'DESC' },
    });
  }

  async update(
    id: number,
    updateDateIvtDto: UpdateDateIvtDto,
  ): Promise<DateIvt> {
    const dateIvt = await this.findOne(id);

    Object.assign(dateIvt, updateDateIvtDto);
    const updatedDateIvt = await this.dateIvtRepository.save(dateIvt);

    this.logger.log(`Date IVT updated for ID: ${id}`);
    return updatedDateIvt;
  }

  async updateByUuid(
    uuid: string,
    updateDateIvtDto: UpdateDateIvtDto,
  ): Promise<DateIvt> {
    const dateIvt = await this.findByUuid(uuid);

    Object.assign(dateIvt, updateDateIvtDto);
    const updatedDateIvt = await this.dateIvtRepository.save(dateIvt);

    this.logger.log(`Date IVT updated for UUID: ${uuid}`);
    return updatedDateIvt;
  }

  async remove(id: number): Promise<void> {
    const dateIvt = await this.findOne(id);
    await this.dateIvtRepository.remove(dateIvt);
    this.logger.log(`Date IVT deleted for ID: ${id}`);
  }

  async findByDateRange(
    startDate: string,
    endDate: string,
  ): Promise<DateIvt[]> {
    return await this.dateIvtRepository
      .createQueryBuilder('dateIvt')
      .where('dateIvt.next_injection_date >= :startDate', { startDate })
      .andWhere('dateIvt.next_injection_date <= :endDate', { endDate })
      .orderBy('dateIvt.next_injection_date', 'ASC')
      .getMany();
  }
}
