import { Sequelize, WhereOptions, literal, fn, Transaction, Op } from "sequelize";
import Models from "../models";
import { LocalizedContent } from "../../utils/contentGenerator";
import { AppError } from "../../utils/errors";
import Moment from "moment-timezone";
import { Common } from "../../utils/common";
import _ from "lodash";

const promotionAttributes: AttributeElement[] = [
    "id",
    "code",
    "status",
    "link",
    "type",
    "publishAt",
    "expireAt",
    "isPremium",
    "createdAt",
    "updatedAt",
    [literal("(case when `content`.title is not null then `content`.title else `defaultContent`.title END)"), "title"],
    [
        literal(
            "(case when `content`.description is not null then `content`.description else `defaultContent`.description END)"
        ),
        "description",
    ],
    [
        literal(
            "(case when `content`.description_text is not null then `content`.description_text else `defaultContent`.description_text END)"
        ),
        "descriptionText",
    ],
];

const authorAttributes: AttributeElement[] = [
    "id",
    "onlineStatus",
    [literal("`author->userProfile`.`name`"), "name"],
    [
        literal(`(
              SELECT JSON_OBJECT(
                'id', a.id,
                'fileName', a.file_name,
                'unique', 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 = author.id
              LIMIT 1
            )`),
        "profileImage",
    ],
];

const updatedByAttributes: AttributeElement[] = [
    "id",
    "onlineStatus",
    [literal("`updatedBy->userProfile`.`name`"), "name"],
    [
        literal(`(
              SELECT JSON_OBJECT(
                'id', a.id,
                'fileName', a.file_name,
                'unique', 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 = author.id
              LIMIT 1
            )`),
        "profileImage",
    ],
];
const attachmentAttributes: AttributeElement[] = [
    "id",
    "fileName",
    "uniqueName",
    [
        fn(
            "CONCAT",
            process.env.PROTOCOL,
            "://",
            process.env.API_HOST,
            "/attachment/",
            literal("`promotionImage`.`unique_name`")
        ),
        "filePath",
    ],
    [fn("CONCAT", process.env.CDN_PATH, literal("`promotionImage`.`file_path`")), "cdnUrl"],
];

