import { BadRequestException, Body, Logger, RequestTimeoutException, UnauthorizedException, } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as config from 'config';
import { v4 as uuidv4 } from 'uuid';
const https = require('https');
const fs = require('fs');
import { CryptoService } from './crypto.service';
import { AuthCreadentialsDto } from './dto/auth-credentials.dto';
import { TokenDto } from './dto/token.dto';
import { JwtPayload } from './jwt-payload.interface';
import { PcduserDto } from 'src/auth/dto/pcd-user.dto';
import { Connection, createConnection, FindManyOptions, getConnection, getCustomRepository, In, IsNull, Like, Not, QueryRunner } from 'typeorm';
import { TempSignUpDto } from './dto/temp-sign-up.dto';
import { PcduserRepository } from './pcduser.repository';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { PcdotpDto } from './dto/pcdotp.dto';
import { PcdotpRepository } from './pcdotp.repository';

const { exec } = require("child_process");
import { generateOtp } from 'otp-generator';

const dbConfig = config.get('db');
const oracledb = require('oracledb');
import { Pcduser } from './pcduser.entity';
import { convertDateString } from 'src/pipe/pcd-date.pipe';
import { UserBankDto } from './dto/user-bank.dto';

import { ChangePasswordDto } from './dto/change-password.dto';
import { log } from 'console';
import { version } from 'os';
import { EmailService } from 'src/email/email.service';
export enum Provider {
    GOOGLE = 'google'
}

export class AuthService {
    private logger = new Logger('AuthService'); // for logging

    constructor(
        @InjectRepository(PcduserRepository)
        private pcduserRepository: PcduserRepository,
        private jwtService: JwtService,
        private crypto: CryptoService,
        private pcdotpRepository: PcdotpRepository,
        private mailService : EmailService

    ) {

    }

    async getAll() {
        return await this.pcduserRepository.find();
    }
 
    async userInfo(authCredentialsDto: AuthCreadentialsDto){
        const { username ,secuLevel } = authCredentialsDto;
        let user = await this.pcduserRepository.findOne({ email: (username.trim()).toLocaleLowerCase() });
        if(!user){
            throw new BadRequestException('User Details not found')
        }
        if(user){
           // authCredentialsDto.secuLevel = user.secuLevel
            return {username:user.userName ,secuLevel:user.secuLevel}
        }
       

    }
   

    async signIn(authCredentialsDto: AuthCreadentialsDto, ip?: string): Promise<TokenDto> {

        const user: PcduserDto = await this.validateUserPassword(authCredentialsDto);

        if (!user) {
            throw new UnauthorizedException('User not Registered')
        }
        return await this.generateTokens(user, user.userName, ip, authCredentialsDto.module, user['changePassword']);
    }
  

    async validateUserPassword(authCredentialsDto: AuthCreadentialsDto): Promise<Pcduser> {
        const { username, password} = authCredentialsDto;

        let user = await this.pcduserRepository.findOne({ email: (username.trim()).toLocaleLowerCase() });
        console.log("user=============", user);
        if (!user) {
            throw new UnauthorizedException('User not Registered')
        }
      
        if (user && await user.validatePassword(password, user)) {
            return user;
        }
        else {
            throw new BadRequestException("Wrong Password");
            
        }
    }

    async forgotPassword(body: { pass: string, email: string, otp:string }): Promise<any> {
        let iuserData: PcduserDto = await this.pcduserRepository.findOne({ where: { email: body.email } });
        console.log("iuserdata",iuserData);
       
        const filter: FindManyOptions = { where: { "refCode": body.email, "status": "I" }, order: { code: "DESC" } };
                const newOtp = await this.pcdotpRepository.find(filter);

                if(body.otp != newOtp[0].otp){
                    throw new UnauthorizedException('Unauthorized Access')
                }
      
            const salt = await bcrypt.genSalt();
            iuserData.salt = salt;
            iuserData.pass = await bcrypt.hash(body.pass, salt);
            console.log("salt",iuserData.salt);
            console.log("pass",iuserData.pass);
            
            try {
                await this.pcduserRepository.save(iuserData);
            }
            catch (err) {
                throw new BadRequestException("Error in Password Reset");
            }
        }


        async sendMail(email: string, data: string, flag: string) {
            
            let content: string;
            let subject: string = "Login Credentials";
            if (flag == 'otp') {
                content = `Your one time password(OTP) for Tender Registration is, ${data} `;
                subject = 'OTP'
                
            }
          
            if (flag == 'fpwd') {
                content = `Your one time password(OTP) for Password reset is,  ${data}`;
                subject = 'OTP'
            }
    
            await this.mailService.sendMail(email, subject, content, content);
            
        }

    

