import { Sequelize, WhereOptions, FindOptions, literal, fn, col, Op, where } from "sequelize";
import { AppError } from "../../utils/errors";
import Models, { sequelize } from "../models";
import { USER, PAYMENT, LEAD } from "../config/constants"
import { Common } from "../../utils/common"
import { User } from "../models/User";
import { Json } from "sequelize/types/utils";
import Moment from "moment-timezone";

const securityAttributes = ['password']; //['password', 'secret2FA']
const userAttributes: AttributeElement[] = ['id', 'accountId', 'email', 'username', 'countryCode', 'mobile', 'status', 'createdAt', 'updatedAt',];
const userAllAttributes: AttributeElement[] = ['id', 'accountId', 'email', 'username', 'status'];
const profileAttributes: AttributeElement[] = ['name', 'referralCode', 'sessionCount', 'accuracy', 'obesity', 'diabetic', 'promptionalPoints', 'redeemedPromptionalPoints', 'referralPoints', 'redeemedReferralPoints', [literal(`CAST(${process.env.REDEEM_POINTS} AS UNSIGNED)`), 'maxDiscount'], 'presentRankSince', 'presentCompany', 'presentCompanySince', 'aboutMe', 'profileImageId', 'areaOfExpertise', 'nationality', 'followersCount', 'followingCount', 'socialMediaLinks', 'dob'];
const countAttributes: AttributeElement[] = ['questionCount', 'answerCount', 'answerCommentCount', 'postCommentCount', 'seaQALikesCount'];
const userAccountAttributes: AttributeElement[] = [[literal('`userAccounts->refAccount`.`id`'), 'id'], [literal('`userAccounts->refAccount`.`name`'), 'name'], [literal('`userAccounts->refAccount`.`key`'), 'key']]
const userSubscriptionAttributes: AttributeElement[] = ["id", "currentPeriodStart", "currentPeriodEnd", "planData", "status"];
const profileImageAttributes: AttributeElement[] = ['id', 'fileName', 'uniqueName', [fn('CONCAT', process.env.PROTOCOL, '://', process.env.API_HOST, "/attachment/", literal('`userProfile->profileImage`.`unique_name`')), 'filePath']];
const userSettingAttributes: AttributeElement[] = ['twoFactorAuthentication', 'temporaryPassword', 'notificationCount', 'selectedTheme', 'profileQuestionnaireCompleted', 'profileDetailedQuestionnaireCompleted', 'interestSelected', 'isPremium', 'securityDeclaration'];
const UserRoleAttributes: AttributeElement[] = ['id', 'code', [literal('(case when `userRoles->content`.name is not null then `userRoles->content`.name else `userRoles->defaultContent`.name END)'), 'name']]
const buildRankAttributes = (parentAlias: 'userProfile' | 'follower->userProfile' | 'following->userProfile' | 'referredUser->userProfile'): AttributeElement[] => [
    'id',
    [literal(`\`${parentAlias}->rank->content\`.\`name\``), 'name'],
    [literal(`\`${parentAlias}->rank->parentCategory\`.\`code\``), 'category']
];

const buildReferredUserAttributes = (alias: 'follower' | 'following' | 'referredUser'): AttributeElement[] => [
    'id', 'onlineStatus',
    [literal(`\`${alias}->userProfile\`.\`name\``), 'name'],
    [
        literal(`(
              SELECT JSON_OBJECT(
                'id', a.id,
                'fileName', a.file_name,
                'uniqueName', a.unique_name,
                'filePath', CONCAT('${process.env.PROTOCOL}://', '${process.env.API_HOST}', '/attachment/', a.unique_name),
                'cdnUrl', CONCAT('${process.env.CDN_PATH}', '/attachment/', a.unique_name)
              )
              FROM user_profile as up
              JOIN attachments as a ON up.profile_image_id = a.id
              WHERE up.user_id = ${alias}.id
              LIMIT 1
            )`),
        'profileImage'
    ]
];

const userInterestAttributes: AttributeElement[] = [
    "code",
    "status",
    "createdAt",
    "updatedAt",
    [literal("(case when `userInterests->content`.name is not null then `userInterests->content`.name else `userInterests->defaultContent`.name END)"), "name"],
    [
        literal(
            "(case when `userInterests->content`.description is not null then `userInterests->content`.description else `userInterests->defaultContent`.description END)"
        ),
        "description",
    ],
    [
        literal(
            "(case when `userInterests->content`.description_text is not null then `userInterests->content`.description_text else `userInterests->defaultContent`.description_text END)"
        ),
        "descriptionText",
    ],
];

const RolePermissionAttributes: AttributeElement[] = ['code']

