import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { Role } from '../role/entities/role.entity';
import { Permission } from '../permission/entities/permission.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User, 'scheme_dbs')
    private readonly userRepository: Repository<User>,

    @InjectRepository(Role, 'scheme_dbs')
    private readonly roleRepository: Repository<Role>,

    @InjectRepository(Permission, 'scheme_dbs')
    private readonly permissionRepository: Repository<Permission>,
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto);
    return this.userRepository.save(user);
  }

  async findAll(): Promise<User[]> {
    return this.userRepository.find({ relations: ['roles', 'permissions'] });
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.password')
      .addSelect('user.isActive')
      .addSelect('user.otpCode')
      .addSelect('user.otpExpires')
      .addSelect('user.otpAttempts')
      .addSelect('user.otpType')
      .addSelect('user.isEmailVerified')
      .addSelect('user.isApprovedByAdmin')
      .addSelect('user.status')
      .leftJoinAndSelect('user.roles', 'roles')
      .leftJoinAndSelect('user.permissions', 'permissions') // ✅ kept
      .where('user.id = :id', { id })
      .getOne();
    if (!user) throw new NotFoundException(`User with ID ${id} not found`);
    return user;
  }

  async update(
    id: number,
    updateUserDto: Partial<
      CreateUserDto & {
        isActive?: boolean;
        otpCode?: string | null; // ✅ allow null
        otpExpires?: Date | null; // ✅ allow null
        otpAttempts?: number;
        otpType?: string | null; // ✅ allow null
        isEmailVerified?: boolean;
        isApprovedByAdmin?: boolean;
        status?: string;
      }
    >,
  ): Promise<User> {
    const user = await this.findOne(id);
    // const user = await this.userRepository.findOne({ where: { id } });
    if (!user) throw new NotFoundException(`User with ID ${id} not found`);
    Object.assign(user, updateUserDto);
    return this.userRepository.save(user);
  }

  async remove(id: number): Promise<void> {
    const user = await this.userRepository.findOne({ where: { id } });
    if (!user) throw new NotFoundException(`User with ID ${id} not found`);
    await this.userRepository.remove(user);
  }

  async findByEmail(email: string): Promise<User | undefined> {
    const user = await this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.password')
      .addSelect('user.isActive')
      .addSelect('user.otpCode')
      .addSelect('user.otpExpires')
      .addSelect('user.otpAttempts')
      .addSelect('user.otpType')
      .addSelect('user.isEmailVerified')
      .addSelect('user.isApprovedByAdmin')
      .addSelect('user.status')
      .leftJoinAndSelect('user.roles', 'roles')
      .leftJoinAndSelect('user.permissions', 'permissions') // ✅ added back
      .where('user.email = :email', { email })
      .getOne();
    return user ?? undefined;
  }

  // ✅ NEW — find user by OTP code + type
  // findByOtp
  async findByOtp(otpCode: string, otpType: string): Promise<User | undefined> {
    const user = await this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.otpCode')
      .addSelect('user.otpType')
      .addSelect('user.otpExpires')
      .addSelect('user.otpAttempts')
      .addSelect('user.isEmailVerified')
      .addSelect('user.isApprovedByAdmin')
      .addSelect('user.status')
      .addSelect('user.isActive')
      .leftJoinAndSelect('user.roles', 'roles')
      .leftJoinAndSelect('user.permissions', 'permissions') // ✅
      .where('user.otpCode = :otpCode', { otpCode })
      .andWhere('user.otpType = :otpType', { otpType })
      .getOne();
    return user ?? undefined;
  }

  // findPendingUsers
  async findPendingUsers(): Promise<User[]> {
    return this.userRepository
      .createQueryBuilder('user')
      .addSelect('user.isEmailVerified')
      .addSelect('user.isApprovedByAdmin')
      .addSelect('user.status')
      .addSelect('user.isActive')
      .leftJoinAndSelect('user.roles', 'roles')
      .leftJoinAndSelect('user.permissions', 'permissions') // ✅
      .where('user.isEmailVerified = :verified', { verified: true })
      .andWhere('user.isApprovedByAdmin = :approved', { approved: false })
      .andWhere('user.status = :status', { status: 'pending' })
      .getMany();
  }

  async assignRolesToUser(userId: number, roleIds: number[]): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });
    if (!user) throw new Error('User not found');
    const roles = await this.roleRepository.findByIds(roleIds);
    user.roles = roles;
    return this.userRepository.save(user);
  }

  async assignPermissionsToUser(
    userId: number,
    permissionIds: number[],
  ): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['permissions'],
    });
    if (!user) throw new Error('User not found');
    const permissions =
      await this.permissionRepository.findByIds(permissionIds);
    user.permissions = permissions;
    return this.userRepository.save(user);
  }
}
