import {
  Injectable,
  NotFoundException,
  BadRequestException,
  forwardRef,
  Inject,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { User } from './user.entity';
import { SortDirection, UserRole } from 'common/enums/user.enum';
import { CreateUserDto } from './dto/create-user.dto';
import { LeaveBalance } from '../leave/leave-balance.entity';
import { LeaveType } from 'common/enums/leaves.enum';
import { UpdateUserDto } from './dto/update-user.dto';
import { AttendanceMethod } from 'common/enums/attendance-method.enum';
import { PaginationQueryDto } from 'common/dto/pagination-query.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { ILike, Not } from 'typeorm';
import { LeaveService } from 'src/leave/leave.service';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(LeaveBalance)
    private leaveBalanceRepository: Repository<LeaveBalance>,

    @Inject(forwardRef(() => LeaveService))
    private leaveService: LeaveService,
  ) {}

  async findAll(query: PaginationQueryDto, currentUserId?: number) {
    const {
      page = 1,
      limit = 10,
      sortBy = 'createdAt',
      sortDirection = 'DESC',
      search,
      attendanceStatus,
    } = query;

    const skip = (page - 1) * limit;

    const where: any = search
      ? [
          { name: ILike(`%${search}%`), id: Not(currentUserId) },
          { email: ILike(`%${search}%`), id: Not(currentUserId) },
        ]
      : { id: Not(currentUserId) };

    let usersQuery = this.userRepository
      .createQueryBuilder('user')
      .leftJoinAndSelect('user.attendanceRecords', 'attendance')
      .where(where);

    if (attendanceStatus) {
      const todayStart = new Date();
      todayStart.setHours(0, 0, 0, 0);
      const todayEnd = new Date();
      todayEnd.setHours(23, 59, 59, 999);

      if (attendanceStatus === 'CHECKIN') {
        usersQuery = usersQuery.andWhere(
          'attendance.checkIn BETWEEN :start AND :end',
          { start: todayStart, end: todayEnd },
        );
      } else if (attendanceStatus === 'CHECKOUT') {
        usersQuery = usersQuery.andWhere(
          'attendance.checkOut BETWEEN :start AND :end',
          { start: todayStart, end: todayEnd },
        );
      } else if (attendanceStatus === 'ABSENT') {
        usersQuery = usersQuery.andWhere(
          'attendance.id IS NULL OR attendance.checkIn NOT BETWEEN :start AND :end',
          { start: todayStart, end: todayEnd },
        );
      }
    }

    // Pagination + Sorting
    const [data, total] = await usersQuery
      .orderBy(`user.${sortBy}`, sortDirection as 'ASC' | 'DESC')
      .take(limit)
      .skip(skip)
      .getManyAndCount();

    return {
      data,
      total,
      page,
      lastPage: Math.ceil(total / limit),
    };
  }

  async updateProfile(userId: number, dto: UpdateProfileDto): Promise<User> {
    const user = await this.findOne(userId);

    Object.assign(user, dto);

    return this.userRepository.save(user);
  }

  async changePassword(
    userId: number,
    dto: ChangePasswordDto,
  ): Promise<{ message: string }> {
    const user = await this.findOne(userId);

    const isMatch = await bcrypt.compare(dto.currentPassword, user.password);
    if (!isMatch) {
      throw new BadRequestException('Current password is incorrect');
    }

    const hashedPassword = await bcrypt.hash(dto.newPassword, 10);

    user.password = hashedPassword;
    await this.userRepository.save(user);

    return { message: 'Password updated successfully' };
  }

  async findOneByEmail(email: string): Promise<User | null> {
    return this.userRepository.findOne({ where: { email } });
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { id },
      relations: [
        'attendanceRecords',
        'leaveRequests',
        'leaveBalances',
        'notifications',
      ],
    });
    if (!user) throw new NotFoundException('User not found');
    return user;
  }

  async create(createUserDto: CreateUserDto): Promise<User> {
    const existing = await this.userRepository.findOne({
      where: { email: createUserDto.email },
    });

    if (existing) throw new BadRequestException('Email already exists');

    const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
    const user = this.userRepository.create({
      ...createUserDto,
      password: hashedPassword,
      role: createUserDto.role || UserRole.EMPLOYEE,
      attendanceMethod: createUserDto.attendanceMethod || AttendanceMethod.GPS,
    });

    const savedUser = await this.userRepository.save(user);

    await this.leaveService.assignInitialLeave(savedUser);

    const { password, ...userWithoutPassword } = savedUser;
    return userWithoutPassword as User;
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);

    if (updateUserDto.email && updateUserDto.email !== user.email) {
      const existing = await this.userRepository.findOne({
        where: { email: updateUserDto.email },
      });

      if (existing) {
        throw new BadRequestException('Email already exists');
      }
    }

    const oldEmploymentType = user.employmentType;

    Object.assign(user, updateUserDto);
    const updatedUser = await this.userRepository.save(user);

    if (
      updateUserDto.employmentType &&
      updateUserDto.employmentType !== oldEmploymentType
    ) {
      await this.leaveService.assignInitialLeave(updatedUser);
    }

    return updatedUser;
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.userRepository.remove(user);
  }

  async removeBulk(ids: number[]): Promise<void> {
    if (!ids || ids.length === 0) return;
    await this.userRepository.delete(ids);
  }
}