export class UserDao {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    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
    }
    private includeAssociations = (fullInfo: boolean = false, roleFilter: WhereOptions | null = null, includeInterests: boolean = false, includeSettings: boolean = true, deviceFilter: WhereOptions | null = null, includeCounts: boolean = false, settingFilter: WhereOptions | null = null) => {
        let includeModels: IncludeOption[] = [
            {
                attributes: includeCounts ? [...profileAttributes, ...countAttributes] : profileAttributes,
                model: Models.UserProfile, as: 'userProfile',
                include: [
                    { model: Models.Attachment, as: "profileImage", attributes: profileImageAttributes },
                    {
                        model: Models.Category, as: "rank", attributes: buildRankAttributes('userProfile'),
                        include: [
                            { model: Models.CategoryContent, as: 'content', attributes: [] },
                            { model: Models.CategoryContent, as: 'defaultContent', attributes: [] },
                            { model: Models.Category, as: 'parentCategory', attributes: [] }
                        ]
                    }
                ]
            }
        ]
        if (includeSettings) {
            const settingInclude: any = { attributes: userSettingAttributes, model: Models.UserSetting, as: 'userSetting' };
            if (settingFilter) {
                settingInclude.where = settingFilter;
                settingInclude.required = true;
            }
            includeModels.push(settingInclude);
        }
        if (roleFilter) {
            includeModels.push(
                { attributes: [], where: roleFilter, model: Models.UserRole, as: "userRole", required: true },
            )
        }
        if (deviceFilter) {
            includeModels.push(
                { attributes: [], where: deviceFilter, model: Models.UserDevice, as: "userDevice", required: true },
            )
        }
        if (fullInfo) {
            includeModels.push(
                { attributes: userAccountAttributes, model: Models.UserAccount, as: 'userAccounts', include: [{ attributes: [], model: Models.Account, as: 'refAccount' }] },
                {
                    attributes: userSubscriptionAttributes,
                    order: [["id", "desc"]],
                    required: false,
                    where: {
                        id: {
                            [Op.eq]: Sequelize.literal('( SELECT id FROM subscriptions AS us WHERE us.user_id = `User`.`id` and status IN (1,2,5,7) and us.deleted_at IS NULL ORDER BY created_at DESC LIMIT 1)'),
                        }
                    },
                    model: Models.Subscription,
                    as: 'userSubscription'
                }
            )
        }
        return includeModels;
    }

    checkDeletedMobile = async (mobile: string) => {
        const deletedUser = await Models.User.findOne({
            attributes: ["id"],
            where: where(fn('SUBSTRING_INDEX', col('mobile'), '-', 1), mobile),
            paranoid: false
        });

        console.log(deletedUser, " ================= deleted user")

        if (deletedUser) return true;
        else return false;
    }

    // get user by email
    getUserByEmail = async (email: string, status: number | null, withSecurityAttributes: boolean = false, returnMode: boolean = false) => {
        try {
            let returnAttributes = userAttributes.concat(withSecurityAttributes ? securityAttributes : [])
            let where: WhereOptions = { email: email, accountId: this.accountId };
            if (status) {
                where.status = status;
            }
            let user = await Models.User.findOne({ attributes: returnAttributes, where: where, include: this.includeAssociations(false) });
            if (user) {
                return JSON.parse(JSON.stringify(user));
            } else {
                if (returnMode) return null;
                throw new AppError(400, 'INVALID_EMAIL_OR_PASSWORD', { email: 'INVALID_EMAIL_OR_PASSWORD' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    validateEmail = async (email: string, status: number | null): Promise<boolean> => {
        try {
            const where: WhereOptions = { email, accountId: this.accountId };
            if (status !== null && status !== undefined) {
                (where as any).status = status;
            }
            const user = await Models.User.findOne({ where });
            return !!user; // true if found, false if not
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    };

    validateMobile = async (countryCode: string, mobile: string, status: number | null): Promise<boolean> => {
        try {
            const where: WhereOptions = { countryCode, mobile, accountId: this.accountId };
            if (status !== null && status !== undefined) {
                (where as any).status = status;
            }
            const user = await Models.User.findOne({ where });
            return !!user; // true if found, false if not
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    };

    // get user by username
    getUserByUsername = async (username: string, status: number | null, withSecurityAttributes: boolean = false, returnMode: boolean = false) => {
        try {
            let returnAttributes = userAttributes.concat(withSecurityAttributes ? securityAttributes : [])
            let where: WhereOptions = { username: username, accountId: this.accountId };
            if (status) {
                where.status = status;
            }
            let user = await Models.User.findOne({ attributes: returnAttributes, where: where, include: this.includeAssociations(false) });
            if (user) {
                return JSON.parse(JSON.stringify(user));
            } else {
                if (returnMode) return null;
                throw new AppError(400, 'INVALID_USERNAME_OR_PASSWORD', { username: 'INVALID_USERNAME_OR_PASSWORD' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get user by mobile no
    getUserByMobileNo = async (countryCode: string | null, mobile: string, status: number | null, withSecurityAttributes: boolean = false, returnMode: boolean = false) => {
        try {
            let returnAttributes = userAttributes.concat(withSecurityAttributes ? securityAttributes : [])
            let where: WhereOptions = { countryCode: countryCode, mobile: mobile, accountId: this.accountId };
            if (status) {
                where.status = status;
            }
            let user = await Models.User.findOne({ attributes: returnAttributes, where: where, include: this.includeAssociations(false) });
            if (user) {
                return JSON.parse(JSON.stringify(user));
            } else {
                if (returnMode) return null;
                throw new AppError(400, 'INVALID_MOBILE_OR_PASSWORD', { username: 'INVALID_MOBILE_OR_PASSWORD' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUserSettings = async (userId: number, settingsAttributes: AttributeElement[] | boolean = false) => {
        try {
            let attributesArray: AttributeElement[] = []
            if (settingsAttributes && typeof settingsAttributes == 'object') {
                attributesArray = settingsAttributes;
            }
            const queryOptions: FindOptions = {
                where: { userId: userId },
                ...(attributesArray.length > 0 && { attributes: attributesArray })
            };
            const settings = await Models.UserSetting.findOne(queryOptions);
            return settings;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUserAuthData = async (userId: number, languageId: number, includeCounts: boolean = false) => {
        try {
            let user = await Models.User.findOne({ attributes: userAttributes, where: { id: userId, accountId: this.accountId }, include: this.includeAssociations(true, null, false, true, null, includeCounts) });
            if (user) {
                let userData = user.get({ plain: true });
                let userRoles: userRolesObject[] = await this.getUserRoles([user.id], languageId);
                if (userRoles.length) {
                    userData.userRoles = userRoles[0].userRoles as unknown as userRoleObject[];
                } else {
                    userData.userRoles = [];
                }
                return userData;
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    accountExists = async (email: string | null, countryCode: string | null, mobile: string | null): Promise<boolean> => {
        try {
            let where: WhereOptions = [];
            if (email) {
                where.push({ email: email })
            }
            if (countryCode && mobile) {
                where.push({ countryCode: countryCode, mobile: mobile })
            }
            let user = await Models.User.findOne({ where: { [Op.or]: where, accountId: this.accountId } })
            if (user) {
                return true
            } else {
                return false
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    accountExistsForDiffUser = async (email: string | null, countryCode: string | null, mobile: string | null, userId: null | number = null): Promise<boolean> => {
        try {
            let where: WhereOptions = [];
            if (email) {
                where.push({ email: email })
            }
            if (countryCode && mobile) {
                where.push({ countryCode: countryCode, mobile: mobile })
            }

            let user = await Models.User.findOne({ where: { [Op.or]: where, accountId: this.accountId, ...(userId ? { id: { [Op.ne]: userId } } : {}) } })
            if (user) {
                return true
            } else {
                return false
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    createUser = async (data: UserCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { userInfo, roles } = data;
            const { transaction } = options;
            let newUser = await Models.User.create({
                id: userInfo.id ?? undefined,
                accountId: userInfo.accountId ? userInfo.accountId : null,
                email: userInfo.email ? userInfo.email : null,
                username: userInfo.username ? userInfo.username : null,
                countryCode: userInfo.countryCode ? userInfo.countryCode : null,
                password: userInfo.password,
                mobile: userInfo.mobile ? userInfo.mobile : null,
                status: USER.STATUS.ACTIVE,
                userProfile: {
                    name: userInfo.name,
                    firstName: userInfo.firstName ?? null,
                    lastName: userInfo.lastName ?? null,
                    rankId: userInfo.rankId ?? null,
                    presentRankSince: userInfo.presentRankSince ?? null,
                    presentCompany: userInfo.presentCompany ?? null,
                    presentCompanySince: userInfo.presentCompanySince ?? null,
                    aboutMe: userInfo.aboutMe ?? null,
                    nationality: userInfo.nationality ?? null,
                    profileImageId: userInfo.profileImageId ?? null,
                    areaOfExpertise: userInfo.areaOfExpertise ?? null,
                    dob: userInfo.dob ?? null,
                    userType: userInfo.userType ?? null,
                    followersCount: userInfo.followersCount ?? 0,
                    followingCount: userInfo.followingCount ?? 0,
                    socialMediaLinks: userInfo.socialMediaLinks ?? null
                },
                userSetting: { twoTactorAuthentication: false, temporaryPassword: false },
                userAccount: { accountId: userInfo.accountId, isDefault: true }
            }, {
                include: [
                    { model: Models.UserProfile, as: 'userProfile' },
                    { model: Models.UserSetting, as: 'userSetting' },
                    { model: Models.UserAccount, as: 'userAccount' }
                ], transaction: transaction
            });
            //newUser.setUserRoles(roles, { transaction: transaction });
            await newUser.setUserRoles(roles, { transaction: transaction });
            await Models.UserProfile.update({ referralCode: `TRANSFORM-${newUser.id}` }, { where: { userId: newUser.id }, transaction: transaction });
            if (userInfo.referralCode) {
                let referrer = await Models.UserProfile.findOne({ attributes: ['userId'], where: { referralCode: userInfo.referralCode } })
                if (referrer) {
                    await Models.UserReferral.create({ userId: newUser.id, referrerId: referrer.userId, points: process.env.REFERRAL_POINTS }, { transaction: transaction });
                }
            }
            return newUser.id;
        } catch (err: any) {
            if (err.name === 'SequelizeUniqueConstraintError') {
                throw new AppError(400, 'USER_ALREADY_EXISTS', { message: err.errors[0]?.message || 'USER_ALREADY_EXISTS' });
            }
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    setPassword = async (data: UserSetPasswordDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { userId, password } = data;
            await Models.User.update({ password }, { where: { id: userId }, transaction: options.transaction })
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUserDetails = async (data: UserUpdateUserDetailsDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { id, userInfo, roles } = data;
            const { transaction } = options;

            const user = await Models.User.findOne({ where: { id }, transaction });

            if (!user) throw new AppError(404, "USER_NOT_FOUND", {});
            await Models.User.update({
                accountId: userInfo.accountId ? userInfo.accountId : null,
                email: userInfo.email ? userInfo.email : null,
                username: userInfo.username ? userInfo.username : null,
                countryCode: userInfo.countryCode ? userInfo.countryCode : null,
                password: userInfo.password,
                mobile: userInfo.mobile ? userInfo.mobile : null
            }, {
                transaction: transaction, where: { id: id }
            });

            console.log(roles, " ============== roles")


            const profileUpdateData: any = { name: userInfo.name };
            if (userInfo.socialMediaLinks !== undefined) {
                profileUpdateData.socialMediaLinks = userInfo.socialMediaLinks;
            }
            if (userInfo.profileImageId !== undefined) {
                profileUpdateData.profileImageId = userInfo.profileImageId;
            }
            if (userInfo.aboutMe !== undefined) {
                profileUpdateData.aboutMe = userInfo.aboutMe;
            }
            if (userInfo.nationality !== undefined) {
                profileUpdateData.nationality = userInfo.nationality;
            }
            await Models.UserProfile.update(profileUpdateData, { transaction, where: { userId: id } });
            await Models.UserRole.destroy({ where: { userId: id }, force: true, transaction });
            await user.setUserRoles(roles, { transaction: transaction });
            return id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    setAccountPassword = async (id: number, password: string): Promise<void> => {
        try {
            await Models.User.update({ password: password }, { where: { id: id } });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
    // Build order by clause
    private buildOrderBy = (field: string, direction: string = 'desc') => {
        switch (field) {
            case 'sort-order':
                return [['sortOrder', direction]];
            case 'name':
                return [[literal(`name`), direction]];
            case 'id':
                return [['id', direction]];
            default:
                // Fallback to default order
                return [
                    ['id', 'ASC']
                ];
        }
    }
    // associate user roles to user
    private getUserRoles = async (userIds: number[], languageId: number): Promise<userRolesObject[]> => {
        try {
            const users = await Models.User.findAll({
                attributes: ['id'],
                where: {
                    accountId: this.accountId,
                    id: { [Op.in]: userIds },
                },
                include: [
                    {
                        attributes: UserRoleAttributes,
                        model: Models.Role,
                        as: 'userRoles',
                        through: { attributes: [] },
                        include: [
                            {
                                attributes: [],
                                model: Models.RoleContent,
                                as: 'content',
                                include: [{ attributes: [], model: Models.Language, as: 'language', where: { id: languageId } }]
                            },
                            {
                                attributes: [],
                                model: Models.RoleContent,
                                as: 'defaultContent',
                                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
                            },
                            {
                                attributes: ['code'],
                                model: Models.Permission,
                                as: 'rolePermissions',
                                through: { attributes: [] }
                            }
                        ],
                    },
                ],
                // preserve order using FIELD function
                order: [
                    [Sequelize.literal(`FIELD(\`User\`.id, ${userIds.join(',')})`)],
                ],
            });
            const userRolesArray: userRolesObject[] = JSON.parse(JSON.stringify(users));
            return userRolesArray;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(400, 'ERROR_WHILE_GETTING_ROLES', err);
        }
    }
    // list category types
    getUserList = async (languageId: number, page: number, perPage: number, searchText: string | null, sortBy: string, sortDirection: string, roleId: number | number[] | null = null, status: number | null = null, userIds: number[] | null = null, deviceType: string | null | undefined = null, higherOrEqualRank: boolean | null | undefined = null, isPremium: boolean | null | undefined = null): Promise<UserPaginatedData> => {
        try {
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = { accountId: this.accountId, [Op.and]: [] };
            const andConditions = where[Op.and] as any[];
            let roleFilter: WhereOptions | null = null;
            let deviceFilter: WhereOptions | null = null;
            let settingFilter: WhereOptions | null = null;
            const orBlock: any[] = [];
            let alphaString = '';
            let specialString = '';
            if (searchText) {
                let searchData: searchText = Common.prepareSearchText(searchText);
                if (searchData.alphaString) {
                    alphaString = '*' + searchData.alphaString + '*'
                } if (searchData.specialString) {
                    specialString = searchData.specialString
                }
                if (alphaString) {
                    orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                    orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                } if (specialString) {
                    orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:specialString IN BOOLEAN MODE)'));
                    orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                }
            }
            if (roleId) {
                roleFilter = { roleId: roleId };
            }
            if (deviceType) {
                deviceFilter = { deviceType: deviceType, status: 1 };
            }
            if (isPremium !== null && isPremium !== undefined) {
                settingFilter = { isPremium: isPremium };
            }
            if (status != null) {
                where[Op.and].push({ status: status });
            }
            if (orBlock.length > 0) {
                where[Op.and].push({ [Op.or]: orBlock })
            }
            if (userIds && userIds.length > 0) {
                where[Op.and].push({ id: userIds })
            }

            if (higherOrEqualRank && this.userId) {
                const loggedInUser = await Models.UserProfile.findOne({ where: { userId: this.userId }, attributes: ['rankId'] });
                if (loggedInUser && loggedInUser.rankId) {
                    where[Op.and].push(
                        Sequelize.literal(`EXISTS (
                            SELECT 1 FROM user_profile up
                            WHERE up.user_id = \`User\`.id
                            AND (up.rank_id <= ${loggedInUser.rankId} OR up.rank_id IN (65, 66))
                            AND up.rank_id IS NOT NULL
                        )`)
                    );
                    where[Op.and].push({ id: { [Op.ne]: this.userId } });
                }
            }

            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const users = await Models.User.findAndCountAll({
                attributes: userAttributes,
                where: where,
                include: this.includeAssociations(true, roleFilter, false, true, deviceFilter, true, settingFilter),
                replacements: { alphaString: alphaString, specialString: specialString },
                offset: offset,
                limit: perPage,
                subQuery: false,
                order: orderBy,
            });
            console.log("DEBUG: users.count =", users.count, "type =", typeof users.count);
            // associte roles connected with belongs to many
            if (users.rows.length) {
                const userIds = users.rows.map((u: any) => u.id);
                let userRoleArray: userRolesObject[] = await this.getUserRoles(userIds, languageId);
                // Create a Map from userId to userRoles array
                const userRolesMap = new Map<number, userRolesObject['userRoles']>();
                userRoleArray.forEach((user) => {
                    userRolesMap.set(user.id, user.userRoles);
                });
                // Merge userRoles into users.rows
                const userWithRoles = users.rows.map((user: any) => {
                    return {
                        ...user.get({ plain: true }),
                        userRoles: userRolesMap.get(user.id) || [],
                    };
                });
                users.rows = userWithRoles;
            }
            return {
                totalRecords: users.count,
                data: JSON.parse(JSON.stringify(users.rows)),
                totalPages: Math.ceil(users.count / perPage),
                page: page,
                perPage: perPage
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    referralList = async (page: number, perPage: number, userId: number | null) => {
        try {
            let offset = (page - 1) * perPage;
            const sortBy = "id";
            const sortDirection = "desc"
            const orderBy = this.buildOrderBy(sortBy, sortDirection);

            const users = await Models.UserReferral.findAndCountAll({
                attributes: ["id", "points", "status", "createdAt", "updatedAt"],
                include: [{
                    attributes: buildReferredUserAttributes('referredUser'),
                    model: Models.User,
                    as: 'referredUser',
                    include: [{
                        attributes: [], model: Models.UserProfile, as: 'userProfile', include: [{ model: Models.Attachment, as: "profileImage" }, {
                            model: Models.Category, as: 'rank', attributes: buildRankAttributes('referredUser->userProfile'), include: [
                                { model: Models.CategoryContent, as: 'content', attributes: [] },
                                { model: Models.CategoryContent, as: 'defaultContent', attributes: [] },
                                { model: Models.Category, as: 'parentCategory', attributes: [] }
                            ]
                        }]
                    }]
                },],
                where: { referrerId: userId ?? this.userId },
                offset: offset,
                limit: perPage,
                subQuery: false,
                order: orderBy,
            });

            return {
                totalRecords: users.count,
                data: JSON.parse(JSON.stringify(users.rows)),
                totalPages: Math.ceil(users.count / perPage),
                page: page,
                perPage: perPage
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    toggleFollow = async (followerId: number, followingId: number) => {
        const transaction = await sequelize.transaction();
        try {
            if (followerId === followingId) throw new AppError(400, "CANNOT_FOLLOW_SELF", {});

            const existingFollow = await Models.UserFollower.findOne({
                where: { followerId, followingId },
                paranoid: false,
                transaction
            });

            if (existingFollow && !existingFollow.deletedAt) {
                // Unfollow by soft-deleting the relation
                await existingFollow.destroy({ transaction });
                await Models.UserProfile.decrement('followersCount', { by: 1, where: { userId: followingId }, transaction });
                await Models.UserProfile.decrement('followingCount', { by: 1, where: { userId: followerId }, transaction });
                await transaction.commit();
                return { isFollowing: false };
            } else {
                // Follow by restoring a soft-deleted relation when present, otherwise create a new one
                if (existingFollow) {
                    await existingFollow.restore({ transaction });
                } else {
                    await Models.UserFollower.create({ followerId, followingId }, { transaction });
                }
                await Models.UserProfile.increment('followersCount', { by: 1, where: { userId: followingId }, transaction });
                await Models.UserProfile.increment('followingCount', { by: 1, where: { userId: followerId }, transaction });
                await transaction.commit();
                return { isFollowing: true };
            }
        } catch (err) {
            await transaction.rollback();
            if (err instanceof Error && err.name === 'SequelizeUniqueConstraintError') {
                const row = await Models.UserFollower.findOne({
                    where: { followerId, followingId },
                    paranoid: false
                });
                if (row && row.deletedAt) {
                    const retryTransaction = await sequelize.transaction();
                    try {
                        await row.restore({ transaction: retryTransaction });
                        await Models.UserProfile.increment('followersCount', { by: 1, where: { userId: followingId }, transaction: retryTransaction });
                        await Models.UserProfile.increment('followingCount', { by: 1, where: { userId: followerId }, transaction: retryTransaction });
                        await retryTransaction.commit();
                        return { isFollowing: true };
                    } catch (retryErr) {
                        await retryTransaction.rollback();
                        if (retryErr instanceof AppError) throw retryErr;
                        throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', retryErr);
                    }
                }
            }
            if (err instanceof AppError) throw err;
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    followersList = async (userId: number, page: number, perPage: number, searchText?: string) => {
        try {
            let offset = (page - 1) * perPage;
            
            let topLevelWhere: any = { followingId: userId };
            if (searchText) {
                topLevelWhere[Op.and] = [{
                    [Op.or]: [
                        { '$follower.email$': { [Op.like]: `%${searchText}%` } },
                        { '$follower.mobile$': { [Op.like]: `%${searchText}%` } },
                        { '$follower.username$': { [Op.like]: `%${searchText}%` } },
                        { '$follower.userProfile.name$': { [Op.like]: `%${searchText}%` } }
                    ]
                }];
            }

            const users = await Models.UserFollower.findAndCountAll({
                attributes: ["id", "createdAt"],
                include: [{
                    attributes: buildReferredUserAttributes('follower'),
                    model: Models.User,
                    as: 'follower',
                    include: [{
                        model: Models.UserProfile, as: 'userProfile', include: [{ model: Models.Attachment, as: "profileImage" }, {
                            model: Models.Category, as: 'rank', attributes: buildRankAttributes('follower->userProfile'), include: [
                                { model: Models.CategoryContent, as: 'content', attributes: [] },
                                { model: Models.CategoryContent, as: 'defaultContent', attributes: [] },
                                { model: Models.Category, as: 'parentCategory', attributes: [] }
                            ]
                        }]
                    }]
                }],
                where: topLevelWhere,
                offset: offset,
                limit: perPage,
                subQuery: false,
                order: [['createdAt', 'DESC']],
            });
            let rows = users.rows.map((u: any) => u.get({ plain: true }));
            if (this.userId && rows.length > 0) {
                const followerIds = rows.map((u: any) => u.follower?.id).filter(Boolean);
                if (followerIds.length > 0) {
                    const followed = await Models.UserFollower.findAll({
                        where: { followerId: this.userId, followingId: followerIds },
                        attributes: ['followingId']
                    });
                    const followedByMeIds = followed.map((f: any) => f.followingId);
                    rows = rows.map((row: any) => {
                        if (row.follower) {
                            row.follower.isFollowedByMe = followedByMeIds.includes(row.follower.id);
                        }
                        return row;
                    });
                }
            }

            return {
                data: rows,
                totalRecords: users.count,
                page,
                perPage
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    followingList = async (userId: number, page: number, perPage: number, searchText?: string) => {
        try {
            let offset = (page - 1) * perPage;

            let topLevelWhere: any = { followerId: userId };
            if (searchText) {
                topLevelWhere[Op.and] = [{
                    [Op.or]: [
                        { '$following.email$': { [Op.like]: `%${searchText}%` } },
                        { '$following.mobile$': { [Op.like]: `%${searchText}%` } },
                        { '$following.username$': { [Op.like]: `%${searchText}%` } },
                        { '$following.userProfile.name$': { [Op.like]: `%${searchText}%` } }
                    ]
                }];
            }

            const users = await Models.UserFollower.findAndCountAll({
                attributes: ["id", "createdAt"],
                include: [{
                    attributes: buildReferredUserAttributes('following'),
                    model: Models.User,
                    as: 'following',
                    include: [{
                        model: Models.UserProfile, as: 'userProfile', include: [{ model: Models.Attachment, as: "profileImage" }, {
                            model: Models.Category, as: 'rank', attributes: buildRankAttributes('following->userProfile'), include: [
                                { model: Models.CategoryContent, as: 'content', attributes: [] },
                                { model: Models.CategoryContent, as: 'defaultContent', attributes: [] },
                                { model: Models.Category, as: 'parentCategory', attributes: [] }
                            ]
                        }]
                    }]
                }],
                where: topLevelWhere,
                offset: offset,
                limit: perPage,
                subQuery: false,
                order: [['createdAt', 'DESC']],
            });
            let rows = users.rows.map((u: any) => u.get({ plain: true }));
            if (this.userId && rows.length > 0) {
                const followingIds = rows.map((u: any) => u.following?.id).filter(Boolean);
                if (followingIds.length > 0) {
                    const followed = await Models.UserFollower.findAll({
                        where: { followerId: this.userId, followingId: followingIds },
                        attributes: ['followingId']
                    });
                    const followedByMeIds = followed.map((f: any) => f.followingId);
                    rows = rows.map((row: any) => {
                        if (row.following) {
                            row.following.isFollowedByMe = followedByMeIds.includes(row.following.id);
                        }
                        return row;
                    });
                }
            }

            return {
                data: rows,
                totalRecords: users.count,
                page,
                perPage
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUserListAll = async (listRequest: UserPaginatioinListRequestObject): Promise<UserPaginatedData> => {
        try {
            let where: WhereOptions & { [Op.and]: any[] } = { accountId: this.accountId, [Op.and]: [] };
            let roleFilter: WhereOptions | null = null;
            let deviceFilter: WhereOptions | null = null;
            const orBlock: any[] = [];
            let alphaString = '';
            let specialString = '';
            if (listRequest.searchText) {
                let searchData: searchText = Common.prepareSearchText(listRequest.searchText);
                if (searchData.alphaString) {
                    alphaString = '*' + searchData.alphaString + '*'
                } if (searchData.specialString) {
                    specialString = searchData.specialString
                }
                if (alphaString) {
                    orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                    orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                } if (specialString) {
                    orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:specialString IN BOOLEAN MODE)'));
                    orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                }
            }
            if (listRequest.deviceType) {
                deviceFilter = { deviceType: listRequest.deviceType, status: 1 };
            }
            if (listRequest.roleId) {
                roleFilter = { roleId: listRequest.roleId };
            }
            if (listRequest.status != null) {
                where[Op.and].push({ status: listRequest.status });
            }
            if (orBlock.length > 0) {
                where[Op.and].push({ [Op.or]: orBlock })
            }
            const orderBy = this.buildOrderBy(listRequest.sortBy, listRequest.sortDirection);
            const users = await Models.User.findAndCountAll({
                attributes: userAllAttributes,
                where: where,
                include: this.includeAssociations(false, roleFilter, false, false, deviceFilter),
                replacements: { alphaString: alphaString, specialString: specialString },
                limit: +process.env.LIST_ALL_LIMIT!,
                subQuery: false,
                order: orderBy,
            });

            return {
                totalRecords: users.count,
                data: JSON.parse(JSON.stringify(users.rows))
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUsersListByIds = async (userIds: number[]): Promise<UserPaginatedData> => {
        try {
            let where: WhereOptions = {
                status: USER.STATUS.ACTIVE, id: userIds
            };

            // let alphaString = '';
            // let specialString = '';

            // if (listRequest.searchText) {
            //     let searchData: searchText = Common.prepareSearchText(listRequest.searchText);
            //     if (searchData.alphaString) {
            //         alphaString = '*' + searchData.alphaString + '*'
            //     } if (searchData.specialString) {
            //         specialString = searchData.specialString
            //     }
            //     if (alphaString) {
            //         orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:alphaString IN BOOLEAN MODE)'));
            //         orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
            //     } if (specialString) {
            //         orBlock.push(Sequelize.literal('MATCH(`User`.`email`,`User`.`mobile`,`User`.`username`) AGAINST(:specialString IN BOOLEAN MODE)'));
            //         orBlock.push(Sequelize.literal('MATCH(`userProfile`.`name`) AGAINST(:alphaString IN BOOLEAN MODE)'));
            //     }
            // }

            // const orderBy = this.buildOrderBy(listRequest.sortBy, listRequest.sortDirection);
            const users = await Models.User.findAll({
                attributes: userAllAttributes,
                where: where,
                include: this.includeAssociations(false, null, false, false),
                // replacements: { alphaString: alphaString, specialString: specialString },
                limit: +process.env.LIST_ALL_LIMIT!,
                subQuery: false,
                // order: orderBy,
            });

            return {
                data: JSON.parse(JSON.stringify(users)),
                totalRecords: users.length
            } as unknown as UserPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUserProfile = async (fieldName: string, fieldValue: string | number | boolean): Promise<void> => {
        try {
            await Models.UserProfile.update({ [fieldName]: fieldValue }, { where: { userId: this.userId } });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUser = async (fieldName: string, fieldValue: string | number | boolean, userId: number): Promise<void> => {
        try {
            await Models.User.update({ [fieldName]: fieldValue }, { where: { id: userId, accountId: this.accountId } });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUserMobile = async (countryCode: string, mobile: string, userId: number): Promise<void> => {
        try {
            await Models.User.update({ countryCode, mobile }, { where: { id: userId, accountId: this.accountId } });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    deleteUser = async (data: UserDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { userId, mobile } = data;
            await Models.User.update({
                mobile: mobile + '-' + Moment().toISOString(),
                deletedAt: new Date()
            }, { where: { id: userId }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUserSetting = async (fieldName: string, fieldValue: string | number | boolean): Promise<void> => {
        try {
            await Models.UserSetting.update({ [fieldName]: fieldValue }, { where: { userId: this.userId } });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateUserProfileFields = async (data: UserUpdateUserProfileFieldsDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.UserProfile.update(data.fields, { where: { userId: this.userId }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    updateReferralCode = async (data: UserUpdateReferralCodeDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.UserProfile.update({ referralCode: data.referralCode }, { where: { userId: data.userId }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getUserById = async (userId: number, fullObject: boolean, includeCounts: boolean = false): Promise<User | null> => {
        try {
            let userAccessAttr: AttributeElement[] = userAttributes;
            let includeModels: IncludeOption[] = [];
            if (fullObject) {
                // userAccessAttr = userAttributes;
                includeModels = this.includeAssociations(fullObject, null, fullObject, true, null, includeCounts)
            }
            else {
                includeModels.push({
                    attributes: includeCounts ? [...profileAttributes, ...countAttributes] : profileAttributes,
                    model: Models.UserProfile, as: 'userProfile',
                    include: [
                        { model: Models.Attachment, as: "profileImage", attributes: profileImageAttributes },
                        {
                            model: Models.Category, as: "rank", attributes: buildRankAttributes('userProfile'),
                            include: [
                                { model: Models.CategoryContent, as: 'content', attributes: [] },
                                { model: Models.CategoryContent, as: 'defaultContent', attributes: [] },
                                { model: Models.Category, as: 'parentCategory', attributes: [] }
                            ]
                        }
                    ]
                });
            }

            return await Models.User.findOne({
                where: { id: userId },
                include: includeModels,
                attributes: userAccessAttr
            });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getMenstruationData = async (userId: number): Promise<MenstruationData> => {
        try {
            let menstruationData = await Models.UserProfile.findOne({ where: { userId: userId }, attributes: ['lastCommencingDate', 'menstruationCycle', 'menstruationDuration'] });
            if (menstruationData) {
                return JSON.parse(JSON.stringify(menstruationData)) as unknown as MenstruationData
            } else {
                throw new AppError(400, 'ERROR_WHILE_FETCHING_MENSTRUATION_DATA', {});
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    saveMenstruationDetail = async (userId: number, accountId: number, lastCommencingDate: Date, menstruationCycle: number, menstruationDuration: number): Promise<void> => {
        try {
            const menstruationData = await Models.UserMenstruationDetail.create({ userId, accountId, lastCommencingDate, menstruationCycle, menstruationDuration });
            return
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    };

    registerUserDevice = async (data: UserRegisterUserDeviceDaoInput, options: DaoOptions = {}): Promise<UserDeviceInterface> => {
        try {
            const { userId, accountId, deviceType, device } = data;
            // Look for an existing device
            const existingDevice = await Models.UserDevice.findOne({ where: { userId, accountId, deviceType, device }, transaction: options.transaction });
            if (existingDevice) {
                // Reactivate if previously inactive
                if (!existingDevice.status) {
                    await existingDevice.update({ status: true }, { transaction: options.transaction });
                }
                return existingDevice;
            }
            // deactivate other devices of same type
            await Models.UserDevice.update({ status: false }, { where: { userId, accountId, deviceType }, transaction: options.transaction });
            // Add new device
            const newDevice = await Models.UserDevice.create({ userId, accountId, deviceType, device }, { transaction: options.transaction });
            return newDevice;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };
    getUserDevice = async (userId: number | null, accountId: number | null): Promise<UserDeviceInterface> => {
        try {
            const existingDevice = await Models.UserDevice.findOne({ where: { userId: this.userId, accountId: this.accountId } });
            return existingDevice;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    updateReferralPoints = async (userId: number) => {
        const transaction = await sequelize.transaction();
        try {
            const referral = await Models.UserReferral.findOne({ where: { userId: userId, status: USER.STATUS.INACTIVE }, transaction });
            if (referral) {
                await referral.update({ status: USER.STATUS.ACTIVE }, { transaction });
                await Models.UserProfile.increment({ referralPoints: +process.env.REFERRAL_POINTS! }, { where: { userId: referral.referrerId }, transaction });
                // await Models.UserProfile.increment({ promptionalPoints: +process.env.REFERRAL_POINTS! }, { where: { userId: userId }, transaction });
            };

            await transaction.commit();
            return true;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    updateRedeemedPoints = async (userId: number, points: number) => {
        const transaction = await sequelize.transaction();
        try {
            const referral = await Models.UserProfile.findOne({
                attributes: ["referralPoints", "redeemedReferralPoints"],
                where: { userId: userId }, transaction
            });


            let redeemedPoints;

            const availablePoints = +referral.referralPoints - +referral.redeemedReferralPoints;
            if (availablePoints > points) {
                redeemedPoints = points;
            } else {
                redeemedPoints = availablePoints;
            }

            if (redeemedPoints > 0) {
                await Models.UserProfile.increment({ redeemedReferralPoints: redeemedPoints }, { where: { userId: userId }, transaction });
            };

            await transaction.commit();
            return redeemedPoints;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    updateRedeemedPromptionalPoints = async (userId: number, points: number) => {
        try {
            if (points > 0) {
                await Models.UserProfile.increment({ redeemedPromptionalPoints: points }, { where: { userId: userId } });
            };
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    fetchRedeemedPoints = async () => {
        try {
            const profile = await Models.UserProfile.findOne({
                attributes: ["promptionalPoints", "redeemedPromptionalPoints"],
                where: { userId: this.userId }
            });

            return +profile.promptionalPoints - +profile.redeemedPromptionalPoints;
        } catch (err) {
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    isActive = async (userId: number) => {
        try {
            let user = await Models.User.findOne({
                attributes: ["id"],
                where: { id: userId, status: USER.STATUS.ACTIVE }
            });
            if (user)
                return true;
            else
                return false

        } catch (err) {
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    }

    applyReferral = async (data: UserApplyReferralDaoInput, options: DaoOptions = {}) => {
        try {
            const { referralCode, userId } = data;
            const { transaction } = options;

            const referredBy = await Models.UserProfile.findOne({ attributes: ["userId"], where: { referralCode }, transaction });
            if (!referredBy) {
                throw new AppError(400, 'INVALID_REFERRAL_CODE', { referralCode: 'INVALID_REFERRAL_CODE' });
            }

            const referralExists = await Models.UserReferral.findOne({ where: { userId }, transaction });
            if (referralExists) {
                throw new AppError(400, 'REFERRAL_CODE_ALREADY_APPLIED', { referralCode: 'REFERRAL_CODE_ALREADY_APPLIED' });
            }

            if (userId === referredBy.userId) {
                throw new AppError(400, 'INVALID_REFERRAL_CODE', { referralCode: 'INVALID_REFERRAL_CODE' });
            }

            await Models.UserReferral.create({
                userId, referrerId: referredBy.userId, status: USER.STATUS.INACTIVE, points: process.env.REFERRAL_POINTS
            }, { transaction: transaction });

            await Models.UserProfile.increment({ promptionalPoints: +process.env.REFERRAL_POINTS! }, { where: { userId }, transaction });
            return { promptionalPoints: +process.env.REFERRAL_POINTS! };
        } catch (err) {
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    }

    securityDeclaration = async () => {
        try {
            await Models.UserSetting.update({ securityDeclaration: true }, { where: { userId: this.userId } });
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    }
}