export class PromotionDao {
    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;
    }

    // set category sortOrder
    setSortOrder = async (data: PromotionSetSortOrderDaoInput, options: DaoOptions = {}): Promise<boolean> => {
        const { id, before = null, after = null } = data;
        const { transaction } = options;
        try {
            let promotionData = await Models.Promotion.findOne({
                attributes: ["id", "type", "sortOrder"],
                where: { id: id, accountId: this.accountId, isRevision: false },
                transaction,
            });
            if (promotionData) {
                if (before || after) {
                    let locationData = await Models.Promotion.findOne({
                        attributes: ["id", "type", "sortOrder"],
                        where: { id: before ? before : after, accountId: this.accountId, isRevision: false },
                        transaction,
                    });
                    if (locationData.type != promotionData.type) {
                        return false;
                    }
                    if (promotionData.sortOrder < locationData.sortOrder) {
                        await Models.Category.decrement("sortOrder", {
                            by: 1,
                            where: {
                                accountId: this.accountId,
                                type: promotionData.type,
                                [Op.and]: [
                                    { sortOrder: { [Op.gt]: promotionData.sortOrder } },
                                    before
                                        ? { sortOrder: { [Op.lt]: locationData.sortOrder } }
                                        : { sortOrder: { [Op.lte]: locationData.sortOrder } },
                                ],
                            },
                            transaction,
                        });
                        await Models.Promotion.update(
                            before ? { sortOrder: locationData.sortOrder - 1 } : { sortOrder: locationData.sortOrder },
                            { where: { id: promotionData.id, accountId: this.accountId }, transaction }
                        );
                    } else if (promotionData.sortOrder > locationData.sortOrder) {
                        await Models.Promotion.increment("sortOrder", {
                            by: 1,
                            where: {
                                accountId: this.accountId,
                                type: promotionData.type,
                                [Op.and]: [
                                    before
                                        ? { sortOrder: { [Op.gte]: locationData.sortOrder } }
                                        : { sortOrder: { [Op.gt]: locationData.sortOrder } },
                                    { sortOrder: { [Op.lt]: promotionData.sortOrder } },
                                ],
                            },
                            transaction,
                        });
                        await Models.Promotion.update(
                            before ? { sortOrder: locationData.sortOrder } : { sortOrder: locationData.sortOrder + 1 },
                            { where: { id: promotionData.id, accountId: this.accountId }, transaction }
                        );
                    }
                } else {
                    let maxSortOrder = await Models.Promotion.max("sortOrder", {
                        where: { type: promotionData.type, accountId: this.accountId },
                        transaction,
                    });
                    await Models.Promotion.update(
                        { sortOrder: (maxSortOrder || 0) + 1 },
                        { where: { id: promotionData.id, accountId: this.accountId }, transaction }
                    );
                }
                return true;
            } else {
                return false;
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // Get full promotion object
    private getFullObject = async (id: number, options: DaoOptions = {}): Promise<PromotionInterface> => {
        try {
            let promotion = await Models.Promotion.findOne({
                where: { id: id, accountId: this.accountId },
                include: [
                    { model: Models.PromotionContent, as: "promotionContents" },
                    { model: Models.Attachment, as: "promotionImage" },
                ],
                transaction: options.transaction,
            });
            return JSON.parse(JSON.stringify(promotion));
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "ERROR_WHILE_GETTING_FULL_DATA", err);
        }
    };

    // Generate revision of promotion prior to update and delete functions.
    private storeRevision = async (id: number, options: DaoOptions = {}): Promise<PromotionInterface> => {
        try {
            let Object: PromotionInterface = await this.getFullObject(id, options);
            let revisonObject = JSON.parse(JSON.stringify(Object));
            let revisionId = revisonObject.id;
            revisonObject = _.omit(revisonObject, ["id", "createdAt", "updatedAt", "deletedAt"]);
            revisonObject.isRevision = true;
            revisonObject.code = revisonObject.code + "-" + Moment().toISOString();
            revisonObject.revisionId = revisionId;
            for (const key in revisonObject.promotionContents) {
                revisonObject.promotionContents[key] = _.omit(revisonObject.promotionContents[key], ["id", "promotionId"]);
            }
            let revision = await Models.Promotion.create(revisonObject, {
                include: [{ model: Models.PromotionContent, as: "promotionContents" }],
                transaction: options.transaction,
            });
            if (revision) {
                return revision;
            } else {
                throw new AppError(400, "ERROR_WHILE_CREATING_REVISION", {});
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // get association for the entity
    private includeAssociations = (fullInfo: boolean = false): IncludeOption[] => {
        const includeModels: IncludeOption[] = [
            {
                attributes: [],
                model: Models.PromotionContent,
                as: "content",
                include: [{ attributes: [], model: Models.Language, as: "language", where: { code: this.language } }],
            },
            {
                attributes: [],
                model: Models.PromotionContent,
                as: "defaultContent",
                include: [
                    {
                        attributes: [],
                        model: Models.Language,
                        as: "language",
                        where: { code: process.env.DEFAULT_LANGUAGE_CODE },
                    },
                ],
            },
            {
                attributes: attachmentAttributes,
                model: Models.Attachment,
                as: "promotionImage",
            },
        ];
        if (fullInfo) {
            includeModels.push(
                {
                    attributes: authorAttributes,
                    model: Models.User,
                    as: "author",
                    include: [
                        {
                            attributes: [],
                            model: Models.UserProfile,
                            as: "userProfile",
                            include: [{ model: Models.Attachment, as: "profileImage" }],
                        },
                    ],
                },
                {
                    attributes: updatedByAttributes,
                    model: Models.User,
                    as: "updatedBy",
                    include: [
                        {
                            attributes: [],
                            model: Models.UserProfile,
                            as: "userProfile",
                            include: [{ model: Models.Attachment, as: "profileImage" }],
                        },
                    ],
                }
            );
        }
        return includeModels;
    };

    // get promotion
    getPromotion = async (data: PromotionGetDaoInput, options: DaoOptions = {}): Promise<PromotionObjectInteface> => {
        try {
            const { id = null, code = null, expanded = true, paranoid = true } = data;
            const promotion = await Models.Promotion.findOne({
                attributes: promotionAttributes,
                where: id ? { id } : code ? { code } : undefined,
                ...{ accountId: this.accountId },
                include: this.includeAssociations(expanded),
                paranoid: paranoid,
                transaction: options.transaction,
            });
            if (promotion) {
                return JSON.parse(JSON.stringify(promotion)) as unknown as PromotionObjectInteface;
            } else {
                throw new AppError(404, "PROMOTIONS_NOT_FOUND", { id: "PROMOTIONS_NOT_FOUND", code: "PROMOTIONS_NOT_FOUND" });
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // get promotion by id
    getPromotionById = async (data: PromotionGetByIdDaoInput, options: DaoOptions = {}): Promise<PromotionObjectInteface> => {
        try {
            const getPromotionInput: PromotionGetDaoInput = {
                id: data.id,
                code: null,
                expanded: data.expanded ?? true,
                paranoid: data.paranoid ?? true,
            };
            return await this.getPromotion(getPromotionInput, options);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // get promotion by code
    getPromotionByCode = async (data: PromotionGetByCodeDaoInput, options: DaoOptions = {}): Promise<PromotionObjectInteface> => {
        try {
            const getPromotionInput: PromotionGetDaoInput = {
                id: null,
                code: data.code,
                expanded: data.expanded ?? true,
                paranoid: data.paranoid ?? true,
            };
            return await this.getPromotion(getPromotionInput, options);
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // check if promotion exists by code
    doExistsByCode = async (data: PromotionDoExistsByCodeDaoInput, options: DaoOptions = {}): Promise<PromotionInterface | false> => {
        try {
            const { code, excludeId = null } = data;
            const promotion = await Models.Promotion.findOne({
                attributes: ["id"],
                where: { ...(excludeId ? { id: { [Op.ne]: excludeId } } : {}), code: code, accountId: this.accountId, isRevision: false },
                transaction: options.transaction,
            });
            return promotion?.id ? promotion : false;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // check if promotion exists by id
    doExistsById = async (data: PromotionDoExistsByIdDaoInput, options: DaoOptions = {}): Promise<PromotionInterface | false> => {
        try {
            const { id, includeRevision = false } = data;
            const promotion = await Models.Promotion.findOne({
                attributes: ["id"],
                where: { id: id, accountId: this.accountId, isRevision: includeRevision },
                transaction: options.transaction,
            });
            return promotion?.id ? promotion : false;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // create a new promotion
    create = async (data: PromotionCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { promotionObj, promotionContentObj, languages } = data;
            const promotionContents: PromotionContentObject[] = LocalizedContent.generate(
                promotionContentObj,
                languages
            ) as unknown as PromotionContentObject[];
            const promotionObject: PromotionDataObject = {
                ...promotionObj,
                userId: this.userId,
                accountId: this.accountId,
                promotionContents: promotionContents,
            };
            let promotion = await Models.Promotion.create(promotionObject, {
                include: [{ model: Models.PromotionContent, as: "promotionContents" }],
                transaction: options.transaction,
            });
            return promotion.id;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // update promotion
    update = async (data: PromotionUpdateDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id, promotionObj, promotionContentObj, languages } = data;
            await this.storeRevision(id, options);
            await Models.Promotion.update(
                { ...promotionObj, lastUpdatedBy: this.userId },
                { where: { id: id }, transaction: options.transaction }
            );
            let verification = await Models.PromotionContent.findOne({
                where: { promotionId: id, languageId: languages.requested.id },
                transaction: options.transaction,
            });
            if (verification) {
                let promotionContent: PromotionContentObject = {
                    ...promotionContentObj,
                    ...{ languageId: languages.requested.id, promotionId: id },
                };
                await Models.PromotionContent.update(promotionContent, {
                    where: { id: verification.id },
                    transaction: options.transaction,
                });
            } else {
                let promotionContent: PromotionContentObject = {
                    ...promotionContentObj,
                    ...{ languageId: languages.requested.id, promotionId: id },
                };
                await Models.PromotionContent.create(promotionContent, { transaction: options.transaction });
            }
            return;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // delete promotion
    delete = async (data: PromotionDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id } = data;
            const getPromotionByIdInput: PromotionGetByIdDaoInput = { id, expanded: false };
            let promotion = await this.getPromotionById(getPromotionByIdInput, options);
            let code = promotion.code + "-" + Moment().toISOString();
            await Models.Promotion.update(
                { updatedBy: this.userId, code: code },
                { where: { id: id }, transaction: options.transaction }
            );
            await Models.Promotion.destroy({ where: { id: id }, transaction: options.transaction });
            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 "title":
                return [[literal(`title`), direction]];
            case "id":
                return [["id", direction]];
            default:
                return [["id", "ASC"]];
        }
    };

    // Build filter
    private buildFilter = (where: WhereOptions & { [Op.and]: any[] }, searchText: string | null, status: number | null | undefined) => {
        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) {
                (where[Op.and] as any[]).push(
                    Sequelize.literal("MATCH(`defaultContent`.`title`,`defaultContent`.`description_text`) AGAINST(:alphaString IN BOOLEAN MODE)")
                );
                (where[Op.and] as any[]).push(
                    Sequelize.literal("MATCH(`content`.`title`,`content`.`description_text`) AGAINST(:alphaString IN BOOLEAN MODE)")
                );
            }
            if (specialString) {
                (where[Op.and] as any[]).push(
                    Sequelize.literal("MATCH(`defaultContent`.`title`,`defaultContent`.`description_text`) AGAINST(:specialString IN BOOLEAN MODE)")
                );
                (where[Op.and] as any[]).push(
                    Sequelize.literal("MATCH(`content`.`title`,`content`.`description_text`) AGAINST(:specialString IN BOOLEAN MODE)")
                );
            }
        }
        if (status != null) {
            where = { ...where, status: status };
        }
        return { where: where, replacements: { alphaString: alphaString, specialString: specialString } };
    };

    // list promotions with pagination
    getPromotionList = async (data: PromotionGetPromotionListDaoInput, options: DaoOptions = {}): Promise<PromotionPaginatedData> => {
        try {
            const { type, listRequest } = data;
            const { page, perPage, status, searchText, sortBy, sortDirection } = listRequest;
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = { accountId: this.accountId, isRevision: false, [Op.and]: [] };
            if (!this.userId) {
                where[Op.and].push({ isPremium: false });
            }
            if (status) {
                const timeZone = this.config?.timeZone || "UTC";
                where[Op.and].push(
                    literal(`CONVERT_TZ(NOW(), 'UTC', '${timeZone}') BETWEEN \`publish_at\` AND \`expire_at\``)
                );
            }
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            if (type) {
                where[Op.and].push({ type: type });
            }
            const promotions = await Models.Promotion.findAndCountAll({
                attributes: promotionAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(false),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                subQuery: false,
                transaction: options.transaction,
            });
            return JSON.parse(JSON.stringify(promotions)) as unknown as PromotionPaginatedData;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // list all promotions
    getAllPromotions = async (data: PromotionGetAllPromotionsDaoInput, options: DaoOptions = {}): Promise<PromotionObjectSummaryInterface[]> => {
        try {
            const { type, listRequest } = data;
            const { status, searchText, sortBy, sortDirection } = listRequest;
            let where: WhereOptions & { [Op.and]: any[] } = { accountId: this.accountId, isRevision: false, [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            if (type) {
                where[Op.and].push({ type: type });
            }
            const promotions = await Models.Promotion.findAll({
                attributes: promotionAttributes,
                where: applyfilter.where,
                replacements: applyfilter.replacements,
                include: this.includeAssociations(false),
                order: orderBy,
                limit: +process.env.LIST_ALL_LIMIT!,
                transaction: options.transaction,
            });
            return JSON.parse(JSON.stringify(promotions)) as unknown as PromotionObjectSummaryInterface[];
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // list promotions revisions
    getPromotionRevisionList = async (
        data: PromotionGetPromotionRevisionListDaoInput,
        options: DaoOptions = {}
    ): Promise<PromotionPaginatedData> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage, status, searchText, sortBy, sortDirection } = listRequest;
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = {
                isRevision: true,
                accountId: this.accountId,
                revisionId: id,
                [Op.and]: [],
            };
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const promotions = await Models.Promotion.findAndCountAll({
                attributes: promotionAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(false),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                transaction: options.transaction,
            });
            return JSON.parse(JSON.stringify(promotions)) as unknown as PromotionPaginatedData;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // restore revision for promotion
    restoreRevision = async (data: PromotionRestoreRevisionDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { id } = data;
            const checkPromotionRevisionInput: PromotionDoExistsByIdDaoInput = { id, includeRevision: true };
            if (await this.doExistsById(checkPromotionRevisionInput, options)) {
                let Object: PromotionInterface = await this.getFullObject(id, options);
                if (Object && Object.revisionId) {
                    await this.storeRevision(Object.revisionId, options);
                    let revisonObject = JSON.parse(JSON.stringify(Object));
                    revisonObject = _.omit(revisonObject, ["id", "createdAt", "updatedAt", "deletedAt"]);
                    await Models.Promotion.update(
                        { lastUpdatedBy: this.userId },
                        { where: { id: revisonObject.revisionId }, transaction: options.transaction }
                    );
                    if (revisonObject.promotionContents && revisonObject.promotionContents.length) {
                        for (let content of revisonObject.promotionContents) {
                            await Models.PromotionContent.upsert(
                                {
                                    title: content.title,
                                    description: content.description,
                                    descriptionText: content.descriptionText,
                                    languageId: content.languageId,
                                    promotionId: Object.revisionId,
                                },
                                { transaction: options.transaction }
                            );
                        }
                    }
                    return Object.revisionId;
                } else {
                    throw new AppError(400, "REQUESTED_ENTITY_IS_NOT_A_REVISION", { id: "REQUESTED_ENTITY_IS_NOT_A_REVISION" });
                }
            } else {
                throw new AppError(404, "PROMOTIONS_NOT_FOUND", { id: "PROMOTIONS_NOT_FOUND" });
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // get promotion id from promotion code
    getIdFromCode = async (data: PromotionGetIdFromCodeDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            let promotion = await Models.Promotion.findOne({
                attributes: ["id"],
                where: { code: data.code, accountId: this.accountId },
                transaction: options.transaction,
            });
            if (promotion) {
                return promotion.id;
            } else {
                throw new AppError(404, "PROMOTIONS_NOT_FOUND", { code: "PROMOTIONS_NOT_FOUND" });
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };

    // update question status
    updateStatus = async (data: PromotionUpdateStatusDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.Promotion.update(
                { status: data.status },
                { where: { id: data.id, accountId: this.accountId }, transaction: options.transaction }
            );
            return;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_WITH_DAO", err);
        }
    };
}
