import { RequestQuery } from '@hapi/hapi'
import * as Constants from "../config/constants"
import crypto from "crypto";
import { UserDao } from "../dao/user.dao"
import Bcrypt from "bcrypt";
import { AppError } from "../../utils/errors"
import { Token } from "../../utils/token"
import { Common } from "../../utils/common"
import { USER } from "../config/constants"
import { TOKEN } from "../config/constants"
// import other services
import { EmailTemplateService } from './emailTemplate.service';
import { TokenService } from './token.service';
import { RoleService } from './role.service';
import { LanguageService } from './language.service';
import { ActivityService } from './activity.service';
import axios, { isAxiosError } from 'axios';
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const jwksLocalClient = jwksClient;
import AppCache from "../../utils/appCache";
import { PaymentService } from "../services/payment.service"
import { sequelize } from "../models";

const rounds = process.env.HASH_ROUNDS ? +process.env.HASH_ROUNDS : 5;

export class UserService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private userDao: UserDao;
    private emailTemplateService: EmailTemplateService;
    private tokenService: TokenService;
    private roleService: RoleService;
    private languageService: LanguageService;
    private activityService: ActivityService;
    private paymentService: PaymentService;
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                scope: null,
                config: null,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.userDao = new UserDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.emailTemplateService = new EmailTemplateService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.tokenService = new TokenService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.roleService = new RoleService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.activityService = new ActivityService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.paymentService = new PaymentService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }
    private loginProcess = async (userId: number) => {
        try {
            const getLanguageIdInput: LanguageGetLanguageIdServiceInput = { languageCode: this.language };
            let languageId = await this.languageService.getLanguageId(getLanguageIdInput);
            if (process.env.ENABLE_2FA == 'true') {
                // generate 2FA token and return with token data;
                let userSettings = await this.userDao.getUserSettings(userId, ['twoFactorAuthentication']);
                if (userSettings.twoFactorAuthentication) {

                }
            }
            // continue with normal login
            let userData = await this.userDao.getUserAuthData(userId, languageId);
            let permissions: string[] = [];
            for (let role of userData.userRoles) {
                permissions.push(role.code);
                if (role.rolePermissions.length) {
                    for (let rolePermission of role.rolePermissions) {
                        permissions.push(rolePermission.code);
                    }
                }
            }
            let defaultAccount = userData.userAccounts.find((accounts: { id: number, name: string, key: string }) => accounts.id === this.accountId);
            if (defaultAccount) {
                let authToken = Token.signToken('authorizationToken', { userId: userData.id, accountKey: defaultAccount.key, accountId: defaultAccount.id, email: userData.email, name: userData.userProfile.name, permissions: permissions, isPremium: userData.userSetting.isPremium });
                let refreshToken = Token.signToken('refreshToken', { userId: userData.id, accountKey: defaultAccount.key, accountId: defaultAccount.id, permissions: permissions });
                userData.token = authToken;
                userData.refreshToken = refreshToken;
                userData.accountId = defaultAccount.id
                return userData;
            }
            throw new AppError(401, 'INVALID_ACCOUNT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getUserProfile = async (userId: number, includeCounts: boolean = false) => {
        try {
            const getLanguageIdInput: LanguageGetLanguageIdServiceInput = { languageCode: this.language };
            let languageId = await this.languageService.getLanguageId(getLanguageIdInput);

            let userData = await this.userDao.getUserAuthData(userId, languageId, includeCounts);
            if (!userData) {
                throw new AppError(404, 'USER_NOT_FOUND', { userId });
            }
            let permissions: string[] = [];
            for (let role of userData.userRoles) {
                permissions.push(role.code);
                if (role.rolePermissions.length) {
                    for (let rolePermission of role.rolePermissions) {
                        permissions.push(rolePermission.code);
                    }
                }
            }

            return userData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    private userAuthVerification = async (userId: number, password: string, verifyWithPassword: string) => {
        try {
            let passwordVerification = false;
            const storedHash = verifyWithPassword ? verifyWithPassword.trim() : '';

            if (!storedHash) {
                throw new AppError(400, 'INVALID_EMAIL_OR_PASSWORD_PROVIDED', { password: 'INVALID_EMAIL_OR_PASSWORD_PROVIDED' });
            }

            // Check if it's Bcrypt (handles $2a, $2b, and Laravel's $2y)
            if (storedHash.startsWith('$2')) {
                // Try original hash first (some modern bcrypt libraries support $2y natively)
                passwordVerification = Bcrypt.compareSync(password, storedHash);

                // If failed and it's a PHP/Laravel $2y$ hash, try common normalization variants
                if (!passwordVerification && storedHash.startsWith('$2y$')) {
                    const variants = ['$2a$', '$2b$'];
                    for (const variant of variants) {
                        const hashToVerify = storedHash.replace(/^\$2y\$/, variant);
                        passwordVerification = Bcrypt.compareSync(password, hashToVerify);
                        if (passwordVerification) break;
                    }
                }

                if (passwordVerification && storedHash.startsWith('$2y$')) {
                    // Auto-upgrade Laravel hash to Node's default Bcrypt format ($2b)
                    const newHash = Bcrypt.hashSync(password, rounds);
                    await this.userDao.setAccountPassword(userId, newHash);
                }
            }
            // Check if it's MD5 (32 chars hex)
            else if (storedHash.length === 32 && /^[a-f0-9]{32}$/i.test(storedHash)) {
                const md5Hash = crypto.createHash('md5').update(password).digest('hex');
                passwordVerification = (md5Hash === storedHash.toLowerCase());

                if (passwordVerification) {
                    // Auto-upgrade to Bcrypt for future logins
                    const newHash = Bcrypt.hashSync(password, rounds);
                    await this.userDao.setAccountPassword(userId, newHash);
                }
            }

            if (passwordVerification) {
                let userData = await this.loginProcess(userId);
                return userData
            } else {
                throw new AppError(400, 'INVALID_EMAIL_OR_PASSWORD_PROVIDED', { password: 'INVALID_EMAIL_OR_PASSWORD_PROVIDED' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getUserFromToken = (request: RequestQuery) => {
        try {
            if (request.auth.isAuthenticated && request.auth.credentials.userData) {
                let userDetails: AuthCredentials = request.auth.credentials.userData;
                return userDetails;
            } else {
                return false;
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    generateSignUpToken = async (signUpRequest: SignUpEmailObject) => {
        try {
            if (!signUpRequest.email) {
                throw new AppError(400, 'INVALID_EMAIL', { email: 'INVALID_EMAIL' });
            }
            let accountExists: boolean = await this.userDao.accountExists(signUpRequest.email, null, null);
            if (!accountExists) {
                // create a verification token 
                if (+process.env.MAILBOXLAYER_FLAG!) {
                    let validateEmail = await Common.validateEmail(signUpRequest.email);
                    if (!validateEmail) {
                        throw new AppError(400, 'INVALID_EMAIL', { email: 'INVALID_EMAIL' });
                    }
                }
                let verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : Common.generateCode(+process.env.CODE_LENGTH!, 'number');
                const tokenData = {
                    ...signUpRequest,
                    accountId: this.accountId,
                    firstName: signUpRequest.firstName ?? null,
                    lastName: signUpRequest.lastName ?? null,
                    rankId: signUpRequest.rankId ?? null,
                };
                let signupToken = Token.signToken('signup', { data: tokenData, accountId: this.accountId, verificationCode: verificationCode });
                let token: TokenObject = { type: 'signup', entityValue: signUpRequest.email, token: signupToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                await this.tokenService.createToken(token);
                // send email to user to with verification code
                if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                    this.emailTemplateService.sendEmailTemplate('signup-email', [`"${signUpRequest.name}" <${signUpRequest.email}>`], { name: signUpRequest.name, "verification-code": verificationCode, verificationCode: verificationCode, code: verificationCode })
                }
                return { signUpToken: signupToken }
            } else {
                let errorDetails = {};
                errorDetails = { ...errorDetails, email: 'EMAIL_ALREADY_IN_USE' }
                throw new AppError(400, 'EMAIL_ALREADY_IN_USE', errorDetails);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    createNewAccount = async (input: UserCreateNewAccountServiceInput) => {
        try {
            const { signupData, createUser = true } = input;
            let accountExists: boolean = await this.userDao.accountExistsForDiffUser(null, signupData.countryCode!, signupData.mobile!);
            if (accountExists) {
                throw new AppError(400, 'MOBILE_ALREADY_EXISTS', {});
            }
            const createAccountInput: UserCreateServiceInput = { signupData, createUser };
            return this.createAccount(createAccountInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    createAccount = async (input: UserCreateServiceInput) => {
        try {
            const { signupData, createUser = false } = input;
            let addTrial = true;
            let roleIds = [];
            if (signupData.roles) {
                const roles: string[] = signupData.roles;
                roleIds = await Promise.all(
                    roles.map(async (code: string) => {
                        const getRoleByCodeInput: RoleGetByCodeServiceInput = { code };
                        const role = await this.roleService.getByCode(getRoleByCodeInput);
                        return role.id;
                    })
                );
            }
            else roleIds = await this.roleService.getDafaultRoles();

            if (signupData.password)
                signupData.password = Bcrypt.hashSync(signupData.password, rounds);
            if (!signupData.accountId)
                signupData.accountId = this.accountId;

            if (signupData.mobile) {
                const check = await this.userDao.checkDeletedMobile(signupData.mobile);
                addTrial = !check;
            }

            // Check if user already exists with email or username
            if (signupData.email) {
                const emailExists = await this.userDao.accountExists(signupData.email, null, null);
                if (emailExists) {
                    throw new AppError(400, 'USER_ALREADY_EXISTS_WITH_EMAIL', { email: 'USER_ALREADY_EXISTS_WITH_EMAIL' });
                }
            }
            if (signupData.username) {
                const usernameExists = await this.userDao.accountExists(null, signupData.username, null);
                if (usernameExists) {
                    throw new AppError(400, 'USER_ALREADY_EXISTS_WITH_USERNAME', { username: 'USER_ALREADY_EXISTS_WITH_USERNAME' });
                }
            }

            const transaction = await sequelize.transaction();
            let newUserId: number;
            try {
                const createUserDaoInput: UserCreateDaoInput = { userInfo: signupData, roles: roleIds };
                newUserId = await this.userDao.createUser(createUserDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }

            // if (addTrial && newUserId && !createUser) {
            //     const addTrialSubscriptionInput: PaymentAddTrialSubscriptionServiceInput = { userId: newUserId };
            //     await this.paymentService.addTrialSubscription(addTrialSubscriptionInput);
            // }
            let userData = null;
            if (createUser)
                userData = await this.userDao.getUserById(newUserId, true);
            else
                userData = await this.loginProcess(newUserId);

            return userData
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    setPassword = async (input: UserSetPasswordServiceInput) => {
        try {
            const { userId, password } = input;
            const userExists = await this.userDao.getUserById(userId, false);
            if (!userExists) {
                throw new AppError(400, 'USER_DOES_NOT_EXISTS', {});
            }
            const passwordEncr = Bcrypt.hashSync(password, rounds);
            const setPasswordDaoInput: UserSetPasswordDaoInput = { userId, password: passwordEncr };
            await this.userDao.setPassword(setPasswordDaoInput, {});
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateUserDetails = async (input: UserUpdateUserDetailsServiceInput) => {
        try {
            const { id, signupData } = input;

            let accountExists: boolean = await this.userDao.accountExistsForDiffUser(null, signupData.countryCode!, signupData.mobile!, id);
            if (accountExists) {
                throw new AppError(400, 'MOBILE_ALREADY_EXISTS', {});
            }

            let roleIds = [];
            if (signupData.roles) {
                const roles: string[] = signupData.roles;
                roleIds = await Promise.all(
                    roles.map(async (code: string) => {
                        const getRoleByCodeInput: RoleGetByCodeServiceInput = { code };
                        const role = await this.roleService.getByCode(getRoleByCodeInput);
                        return role.id;
                    })
                );
            }
            else roleIds = await this.roleService.getDafaultRoles();

            if (signupData.password)
                signupData.password = Bcrypt.hashSync(signupData.password, rounds);
            if (!signupData.accountId)
                signupData.accountId = this.accountId;

            const transaction = await sequelize.transaction();
            let newUserId: number;
            try {
                const updateUserDetailsDaoInput: UserUpdateUserDetailsDaoInput = { id, userInfo: signupData, roles: roleIds };
                newUserId = await this.userDao.updateUserDetails(updateUserDetailsDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }

            const userData = await this.userDao.getUserById(newUserId, true);
            return userData
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    verifyToken = async (token: Text, verificationCode: string, type: string): Promise<any> => {
        try {
            if (await this.tokenService.isActive(token)) {
                await this.tokenService.recordAttempt(token);
                let tokenData = await Token.getTokenData(token, type);
                if (tokenData.verificationCode == verificationCode && this.accountId == tokenData.accountId) {
                    await this.tokenService.markAsUsed(token);
                    return type == 'signup' ? tokenData.data : tokenData
                }
            }
            throw new AppError(400, 'INVALID_OR_EXPIRED_CODE', { code: 'INVALID_OR_EXPIRED_CODE' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    verifyOTP = async (token: Text, verificationCode: string, type: string): Promise<any> => {
        try {
            if (await this.tokenService.isActive(token)) {
                await this.tokenService.recordAttempt(token);
                let tokenData = await Token.getTokenData(token, type);
                let mobile = tokenData.countryCode + tokenData.mobile;
                let verifyOtp = +process.env.ENABLE_MASTER_VERIFICATION! ? (tokenData.verificationCode == verificationCode && this.accountId == tokenData.accountId) : await Common.verifyMobileOTP(mobile, verificationCode, process.env.SMS_GATEWAY!);
                if (verifyOtp) {
                    await this.tokenService.markAsUsed(token);
                    return type == 'signup' ? tokenData.data : tokenData
                }
            }
            throw new AppError(400, 'INVALID_OR_EXPIRED_CODE', { code: 'INVALID_OR_EXPIRED_CODE' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    resendCode = async (token: Text, type: string, onCall: boolean = false): Promise<any> => {
        try {
            if (token) {
                let tokenData = await Token.getTokenData(token, type);
                if (tokenData.verificationCode) {
                    await this.tokenService.markAsUsed(token);
                    // create a verification token 
                    let newtoken: TokenObject;
                    let verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : Common.generateCode(+process.env.CODE_LENGTH!, 'number');
                    let newTokenData: Text;
                    if (type == 'signup') {
                        newTokenData = Token.signToken(type, { data: tokenData.data, verificationCode: verificationCode, accountId: this.accountId });
                        newtoken = { type: type, entityValue: tokenData.data.email, token: newTokenData, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                    } else if (type == 'mobileLogin') {
                        let mobile = tokenData.countryCode + tokenData.mobile;
                        verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? verificationCode : await Common.resendOTP(mobile, process.env.SMS_GATEWAY!, onCall ? 'voice' : 'text') ? 'sent' : 'failed';
                        newTokenData = Token.signToken(type, { ...tokenData, verificationCode: verificationCode });
                        newtoken = { type: type, entityValue: tokenData.mobile, token: newTokenData, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                    } else if (type == 'changeMobile') {
                        let mobile = tokenData.countryCode + tokenData.mobile;
                        verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? verificationCode : await Common.resendOTP(mobile, process.env.SMS_GATEWAY!, onCall ? 'voice' : 'text') ? 'sent' : 'failed';
                        newTokenData = Token.signToken(type, { ...tokenData, verificationCode: verificationCode });
                        newtoken = { type: type, entityValue: tokenData.mobile, token: newTokenData, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                    }
                    else {
                        newTokenData = Token.signToken(type, { ...tokenData, verificationCode: verificationCode });
                        newtoken = { type: type, entityValue: tokenData.email, token: newTokenData, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                    }

                    await this.tokenService.createToken(newtoken);
                    // send email to user to with verification code
                    if (type == 'signup') {
                        if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                            this.emailTemplateService.sendEmailTemplate('signup-email', [`"${tokenData.data.name}" <${tokenData.data.email}>`], { name: tokenData.data.name, "verification-code": verificationCode, verificationCode: verificationCode, code: verificationCode })
                        }
                    } else if (type == 'mobileLogin') {
                        if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                            // this.emailTemplateService.sendEmailTemplate('signup-email', [`"${tokenData.data.name}" <${tokenData.data.email}>`], { name: tokenData.data.name, "verification-code": verificationCode })
                        }
                    } else {
                        if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                            const templateCode = type === 'forgotPassword' ? 'reset-password-email' : type + '-email';
                            const recipient = tokenData.name ? `"${tokenData.name}" <${tokenData.email}>` : `<${tokenData.email}>`;
                            this.emailTemplateService.sendEmailTemplate(templateCode, [recipient], { name: tokenData.name || '', "verification-code": verificationCode, verificationCode: verificationCode, code: verificationCode })
                        }
                    }
                    return { [type == 'signup' ? 'signUpToken' : type + 'Token']: newTokenData }
                } else {
                    throw new AppError(400, 'INVALID_OR_EXPIRED_CODE', { code: 'INVALID_OR_EXPIRED_CODE' });
                }
            }
            throw new AppError(400, 'INVALID_OR_EXPIRED_CODE', { code: 'INVALID_OR_EXPIRED_CODE' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    loginWithEmailPassword = async (email: string, password: string) => {
        try {
            let user = await this.userDao.getUserByEmail(email, Constants.USER.STATUS.ACTIVE, true);
            if (!user.password) {
                throw new AppError(400, 'SOCIAL_LOGIN_ALLOWED', { email: 'SOCIAL_LOGIN_ALLOWED' });
            }
            return this.userAuthVerification(user.id, password, user.password);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    loginWithUsernamePassword = async (username: string, password: string) => {
        try {
            let user = await this.userDao.getUserByUsername(username, Constants.USER.STATUS.ACTIVE, true);
            if (!user.password) {
                throw new AppError(400, 'SOCIAL_LOGIN_ALLOWED', { username: 'SOCIAL_LOGIN_ALLOWED' });
            }
            return this.userAuthVerification(user.id, password, user.password);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    loginWithMobilePassword = async (countryCode: string, mobile: string, password: string) => {
        try {
            let user = await this.userDao.getUserByMobileNo(countryCode, mobile, Constants.USER.STATUS.ACTIVE, true);
            if (!user.password) {
                throw new AppError(400, 'SOCIAL_LOGIN_ALLOWED', { mobile: 'SOCIAL_LOGIN_ALLOWED' });
            }
            return this.userAuthVerification(user.id, password, user.password);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getResetPasswordToken = async (email: string, accountKey: string) => {
        try {
            let user = await this.userDao.getUserByEmail(email, null, false, true);
            if (user) {
                // create a verification token 
                let verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : Common.generateCode(+process.env.CODE_LENGTH!, 'number');
                let resetPasswordToken = Token.signToken('forgotPassword', { name: user.userProfile.name, email: email, accountId: this.accountId, accountKey: accountKey, verificationCode: verificationCode });
                let token: TokenObject = { type: 'forgotPassword', entityValue: email, token: resetPasswordToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                await this.tokenService.createToken(token);
                // send email to user to with verification code
                if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                    this.emailTemplateService.sendEmailTemplate('reset-password-email', [`"${user.userProfile.name}" <${email}>`], { name: user.userProfile.name, "verification-code": verificationCode, verificationCode: verificationCode, code: verificationCode })
                }
                return { resetPasswordToken: resetPasswordToken }
            } else {
                let errorDetails = {};
                errorDetails = { ...errorDetails, email: 'INVALID_ACCOUNT' }
                throw new AppError(400, 'INVALID_ACCOUNT', errorDetails);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    resetPassword = async (email: string, password: string) => {
        try {
            let accountExists = await this.userDao.getUserByEmail(email, USER.STATUS.ACTIVE, false);
            if (accountExists) {
                // update password 
                const newPassword = Bcrypt.hashSync(password, rounds);
                this.userDao.setAccountPassword(accountExists.id, newPassword);
                // notify user via email
                this.emailTemplateService.sendEmailTemplate('reset-password-update', [`${accountExists.userProfile.name} <${accountExists.email}>`], { name: accountExists.userProfile.name, email: accountExists.email });
                let userData = await this.loginProcess(accountExists.id);
                return userData
            } else {
                let errorDetails = {};
                errorDetails = { ...errorDetails, email: 'INVALID_OR_INACTIVE_ACCOUNT' }
                throw new AppError(400, 'INVALID_OR_INACTIVE_ACCOUNT', errorDetails);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    changePassword = async (id: number, name: string, email: string, password: string): Promise<boolean> => {
        try {
            // update password 
            const newPassword = Bcrypt.hashSync(password, rounds);
            this.userDao.setAccountPassword(id, newPassword);
            // notify user via email
            this.emailTemplateService.sendEmailTemplate('change-password-update', [`${name} <${email}>`], { name: name, email: email });
            return true
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updatePassword = async (id: number, name: string, email: string, currentPassword: string, newPassword: string): Promise<boolean> => {
        try {
            const user = await this.userDao.getUserByEmail(email, null, true);
            if (!user) throw new AppError(404, 'USER_NOT_FOUND', {});

            if (!user.password) {
                throw new AppError(400, 'SOCIAL_LOGIN_ALLOWED', {});
            }

            const isMatch = Bcrypt.compareSync(currentPassword, user.password);
            if (!isMatch) {
                throw new AppError(400, 'INVALID_CURRENT_PASSWORD', { currentPassword: 'INVALID_CURRENT_PASSWORD' });
            }

            const encrNewPassword = Bcrypt.hashSync(newPassword, rounds);
            this.userDao.setAccountPassword(id, encrNewPassword);
            this.emailTemplateService.sendEmailTemplate('change-password-update', [`${name} <${email}>`], { name: name, email: email });
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }


    getUsers = async (listRequest: UserPaginatioinListRequestObject): Promise<UserPaginatedList> => {
        try {
            let { page, perPage, searchText, sortBy, sortDirection, roleId, status, deviceType, userIds, higherOrEqualRank, isPremium } = listRequest;
            const getLanguageIdInput: LanguageGetLanguageIdServiceInput = { languageCode: this.language };
            let languageId = await this.languageService.getLanguageId(getLanguageIdInput);
            let roles: number[] = []
            if (listRequest.staffOnly) {
                let staffRoles = await this.roleService.staffRoles();
                if (listRequest.roleId) {
                    if (Array.isArray(listRequest.roleId)) {
                        roles = [...listRequest.roleId]
                    } else {
                        listRequest.roleId
                        roles.push(listRequest.roleId);
                    }
                } else if (!listRequest.roleId) {
                    roles = staffRoles
                } else {
                    roles.push(-1)
                }
            } else {
                let userRoles = await this.roleService.userRoles();
                if (listRequest.roleId) {
                    if (Array.isArray(listRequest.roleId)) {
                        roles = [...listRequest.roleId]
                    } else {
                        listRequest.roleId
                        roles.push(listRequest.roleId);
                    }
                } else if (!listRequest.roleId) {
                    roles = userRoles
                } else {
                    roles.push(-1)
                }
            }
            roleId = roles;
            let users: UserPaginatedData = await this.userDao.getUserList(languageId, page, perPage, searchText, sortBy, sortDirection, roleId, status, userIds, deviceType, higherOrEqualRank, isPremium);
            if (users) {
                return {
                    data: users.data,
                    page: page,
                    perPage: perPage,
                    totalRecords: users.totalRecords,
                    totalPages: users.totalPages || Common.getTotalPages(users.totalRecords, perPage)
                } as unknown as UserPaginatedList;
            }
            throw new AppError(400, 'USER_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    referralList = async (page: number = 1, perPage: number = +process.env.PER_PAGE_RECORDS! || 20, userId: null | number = null): Promise<UserPaginatedList> => {
        try {
            let users = await this.userDao.referralList(page, perPage, userId);
            if (users) {
                return {
                    data: users.data,
                    page: page,
                    perPage: perPage,
                    totalRecords: users.totalRecords,
                    totalPages: users.totalPages || Common.getTotalPages(users.totalRecords, perPage)
                } as unknown as UserPaginatedList;
            }
            throw new AppError(400, 'USER_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getAllUsers = async (listRequest: UserPaginatioinListRequestObject): Promise<any> => {
        try {
            let roles: number[] = []
            if (listRequest.staffOnly) {
                let staffRoles = await this.roleService.staffRoles();
                if (listRequest.roleId) {
                    if (Array.isArray(listRequest.roleId)) {
                        roles = [...listRequest.roleId]
                    } else {
                        listRequest.roleId
                        roles.push(listRequest.roleId);
                    }
                } else if (!listRequest.roleId) {
                    roles = staffRoles
                } else {
                    roles.push(-1)
                }
            } else {
                let userRoles = await this.roleService.userRoles();
                if (listRequest.roleId) {
                    if (Array.isArray(listRequest.roleId)) {
                        roles = [...listRequest.roleId]
                    } else {
                        listRequest.roleId
                        roles.push(listRequest.roleId);
                    }
                } else if (!listRequest.roleId) {
                    roles = userRoles
                } else {
                    roles.push(-1)
                }
            }
            listRequest.roleId = roles;
            let users = await this.userDao.getUserListAll(listRequest);
            if (users) {
                return users;
            }
            throw new AppError(400, 'USER_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    logUserActivity = async (code: string, ip: string, replacements: any, deviceInfo: any, storeLocation: boolean = false, forUser: number | null = null) => {
        try {
            const logUserActivityInput: ActivityLogUserActivityServiceInput = { code, ip, replacements, deviceInfo, storeLocation, forUser };
            this.activityService.logUserActivity(logUserActivityInput).catch(err => { });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    tokenWithRefreshToken = async (refreshToken: Text, type: string) => {
        try {
            let tokenData = await Token.getTokenData(refreshToken, type);
            if (tokenData.userId) {
                // check if user is active
                if (await this.userDao.isActive(tokenData.userId)) {
                    let userData = await this.loginProcess(tokenData.userId);
                    return {
                        userId: tokenData.userId,
                        accountId: tokenData.accountId,
                        token: userData.token,
                        refreshToken: userData.refreshToken
                    }
                } else {
                    throw new AppError(400, 'INACTIVE_USER', { refreshToken: 'INACTIVE_USER' });
                }
            } else {
                throw new AppError(400, 'INVALID_OR_EXPIRED_REFRESH_TOKEN', { refreshToken: 'INVALID_OR_EXPIRED_REFRESH_TOKEN' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateUserProfile = async (fieldName: string, fieldValue: string | number | boolean): Promise<void> => {
        try {
            this.userDao.updateUserProfile(fieldName, fieldValue);
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateUserProfileFields = async (fields: Record<string, string | number | boolean | Date>): Promise<void> => {
        try {
            const updateUserProfileFieldsDaoInput: UserUpdateUserProfileFieldsDaoInput = { fields };
            await this.userDao.updateUserProfileFields(updateUserProfileFieldsDaoInput, {});
            return;
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateReferralCode = async (input: UserUpdateReferralCodeServiceInput): Promise<void> => {
        try {
            // check user with influencer role
            const updateReferralCodeDaoInput: UserUpdateReferralCodeDaoInput = { userId: input.userId, referralCode: input.referralCode };
            await this.userDao.updateReferralCode(updateReferralCodeDaoInput, {});
            return;
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateUserStatus = async (fieldValue: string | number | boolean, userId: number): Promise<void> => {
        try {
            await this.userDao.updateUser('status', fieldValue, userId);
            return;
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    validateUpdateEmailAndLogin = async (userId: number, fieldName: string, fieldValue: string) => {
        try {
            const isEmailAlreadyExist = await this.userDao.validateEmail(fieldValue!, 1)
            if (isEmailAlreadyExist) {
                throw new AppError(400, 'EMAIL_ALREADY_EXIST', {});
            }
            await this.userDao.updateUser(fieldName, fieldValue, userId);
            const userData = await this.loginProcess(userId)
            return userData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    validateUpdateMobileAndLogin = async (userId: number, countryCode: string, mobile: string) => {
        try {
            const isMobileAlreadyExist = await this.userDao.validateMobile(countryCode, mobile, 1);
            if (isMobileAlreadyExist) {
                throw new AppError(400, 'MOBILE_ALREADY_EXIST', {});
            }
            await this.userDao.updateUserMobile(countryCode, mobile, userId);
            const userData = await this.loginProcess(userId)
            return userData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    deleteUser = async (input: UserDeleteServiceInput) => {
        try {
            const deleteUserDaoInput: UserDeleteDaoInput = { userId: input.userId, countryCode: input.countryCode, mobile: input.mobile };
            await this.userDao.deleteUser(deleteUserDaoInput, {});
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    deleteUserByAdmin = async (userId: number) => {
        try {
            const user = await this.userDao.getUserById(userId, false);
            if (!user) {
                throw new AppError(404, 'USER_NOT_FOUND', { userId: 'USER_NOT_FOUND' });
            }
            const deleteUserDaoInput: UserDeleteDaoInput = {
                userId: user.id,
                countryCode: user.countryCode,
                mobile: user.mobile
            };
            await this.userDao.deleteUser(deleteUserDaoInput, {});
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    toggleFollow = async (authUserId: number, targetUserId: number) => {
        try {
            return await this.userDao.toggleFollow(authUserId, targetUserId);
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    followersList = async (userId: number, listRequest: UserPaginatioinListRequestObject): Promise<UserPaginatedList> => {
        try {
            let { page = 1, perPage = process.env.PER_PAGE_RECORDS ? +process.env.PER_PAGE_RECORDS : 20, searchText } = listRequest;
            let users = await this.userDao.followersList(userId, +page, +perPage, searchText ?? undefined);
            if (users) {
                let totalPages = Common.getTotalPages(users.totalRecords, +perPage);
                return {
                    data: users.data,
                    page: +page,
                    perPage: +perPage,
                    totalRecords: users.totalRecords,
                    totalPages: totalPages
                } as unknown as UserPaginatedList;
            }
            throw new AppError(400, 'USER_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    followingList = async (userId: number, listRequest: UserPaginatioinListRequestObject): Promise<UserPaginatedList> => {
        try {
            let { page = 1, perPage = process.env.PER_PAGE_RECORDS ? +process.env.PER_PAGE_RECORDS : 20, searchText } = listRequest;
            let users = await this.userDao.followingList(userId, +page, +perPage, searchText ?? undefined);
            if (users) {
                let totalPages = Common.getTotalPages(users.totalRecords, +perPage);
                return {
                    data: users.data,
                    page: +page,
                    perPage: +perPage,
                    totalRecords: users.totalRecords,
                    totalPages: totalPages
                } as unknown as UserPaginatedList;
            }
            throw new AppError(400, 'USER_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    mobileLogin = async (userId: number) => {
        try {
            const userData = await this.loginProcess(userId)
            return userData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateUserSetting = async (fieldName: string, fieldValue: string | number | boolean): Promise<void> => {
        try {
            this.userDao.updateUserSetting(fieldName, fieldValue);
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getUserById = async (userId: number, fullObject: boolean, includeCounts: boolean = false): Promise<UserInterface | null> => {
        try {
            const getUserByID = await this.userDao.getUserById(userId, fullObject, includeCounts);
            if (!getUserByID) {
                throw new AppError(404, 'USER_NOT_FOUND', { userId });
            }
            return getUserByID;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    appleKey = async (kid: string) => {
        const client = jwksLocalClient({
            jwksUri: process.env.APPLE_AUTH_KEY_URL!,
            timeout: 30000
        });
        return await client.getSigningKey(kid);
    };

    getStandardFacebookUser = async (token: string) => {
        const { data } = await axios.get(
            `${process.env.FACEBOOK_LOGIN_END_POINT}=${token}`
        );
        if (!data.id) {
            throw new Error("Invalid token (missing id or name)");
        }
        return { facebookUserId: data.id, facebookUserName: data.name, facebookEmail: data.email };
    };

    getLimitedFacebookUser = async ({ token, appId }: { token: string; appId: string | undefined }) => {
        try {
            const jwksClient = jwksLocalClient({
                jwksUri: "https://www.facebook.com/.well-known/oauth/openid/jwks"
            });
            return new Promise((resolve, reject) => {
                jwt.verify(
                    token,
                    async (header: any, callback: any) => {
                        const key = await jwksClient.getSigningKey(header.kid);
                        const signingKey = key.getPublicKey();
                        callback(null, signingKey);
                    },
                    {
                        algorithms: ["RS256"],
                        audience: appId,
                        issuer: "https://www.facebook.com"
                    },
                    (err: any, decoded: any) => {
                        if (err) return reject(err);
                        const decodedData = decoded as any;
                        if (!decodedData.sub) {
                            return reject(new Error("Invalid token (missing sub)"));
                        }
                        resolve({
                            facebookUserId: decodedData.sub,
                            facebookUserName: decodedData.name,
                            facebookEmail: decodedData.email
                        });
                    }
                );
            });
        } catch (error) {
            return null;
        }
    };

    getFacebookUser = async ({ token, appId }: { token: string; appId: string | undefined }) => {
        try {
            return await this.getStandardFacebookUser(token);
        } catch (error) {
            try {
                if (isAxiosError(error)) {
                    console.warn("Failed to get standard Facebook user, trying limited user");
                }
                return this.getLimitedFacebookUser({ token, appId });
            } catch (error) {
                return null;
            }
        }
    };

    verifySocialLogin = async (provider: string, accessToken: string, payloadIdentifier: string | null) => {
        try {
            let url = "";
            let response = null;
            switch (provider) {
                case Constants.SOCIAL_LOGIN.PROVIDER.APPLE:
                    const decoded = jwt.decode(accessToken, { complete: true });
                    if (!decoded || typeof decoded !== 'object' || !('header' in decoded)) {
                        return false;
                    }
                    const { header } = decoded as { header: { kid: string } };
                    const kid = header.kid;
                    const publicKey = (await this.appleKey(kid)).getPublicKey();
                    response = jwt.verify(accessToken, publicKey);
                    const { sub, email } = response as { sub: string, email: string };
                    if (email === payloadIdentifier) return { email, sub };
                    if (!email && sub) return { sub, payloadIdentifier };
                    return false;
                case Constants.SOCIAL_LOGIN.PROVIDER.GOOGLE:
                    url = `${process.env.GOOGLE_LOGIN_END_POINT}=${accessToken}`;
                    response = await axios.get(url);
                    if (response?.status === 200) {
                        const { email, email_verified, sub, exp } = response.data;
                        if (Date.now() / 1000 > exp) return false;
                        if (email_verified && email === payloadIdentifier) return { sub, email };
                        if ((!email_verified || !email) && sub) return { sub, payloadIdentifier };
                    }
                    return false;
                case Constants.SOCIAL_LOGIN.PROVIDER.FACEBOOK:
                    const data: any = await this.getFacebookUser({ token: accessToken, appId: process.env.FACEBOOK_APP_ID });
                    if (data?.facebookEmail) {
                        const facebookEmail = data.facebookEmail;
                        if (facebookEmail === payloadIdentifier) return { email: facebookEmail, sub: data.facebookUserId };
                    }
                    else if (data.facebookUserId) {
                        return { payloadIdentifier, sub: data.facebookUserId };
                    }
                    return false;
                default: {
                    return false;
                }
            }
        } catch (err) {
            return false;
        }
    };

    socialLogin = async (provider: string, name: string | null, countryCode: string | null, verifyAccess: { sub: string; email?: string; phone?: string }) => {
        try {
            let userId = null;
            let email: string | null = verifyAccess.email ?? null;
            let phone: string | null = verifyAccess.phone ?? null;
            let username: string | null = verifyAccess.sub ?? null;
            let userData = null;
            let userExists = null;
            if (email) {
                userExists = await this.userDao.getUserByEmail(email, null, false, true);
            } else if (phone) {
                userExists = await this.userDao.getUserByMobileNo(countryCode, phone, null, false, true);
            }
            else if (username) {
                userExists = await this.userDao.getUserByUsername(username, null, false, true);
            }
            if (userExists) {
                if (userExists.status == USER.STATUS.INACTIVE) {
                    throw new AppError(400, 'INACTIVE_USER', {});
                }
                userId = userExists.id;
                userData = await this.loginProcess(userId);
            } else {
                const signUpData: SignUpEmailObject = { email, username, mobile: phone, countryCode, name, password: null, referralCode: null, accountId: this.accountId };
                const createAccountInput: UserCreateServiceInput = { signupData: signUpData };
                userData = await this.createAccount(createAccountInput);
                userId = userData.id;
            }
            await this.userDao.updateUser(provider, verifyAccess.sub, userId);
            return userData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getMenstruationData = async (userId: number): Promise<MenstruationData> => {
        try {
            let menstruationData = await this.userDao.getMenstruationData(userId);
            return menstruationData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    saveUserMenstruationDetail = async (userId: number, accountId: number | null, lastCommencingDate: Date, menstruationCycle: number, menstruationDuration: number): Promise<void> => {
        try {
            await this.userDao.saveMenstruationDetail(userId, accountId!, lastCommencingDate, menstruationCycle, menstruationDuration);
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    };


    registerUserDevice = async (input: UserRegisterUserDeviceServiceInput): Promise<UserDeviceInterface | null> => {
        try {
            const registerUserDeviceDaoInput: UserRegisterUserDeviceDaoInput = { userId: this.userId!, accountId: this.accountId, deviceType: input.deviceType, device: input.device };
            const registerDevice = await this.userDao.registerUserDevice(registerUserDeviceDaoInput, {});
            if (!registerDevice) {
                throw new AppError(404, 'USER_DEVICE_NOT_REGISTERED', this.userId!);
            }
            return registerDevice;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    generateChangeEmailToken = async (changeEmailRequest: changeEmailRequest) => {
        try {
            let accountExists: boolean = await this.userDao.accountExists(changeEmailRequest.email, null, null);
            if (!accountExists) {
                // create a chnage email verification token 
                let verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : Common.generateCode(+process.env.CODE_LENGTH!, 'number');
                let changeEmailToken = Token.signToken('changeEmail', { ...changeEmailRequest, userId: this.userId, accountId: this.accountId, verificationCode: verificationCode });
                let token: TokenObject = { type: 'changeEmail', entityValue: changeEmailRequest.email, accountId: this.accountId, userId: this.userId, token: changeEmailToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                await this.tokenService.createToken(token);
                // send email to user to with verification code
                if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                    // this.emailTemplateService.sendEmailTemplate('change-email', [`"${accountExists.name}" <${changeEmailRequest.email}>`], { name: signUpRequest.name, "verification-code": verificationCode })
                }
                return { changeEmailToken: changeEmailToken }
            } else {
                let errorDetails = {};
                errorDetails = { ...errorDetails, email: 'EMAIL_ALREADY_IN_USE' }
                throw new AppError(400, 'EMAIL_ALREADY_IN_USE', errorDetails);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    generateChangeMobileToken = async (changeMobileRequest: changeMobileRequest) => {
        try {
            let accountExists: boolean = await this.userDao.accountExists(null, changeMobileRequest.countryCode, changeMobileRequest.mobile);
            if (!accountExists) {
                // create a chnage email verification token 
                let verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : await Common.sendOTP(changeMobileRequest.countryCode + changeMobileRequest.mobile, process.env.SMS_GATEWAY!);
                let changeEmailToken = Token.signToken('changeMobile', { ...changeMobileRequest, userId: this.userId, accountId: this.accountId, verificationCode: verificationCode });
                let token: TokenObject = { type: 'changeMobile', entityValue: changeMobileRequest.mobile, accountId: this.accountId, userId: this.userId, token: changeEmailToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE }
                await this.tokenService.createToken(token);
                // send email to user to with verification code
                if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                    // this.emailTemplateService.sendEmailTemplate('change-email', [`"${accountExists.name}" <${changeEmailRequest.email}>`], { name: signUpRequest.name, "verification-code": verificationCode })
                }
                return { changeMobileToken: changeEmailToken }
            } else {
                let errorDetails = {};
                errorDetails = { ...errorDetails, email: 'MOBILE_ALREADY_IN_USE' }
                throw new AppError(400, 'MOBILE_ALREADY_IN_USE', errorDetails);
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    generateMobileLoginToken = async (mobileLoginRequest: mobileLoginRequest) => {
        try {
            const { countryCode, mobile } = mobileLoginRequest;
            // Check if account exists
            const accountExists = await this.userDao.accountExists(null, countryCode, mobile);
            // Generate verification code
            const verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : await Common.sendOTP(countryCode + mobile, process.env.SMS_GATEWAY!);
            let userId = this.userId;
            // If account exists, get user ID
            if (accountExists) {
                const user = await this.userDao.getUserByMobileNo(countryCode, mobile, null, false, false);
                userId = user?.id!;
            }
            const mobileLoginToken = Token.signToken('mobileLogin', { ...mobileLoginRequest, userId, accountId: this.accountId, verificationCode });
            const token: TokenObject = { type: 'mobileLogin', entityValue: mobile, accountId: this.accountId, userId, token: mobileLoginToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE };
            await this.tokenService.createToken(token);
            // Optional: send verification code via email/SMS
            if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                // this.emailTemplateService.sendEmailTemplate('mobile-login', [`"${user?.name}" <${user?.email}>`], { "verification-code": verificationCode });
            }
            return { mobileLoginToken, existingUser: accountExists };

        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    };

    deleteAccount = async (countryCode: string, mobile: string) => {
        try {
            const accountExists = await this.userDao.accountExists(null, countryCode, mobile);
            if (!accountExists) {
                throw new AppError(404, 'INVALID_USER', {});
            }
            // Generate verification code
            const verificationCode = +process.env.ENABLE_MASTER_VERIFICATION! ? process.env.MASTER_CODE! : await Common.sendOTP(countryCode + mobile, process.env.SMS_GATEWAY!);

            const user = await this.userDao.getUserByMobileNo(countryCode, mobile, null, false, false);
            const userId = user?.id!;

            const deleteAccountToken = Token.signToken('deleteAccount', { userId: userId, accountId: this.accountId, verificationCode, countryCode: countryCode, mobile: mobile });
            const token: TokenObject = { type: 'deleteAccount', entityValue: mobile, accountId: this.accountId, userId: userId, token: deleteAccountToken, code: verificationCode, allowedAttempts: +process.env.ALLOWED_VERIFICATION_ATTEMPTS!, status: TOKEN.STATUS.ACTIVE };
            await this.tokenService.createToken(token);
            // Optional: send verification code via email/SMS
            if (!+process.env.ENABLE_MASTER_VERIFICATION!) {
                // this.emailTemplateService.sendEmailTemplate('mobile-login', [`"${user?.name}" <${user?.email}>`], { "verification-code": verificationCode });
            }
            return { deleteAccountToken };

        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    };
    deleteAuthorizeUserAccount = async (userId: number) => {
        try {
            const accountExists = await this.userDao.getUserById(userId, false);
            if (accountExists) {
                const { countryCode, mobile } = accountExists;
                const deleteUserInput: UserDeleteServiceInput = { userId, countryCode, mobile };
                return this.deleteUser(deleteUserInput)
            }
            if (!accountExists) {
                throw new AppError(404, 'INVALID_USER', {});
            }
        } catch (err) {
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    };

    updateReferralPoints = async (userId: number) => {
        try {
            const updatedPoints = await this.userDao.updateReferralPoints(userId)
            return updatedPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateRedeemedPromptionalPoints = async (userId: number, amount: number) => {
        try {
            const updatedPoints = await this.userDao.updateRedeemedPromptionalPoints(userId, amount)
            return updatedPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    updateRedeemedPoints = async (userId: number, points: number) => {
        try {
            const updatedPoints = await this.userDao.updateRedeemedPoints(userId, points);
            return updatedPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    applyReferral = async (input: UserApplyReferralServiceInput) => {
        try {
            const transaction = await sequelize.transaction();
            let updatedPoints;
            try {
                const applyReferralDaoInput: UserApplyReferralDaoInput = { userId: this.userId!, referralCode: input.referralCode };
                updatedPoints = await this.userDao.applyReferral(applyReferralDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }
            return updatedPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    fetchRedeemedPoints = async () => {
        try {
            const points = await this.userDao.fetchRedeemedPoints();
            return points;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    securityDeclaration = async () => {
        try {
            const updatedPoints = await this.userDao.securityDeclaration();
            return updatedPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
