import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Between, Not, Repository } from 'typeorm';
import { Appointment } from './entities/appointment.entity';
import { CreateAppointmentDto } from './dto/create-appointment.dto';
import { UpdateAppointmentDto } from './dto/update-appointment.dto';
import { AppointmentResponseDto } from './dto/appointment-response.dto';
import { UserService } from 'modules/user/users.service';
import { UserRole } from 'common/enum/UserRole.enum';
import { AppointmentStatus } from 'common/enum/appointment.enum';

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

  constructor(
    @InjectRepository(Appointment)
    private readonly appointmentRepository: Repository<Appointment>,
    private readonly userService: UserService,
  ) {}

  async create(createAppointmentDto: CreateAppointmentDto): Promise<AppointmentResponseDto> {
    const timeWithoutAmPm = createAppointmentDto.appointmentTime.replace(/(am|pm)/i, '').trim();
    try {
      const conflicts = await this.findConflictingAppointments(createAppointmentDto.appointmentDate, timeWithoutAmPm, createAppointmentDto.duration);

      if (conflicts.length > 0) {
        throw new HttpException('Time slot is not available', HttpStatus.CONFLICT);
      }

      const appointment = this.appointmentRepository.create({
        appointmentDate: createAppointmentDto.appointmentDate,
        appointmentTime: timeWithoutAmPm,
        duration: createAppointmentDto.duration,
        status: AppointmentStatus.PENDING,
        userId: createAppointmentDto.userId,
        fullName: createAppointmentDto.fullName,
        title: createAppointmentDto.title,
        notes: createAppointmentDto.notes,
        phone: createAppointmentDto.phone,
      });
      return await this.appointmentRepository.save(appointment);
    } catch (error) {
      this.logger.error('Error creating appointment', error);
      throw new HttpException(error.message || 'Could not create appointment', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async findAll(): Promise<AppointmentResponseDto[]> {
    try {
      return await this.appointmentRepository.find();
    } catch (error) {
      this.logger.error('Error fetching appointments', error);
      throw new HttpException('Could not fetch appointments', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async findByUserId(userId: number): Promise<AppointmentResponseDto[]> {
    this.logger.log(`Fetching appointments for user ${userId}`);
    try {
      return await this.appointmentRepository.find({ where: { userId } });
    } catch (error) {
      this.logger.error('Error fetching user appointments', error);
      throw new HttpException('Could not fetch appointments', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async findOne(id: number, userId: number, role: UserRole): Promise<AppointmentResponseDto> {
    try {
      const appointment = await this.appointmentRepository.findOne({ where: { id } });

      if (!appointment) {
        throw new HttpException('Appointment not found', HttpStatus.NOT_FOUND);
      }

      if (!this.isAuthorized(appointment, userId, role)) {
        throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
      }

      return appointment;
    } catch (error) {
      this.logger.error(`Error fetching appointment ${id}`, error);
      throw new HttpException(error.message || 'Could not fetch appointment', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async update(id: number, userId: number, role: UserRole, updateAppointmentDto: UpdateAppointmentDto): Promise<AppointmentResponseDto> {
    this.logger.log(`Updating appointment ${id}`);
    try {
      const appointment = await this.findOne(id, userId, role);
      if (!appointment) {
        this.logger.error(`Appointment ${id} not found`);
        throw new HttpException('Appointment not found', HttpStatus.NOT_FOUND);
      }

      if (role === UserRole.USER && appointment.status !== AppointmentStatus.PENDING) {
        this.logger.error(`User ${userId} cannot update an approved appointment`);
        throw new HttpException('You cannot update an approved appointment', HttpStatus.FORBIDDEN);
      }

      if (updateAppointmentDto.appointmentDate || updateAppointmentDto.appointmentTime) {
        const conflicts = await this.findConflictingAppointments(
          updateAppointmentDto.appointmentDate || appointment.appointmentDate,
          updateAppointmentDto.appointmentTime || appointment.appointmentTime,
          updateAppointmentDto.duration || appointment.duration,
          id,
        );

        if (conflicts.length > 0) {
          throw new HttpException('Time slot is not available', HttpStatus.CONFLICT);
        }
      }

      Object.assign(appointment, updateAppointmentDto);
      this.logger.log(`Updated appointment ${id}`);
      return await this.appointmentRepository.save(appointment);
    } catch (error) {
      this.logger.error(`Error updating appointment ${id}`, error);
      throw new HttpException(error.message || 'Could not update appointment', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async remove(id: number, userId: number, role: UserRole): Promise<{ success: boolean; message: string }> {
    try {
      const appointment = await this.findOne(id, userId, role);
      if (!appointment) {
        throw new HttpException('Appointment not found', HttpStatus.NOT_FOUND);
      }

      await this.appointmentRepository.remove(appointment);
      return { success: true, message: 'Appointment deleted successfully' };
    } catch (error) {
      this.logger.error(`Error deleting appointment ${id}`, error);
      throw new HttpException(error.message || 'Could not delete appointment', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  private isAuthorized(appointment: Appointment, userId: number, role: UserRole): boolean {
    return appointment.userId === userId || [UserRole.ADMIN, UserRole.SUPER_ADMIN].includes(role);
  }

  private async findConflictingAppointments(date: Date, time: string, duration: number, excludeId?: number): Promise<Appointment[]> {
    const startTime = new Date(`${date.toISOString().split('T')[0]}T${time}:00`);
    const endTime = new Date(startTime.getTime() + duration * 60000);

    return await this.appointmentRepository.find({
      where: {
        appointmentDate: date,
        status: AppointmentStatus.APPROVED,
        appointmentTime: Between(startTime.toISOString().split('T')[1], endTime.toISOString().split('T')[1]),
        ...(excludeId && { id: Not(excludeId) }),
      },
    });
  }

  async getAvailableTimeSlots(date: Date): Promise<string[]> {
    if (isNaN(date.getTime())) {
      throw new HttpException('Invalid date provided', HttpStatus.BAD_REQUEST);
    }

    const startHour = 11;
    const endHour = 16;
    const slotDuration = 30;
    const allSlots: string[] = [];

    for (let hour = startHour; hour < endHour; hour++) {
      for (let minute = 0; minute < 60; minute += slotDuration) {
        allSlots.push(`${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`);
      }
    }

    const existingAppointments = await this.appointmentRepository.find({
      where: { appointmentDate: date, status: AppointmentStatus.APPROVED },
    });

    const busySlots = new Set(existingAppointments.map(a => a.appointmentTime));
    return allSlots.filter(slot => !busySlots.has(slot));
  }
}
