import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { EncryptionService } from '../encryption/encryption.service';
import { JwtConstants } from 'src/constants/jwt.constants';
import { UserSigninResDto } from './dto/user-signin-res.dto';
import { User } from 'src/user/entities/user.entity';
import { UserService } from 'src/user/user.service';
import { CreateUserDto } from 'src/user/dto/create-user.dto';
import { LoginUserDto } from './dto/login-user.dto';
import { ALL_ROLES } from 'src/shared/enum/admin-role.enum';
import { PayloadJwtDto } from 'src/shared/dto/payload-jwt.dto';
import { UpdatePwdDto } from './dto/update-pwd.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { UserResetCode } from './entities/user-reset-code.entity';
import { Repository } from 'typeorm';
import { randomInt } from 'crypto';
import { UserConfirmEmailBody, UserResetPasswordDto } from './dto/user-reset-password.dto';
import { UserConfirmCode } from './entities/user-confirm-code.entity';
import { EmailService } from 'src/email/email.service';

@Injectable()
export class AuthUserService {
  constructor(
    @InjectRepository(UserResetCode) private userResetCodeRepository: Repository<UserResetCode>,
    @InjectRepository(UserConfirmCode) private userConfirmCodeRepository: Repository<UserConfirmCode>,
    private userService: UserService,
    private jwtService: JwtService,
    private encryptionService: EncryptionService,
    private readonly mailService: EmailService,
  ) {}

  async signUp(createAdminDto: CreateUserDto): Promise<UserSigninResDto> {
    const loginAdminDto: LoginUserDto = {
      email: createAdminDto.email,
      password: createAdminDto.password,
    };
    const hashedPwd = await this.encryptionService.hashPassword(createAdminDto.password);
    createAdminDto.password = hashedPwd;
    if (await this.userService.userExists(createAdminDto.email)) {
      throw new BadRequestException('User already exists');
    }
    await this.userService.create(createAdminDto);

    const adminSigninResDto: UserSigninResDto = await this.signIn(loginAdminDto);
    await this.sendConfirmationCode(createAdminDto.email);

    return adminSigninResDto;
  }

  async signIn(logInAdmin: LoginUserDto): Promise<UserSigninResDto> {
    const user: User = await this.userService.findOne(logInAdmin.email);
    if (!user) throw new UnauthorizedException('Invalid credentials');
    const validPwd: boolean = await this.encryptionService.comparePassword(logInAdmin.password, user.password);
    if (!validPwd) {
      throw new UnauthorizedException('Invalid credentials');
    }
    const accessToken = await this.jwtService.sign(
      { email: user.email, name: user.name, lastName: user.lastName, role: ALL_ROLES.USER },
      { secret: JwtConstants.SECRET },
    );
    const userSigninResDto: UserSigninResDto = {
      email: user.email,
      name: user.name,
      lastName: user.lastName,
      token: accessToken,
      role: ALL_ROLES.USER,
    };
    return userSigninResDto;
  }

  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) {
    const user: User = await this.userService.findOne(email);
    const prevPwdHashed = user.password;
    const validPwd: boolean = await this.encryptionService.comparePassword(updatePwdDto.prevPassword, prevPwdHashed);

    if (!validPwd) throw new UnauthorizedException('Invalid password');

    const newPwdHashed = await this.encryptionService.hashPassword(updatePwdDto.newPassword);
    await this.userService.updatePassword(email, newPwdHashed);
  }

  async getResetPasswordCode(email: string) {
    console.log(`Email: ${email}`);
    const user: User = await this.userService.findOne(email);
    if (!user) throw new UnauthorizedException('Invalid email');
    const resetCode = randomInt(100000, 999999).toString();
    const expirationDate = new Date();
    expirationDate.setHours(expirationDate.getHours() + 1);
    const existUserCode = await this.userResetCodeRepository.findBy({ user, code: resetCode });
    console.log(existUserCode);

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

  async resetPassword({ email, resetCode, newPassword }: UserResetPasswordDto) {
    const user: User = await this.userService.findOne(email);
    if (!user) throw new UnauthorizedException('Invalid email');

    const userResetCode: UserResetCode[] = await this.userResetCodeRepository.findBy({ user, code: resetCode });

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

    const newPwdHashed = await this.encryptionService.hashPassword(newPassword);
    await this.userService.updatePassword(email, newPwdHashed);
    await this.userResetCodeRepository.delete(userResetCode[0].id);
  }

  async sendConfirmationCode(email: string) {
    const user: User = await this.userService.findOne(email);
    if (!user) throw new UnauthorizedException('Invalid email');
    const resetCode = randomInt(100000, 999999).toString();
    const expirationDate = new Date();
    expirationDate.setHours(expirationDate.getHours() + 1);
    const existUserCode = await this.userConfirmCodeRepository.findBy({ user, code: resetCode });
    if (existUserCode.length > 0) {
      await this.userConfirmCodeRepository.delete(existUserCode[0].id);
    }
    await this.userConfirmCodeRepository.save({ user, code: resetCode, expirationDate });
    await this.mailService.sendConfirmationCode(email, resetCode);
    return resetCode;
  }

  async confirmEmail(email: string, { code }: UserConfirmEmailBody) {
    const user: User = await this.userService.findOne(email);
    if (!user) throw new UnauthorizedException('Invalid email');

    const userConfirmCode: UserResetCode[] = await this.userConfirmCodeRepository.findBy({ user, code: code });

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

    await this.userService.confirmEmail(email);
    await this.userConfirmCodeRepository.delete(userConfirmCode[0].id);
  }
}