    async sendOtp(body: { email: string}, flag: string): Promise<void> {

        if (body.email) {
            if  (flag == 'F' || flag == 'R'){

                let isUserExist = await this.pcduserRepository.findOne({ where: { email: body.email } });
                
                console.log("isUserExist",isUserExist);

                if(!isUserExist){
                    throw new BadRequestException("User not found");
                }
                if (flag == 'R' && isUserExist) {
                    throw new BadRequestException("Already registered with this email Id.");
                }else{
                let newOtp = await this.generateOtp(body.email, flag);
                if (body.email && flag == 'R') {
                    await this.sendMail(body.email, newOtp, 'otp');
                }
                if (body.email && flag == 'F') {
                    await this.sendMail(body.email, newOtp, 'fpwd');
                }
                    
            }

                    } err => {
                        console.log(err,"hello12345");
                        
                     };


                }
            }

           
            async tempSignUp(pcdUserDto: PcduserDto): Promise<any> {
                const filter: FindManyOptions = { where: { "refCode": pcdUserDto.email, "status": "I" }, order: { code: "DESC" } };
                const newOtp = await this.pcdotpRepository.find(filter);
                const existingUser = await this.pcduserRepository.findOne({ where: { email: pcdUserDto.email } });
                if (existingUser) {
                  throw new BadRequestException('User Already Exists');
                }
                if (pcdUserDto.otp != newOtp[0].otp) {
                  throw new UnauthorizedException('Unauthorized Access');
                }
              

                //Creating Random User_Id for every user registered
                const lastUser: PcduserDto = await this.pcduserRepository.findOne({
                  where: {},
                  order: { userId: 'DESC' },
                });
                let maxCode = 10000;
                if (lastUser && lastUser.userId) {
                  const lastUserId = parseInt(lastUser.userId);
                  maxCode = Math.max(maxCode, lastUserId);
                }
                const newUserId = String(maxCode + 1).padStart(5, '0');
                /////////////////////////////////////////////////////////////

                pcdUserDto.userId = newUserId;
                const salt = await bcrypt.genSalt();
                pcdUserDto.salt = salt;
                pcdUserDto.pass = await bcrypt.hash(pcdUserDto.pass, salt);
                pcdUserDto.secuLevel = 'U';
                delete pcdUserDto['otp'];
                await this.pcduserRepository.save(pcdUserDto);
                this.logger.verbose("User created with email:", pcdUserDto.email);
                return { data: "Successfully Registered" };
              }


    async verifyOtp(body: { otp: string, email: string }, flag: string): Promise<any> {

        console.log('body===', body, flag);
      
        const filter: FindManyOptions = { where: { "refCode": body.email, "status": "I" }, order: { code: "DESC" } };
      

        const newOtp = await this.pcdotpRepository.find(filter);
        if (!newOtp)
            throw new BadRequestException("OTP Error. Resend OTP");
        let currentDateTime = new Date().valueOf();
        let otpTime = newOtp[0].createdDate.valueOf();
        let diffMs = (currentDateTime - otpTime);
        let otpExpiry =  600000; //otp expire in 10 minutes

        let diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);

        if (diffMins > config.get('otp').expiresIn)
            throw new RequestTimeoutException('*OTP Expired');

       
        if (diffMs > otpExpiry)
            throw new RequestTimeoutException('*OTP Expired');

        if (newOtp[0].otp != body.otp)
            throw new UnauthorizedException("*Invalid OTP");
        if (newOtp[0].otp == body.otp) {
            return { data: 'success' };
        }

     }
    // async resendOtp(body: { code?: string | number, email?: string, mobile: string }, flag: string): Promise<void> {
    //     // const newOtp = await this.generateOtp(body.code.toString(), 'RR');

    //     // await this.smsService.sendSms(body.mobile, ` Your one time password(OTP) for Accurate Application Registration is,${newOtp}`);
    //     const isUserExist = await this.pcduserRepository.findOne({ where: { mobileNumber: body.mobile } });
    //     const newOtp = await this.generateOtp(isUserExist.userId.toString(), flag);
        
    //     if (body.mobile)
    //         await this.smsService.sendSms(body.mobile, newOtp);

    // }

  
    async generateOtp(refCode: string, flag: string): Promise<string> {

        var otpGenerator = require('otp-generator')

        const newOtp = await otpGenerator.generate(6, { upperCase: false, specialChars: false, alphabets: false });
        console.log("===newOtp====", newOtp);
       
        let usedFor: string = '';
     

        switch (flag) {
            case "R":
                usedFor = "REGISTRATION";
                break;
            case "RR":
                usedFor = "REGISTRATION RESEND"
                break;
            case "F":
                usedFor = "FORGOT PASSWORD";
                break;
            case "FR":
                usedFor = "FORGOT PASSWORD RESEND";
                break;

        }
        try {
         
            
            let igotp: PcdotpDto = new PcdotpDto();
            igotp.otp = newOtp;
            igotp.createdBy = 'SYSTEM';
            igotp.createdDate = new Date();
            igotp.modifiedBy = 'SYSTEM';
            igotp.modifiedDate = new Date();
            igotp.refCode = refCode;
            igotp.usedFor = usedFor;
            igotp.status = 'I';
            await this.pcdotpRepository.save(igotp);
            return newOtp;
        }
        catch (err) {
            console.log("====otperr===", err);
        }

    }


    /* The below method is used for generating the access token and refresh token when login */
    async generateTokens(user: PcduserDto, username: string, ip: string, module: string, changePassword?: string): Promise<TokenDto> {

        const sessionId = (Math.floor((Math.random() * 100000000) + 1)).toString();
        const payload: JwtPayload = { userId: user.userId.trim(), userName: user.userName.trim(), emailId: user.email, module: module, sessionId: sessionId };
        const accessToken = await this.jwtService.sign(payload, { expiresIn: config.get('jwt').expiresIn });

        const refreshToken = uuidv4();
        await this.pcduserRepository.updateRefreshToken(user.userId, refreshToken);


        this.logger.debug(`generated JWT Token with Payload ${JSON.stringify(payload)}`);
        const result: TokenDto = { accessToken: this.crypto.set(config.get('crypto').accessToken, accessToken), refreshToken: this.crypto.set(config.get('crypto').accessToken, refreshToken), emailId: user.email, module: module, changePassword: changePassword };

        return result;
    }
   
    private async hashPassword(password: string, salt: string): Promise<string> {
        return await bcrypt.hash(password, salt);
    }

    
}