import { HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
import { AdminService } from 'src/admin/admin.service';
import { JwtService } from '@nestjs/jwt';
import { EncryptionService } from '../encryption/encryption.service';
import { CreateAdminDto } from 'src/admin/dto/create-admin.dto';
import { LoginAdminDto } from 'src/auth/admin/dto/login-admin.dto';
import { JwtConstants } from 'src/constants/jwt.constants';
import { AdminSigninResDto } from './dto/admin-signin-res.dto';
import { Admin } from 'src/admin/entities/admin.entity';
import { ADMIN_ROLE } from 'src/shared/enum/admin-role.enum';
import { PayloadJwtDto } from 'src/shared/dto/payload-jwt.dto';
import { UpdatePwdDto } from '../user/dto/update-pwd.dto';
import { randomInt } from 'crypto';
import { InjectRepository } from '@nestjs/typeorm';
import { AdminResetCode } from './entitites/admin-reset-code.entity';
import { Repository } from 'typeorm';
import { AdminResetPasswordDto } from './dto/admin-reset-password.dto';

@Injectable()
export class AuthAdminService {
  constructor(
    @InjectRepository(AdminResetCode) private adminResetCodeRepository: Repository<AdminResetCode>,
    private adminService: AdminService,
    private jwtService: JwtService,
    private encryptionService: EncryptionService,
  ) {}

  async signUp(createAdminDto: CreateAdminDto): Promise<string> {
    const loginAdminDto: LoginAdminDto = {
      email: createAdminDto.email,
      password: createAdminDto.password,
    };
    const hashedPwd = await this.encryptionService.hashPassword(createAdminDto.password);
    createAdminDto.password = hashedPwd;
    if (await this.adminService.adminExists(createAdminDto.email)) {
      throw new HttpException('Admin already exists', HttpStatus.UNAUTHORIZED);
    }
    await this.adminService.create(createAdminDto);

    const adminSigninResDto: AdminSigninResDto = await this.signIn(loginAdminDto);
    return adminSigninResDto.token;
  }

  async signIn(logInAdmin: LoginAdminDto): Promise<AdminSigninResDto> {
    const admin: Admin = await this.adminService.findOne(logInAdmin.email);
    const validPwd: boolean = await this.encryptionService.comparePassword(logInAdmin.password, admin.password);
    if (!validPwd) {
      throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
    }
    const accessToken = await this.jwtService.sign(
      { email: admin.email, role: admin.role },
      { secret: JwtConstants.SECRET },
    );
    const adminSigninResDto: AdminSigninResDto = {
      email: admin.email,
      name: admin.name,
      role: admin.role as ADMIN_ROLE,
      token: accessToken,
    };
    return adminSigninResDto;
  }

  async validateToken(token: string): Promise<PayloadJwtDto> {
    try {
      const payload: PayloadJwtDto = await this.jwtService.verify(token, { secret: JwtConstants.SECRET });
      return payload;
    } catch (error) {
      throw new UnauthorizedException('Invalid token');
    }
  }

  async changePassword(email: string, updatePwdDto: UpdatePwdDto): Promise<void> {
    const admin: Admin = await this.adminService.findOne(email);
    const validPwd: boolean = await this.encryptionService.comparePassword(updatePwdDto.prevPassword, admin.password);
    if (!validPwd) {
      throw new HttpException('Invalid password', HttpStatus.UNAUTHORIZED);
    }
    const hashedPwd = await this.encryptionService.hashPassword(updatePwdDto.newPassword);
    await this.adminService.updatePassword(email, hashedPwd);
  }

  async getResetPasswordCode(email: string): Promise<string> {
    const admin: Admin = await this.adminService.findOne(email);
    if (!admin) throw new UnauthorizedException('Invalid email');

    const resetCode: string = randomInt(100000, 999999).toString();

    const expirationDate: Date = new Date();
    expirationDate.setMinutes(expirationDate.getMinutes() + 10);

    const existUserCode: AdminResetCode[] = await this.adminResetCodeRepository.findBy({
      admin: admin,
      code: resetCode,
    });

    if (existUserCode.length > 0) {
      await this.adminResetCodeRepository.delete(existUserCode[0].id);
    }
    await this.adminResetCodeRepository.save({ admin, code: resetCode, expirationDate });
    return resetCode;
  }

  async resetPassword(adminResetPasswordDto: AdminResetPasswordDto): Promise<void> {
    const admin: Admin = await this.adminService.findOne(adminResetPasswordDto.email);
    if (!admin) throw new UnauthorizedException('Invalid email');

    const adminResetCode: AdminResetCode[] = await this.adminResetCodeRepository.findBy({
      admin: admin,
      code: adminResetPasswordDto.resetCode,
    });

    if (adminResetCode.length === 0) throw new UnauthorizedException('Invalid code');
    if (adminResetCode[0].expirationDate < new Date()) throw new UnauthorizedException('Code expired');

    const hashedPwd = await this.encryptionService.hashPassword(adminResetPasswordDto.newPassword);
    await this.adminService.updatePassword(adminResetPasswordDto.email, hashedPwd);
    await this.adminResetCodeRepository.delete(adminResetCode[0].id);
  }
}
