import { Sequelize, WhereOptions, FindOptions, literal, fn, col, 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 _, { sortBy } from 'lodash';
import { PLAN } from "../config/constants";

const planAttributes: AttributeElement[] = [
    'id', 'code', 'status', 'type', 'createdAt', 'updatedAt', 'gateway',
    [literal('(case when `content`.name is not null then `content`.name else `defaultContent`.name END)'), 'name'],
    [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 priceAttributes: AttributeElement[] = ["id", "amount", "currency", "duration", "frequency", "autoRenew", "isDefault"]

const authorAttributes: AttributeElement[] = [
    'id', 'onlineStatus',
    [literal('`author->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 = 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,
                '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 = author.id
              LIMIT 1
            )`),
        'profileImage'
    ]
];

export class PlanDao {
    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 normalizeAutoRenew = (value: unknown): 0 | 1 => {
        return value ? 1 : 0;
    }

    private normalizeFrequency = (value: unknown): 'days' | 'weeks' | 'months' | 'years' | string | null => {
        const normalizedValue = String(value ?? '').trim().toLowerCase();
        if (!normalizedValue) {
            return null;
        }
        switch (normalizedValue) {
            case 'day':
            case 'daily':
                return 'days';
            case 'week':
            case 'weekly':
                return 'weeks';
            case 'month':
            case 'monthly':
                return 'months';
            case 'year':
            case 'yearly':
                return 'years';
            default:
                return normalizedValue;
        }
    }

    private normalizePrice = (price: any) => {
        if (!price) {
            return price;
        }
        return {
            ...price,
            frequency: this.normalizeFrequency(price.frequency),
            autoRenew: this.normalizeAutoRenew(price.autoRenew)
        };
    }

    private normalizePlan = (plan: any) => {
        if (!plan) {
            return plan;
        }
        const normalizedPlan = { ...plan };
        if (Array.isArray(normalizedPlan.prices)) {
            normalizedPlan.prices = normalizedPlan.prices.map((price: any) => this.normalizePrice(price));
        }
        return normalizedPlan;
    }

    // set plan sort order
    setSortOrder = async (data: PlanSetSortOrderDaoInput, options: DaoOptions = {}): Promise<boolean> => {
        const { id, before = null, after = null } = data;
        const { transaction } = options;
        try {
            let planData = await Models.Plan.findOne({ attributes: ['id', 'sortOrder'], where: { id: id, isRevision: false }, transaction });
            if (planData) {
                if (before || after) {
                    let locationData = await Models.Plan.findOne({ attributes: ['id', 'sortOrder'], where: { id: before ? before : after, isRevision: false }, transaction });
                    if (!locationData) {
                        return false;
                    }
                    if (planData.sortOrder < locationData.sortOrder) {
                        await Models.Plan.decrement('sortOrder', {
                            by: 1,
                            where: {
                                [Op.and]: [
                                    { sortOrder: { [Op.gt]: planData.sortOrder } },
                                    before ? { sortOrder: { [Op.lt]: locationData.sortOrder } } : { sortOrder: { [Op.lte]: locationData.sortOrder } }
                                ]
                            },
                            transaction: transaction
                        });
                        await Models.Plan.update(before ? { sortOrder: locationData.sortOrder - 1 } : { sortOrder: locationData.sortOrder }, { where: { id: planData.id }, transaction: transaction });
                    } else if (planData.sortOrder > locationData.sortOrder) {
                        await Models.Plan.increment('sortOrder', {
                            by: 1,
                            where: {
                                [Op.and]: [
                                    before ? { sortOrder: { [Op.gte]: locationData.sortOrder } } : { sortOrder: { [Op.gt]: locationData.sortOrder } },
                                    { sortOrder: { [Op.lt]: planData.sortOrder } }
                                ]
                            },
                            transaction: transaction
                        });
                        await Models.Plan.update(before ? { sortOrder: locationData.sortOrder } : { sortOrder: locationData.sortOrder + 1 }, { where: { id: planData.id }, transaction: transaction });
                    }
                }
                else {
                    let maxSortOrder = await Models.Plan.max('sortOrder');
                    await Models.Plan.update(
                        { sortOrder: (maxSortOrder || 0) + 1 }, // default to 1 if no max found
                        { where: { id: planData.id }, transaction: 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 plan object
    private getFullObject = async (id: number, options: DaoOptions = {}): Promise<PlanInterface> => {
        try {
            let Plan = await Models.Plan.findOne({
                where: { id: id },
                include: [
                    { model: Models.PlanContent, as: 'planContents' },
                    { model: Models.Price, as: 'prices' }
                ],
                transaction: options.transaction
            });
            return this.normalizePlan(JSON.parse(JSON.stringify(Plan)))
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'ERROR_WHILE_GETTING_FULL_DATA', err);
        }
    }

    // Generate revision of plan prior to update and delete functions.
    private storeRevision = async (id: number, options: DaoOptions = {}): Promise<PlanInterface> => {
        try {
            let Object: PlanInterface = await this.getFullObject(id, options);
            let revisonObject = JSON.parse(JSON.stringify(Object));
            let revisionId = revisonObject.id;
            revisonObject = _.omit(revisonObject, ['id', 'createdAt', 'updatedAt', 'deletedAt', 'externalProductId']);
            revisonObject.isRevision = true;
            revisonObject.code = revisonObject.code + '-' + Moment().toISOString();
            revisonObject.externalProductId = revisonObject.externalProductId + '-' + Moment().toISOString();
            revisonObject.revisionId = revisionId;
            for (const key in revisonObject.planContents) {
                revisonObject.planContents[key] = _.omit(revisonObject.planContents[key], ['id', 'planId'])
            }
            for (const key in revisonObject.prices) {
                revisonObject.prices[key] = _.omit(revisonObject.prices[key], ['id', 'planId'])
            }
            let revision = await Models.Plan.create(revisonObject, {
                include: [
                    { model: Models.PlanContent, as: 'planContents' },
                    { model: Models.Price, as: 'prices' }
                ], 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.PlanContent,
                as: 'content',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: this.language } }]
            },
            {
                attributes: [],
                model: Models.PlanContent,
                as: 'defaultContent',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
            },
            {
                separate: true,
                attributes: priceAttributes,
                model: Models.Price,
                where: { status: PLAN.STATUS.ACTIVE },
                as: 'prices',
            }
        ];
        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 plan
    getPlan = async (data: PlanGetDaoInput, options: DaoOptions = {}): Promise<PlanObjectInteface> => {
        try {
            const { id = null, code = null, expanded = true, paranoid = true } = data;
            const plan = await Models.Plan.findOne({
                attributes: planAttributes,
                where: id ? { id } : code ? { code } : undefined,
                include: this.includeAssociations(expanded),
                paranoid: paranoid,
                transaction: options.transaction
            });
            if (plan) {
                return this.normalizePlan(JSON.parse(JSON.stringify(plan))) as unknown as PlanObjectInteface;
            } else {
                throw new AppError(404, 'PLAN_NOT_FOUND', { id: 'PLAN_NOT_FOUND', code: 'PLAN_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get plan by id
    getPlanById = async (
        data: PlanGetByIdDaoInput | number,
        options: DaoOptions = {},
        legacyExpanded?: boolean,
        legacyParanoid?: boolean
    ): Promise<PlanObjectInteface> => {
        try {
            const normalizedData: PlanGetByIdDaoInput =
                typeof data === "number"
                    ? { id: data, expanded: legacyExpanded ?? true, paranoid: legacyParanoid ?? true }
                    : data;
            const getPlanInput: PlanGetDaoInput = {
                id: normalizedData.id,
                code: null,
                expanded: normalizedData.expanded ?? true,
                paranoid: normalizedData.paranoid ?? true,
            };
            return await this.getPlan(getPlanInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get plan by code
    getPlanByCode = async (
        data: PlanGetByCodeDaoInput | string,
        options: DaoOptions = {},
        legacyExpanded?: boolean,
        legacyParanoid?: boolean
    ): Promise<PlanObjectInteface> => {
        try {
            const normalizedData: PlanGetByCodeDaoInput =
                typeof data === "string"
                    ? { code: data, expanded: legacyExpanded ?? true, paranoid: legacyParanoid ?? true }
                    : data;
            const getPlanInput: PlanGetDaoInput = {
                id: null,
                code: normalizedData.code,
                expanded: normalizedData.expanded ?? true,
                paranoid: normalizedData.paranoid ?? true,
            };
            return await this.getPlan(getPlanInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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

    // check if plan exists by id
    doExistsById = async (data: PlanDoExistsByIdDaoInput, options: DaoOptions = {}): Promise<PlanInterface | false> => {
        try {
            const { id, includeRevision = false } = data;
            const plan = await Models.Plan.findOne({
                where: { id: id, isRevision: includeRevision },
                transaction: options.transaction
            });
            return plan?.id ? plan : false;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new plan
    create = async (data: PlanCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { planObj, planContentObj, prices, languages } = data;
            const planContents: PlanContentObject[] = LocalizedContent.generate(planContentObj, languages) as unknown as PlanContentObject[];
            const planObject: PlanDataObject = { ...planObj, userId: this.userId!, accountId: this.accountId!, planContents: planContents, prices: prices };
            let plan = await Models.Plan.create(planObject, {
                include: [
                    { model: Models.PlanContent, as: 'planContents' },
                    { model: Models.Price, as: 'prices' }
                ],
                transaction: options.transaction
            });
            return plan.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // update plan
    update = async (data: PlanUpdateDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id, planObj, planContentObj, prices, languages } = data;
            await this.storeRevision(id, options);
            await Models.Plan.update(planObj, { where: { id: id }, transaction: options.transaction });
            await Models.Price.update({ status: PLAN.STATUS.INACTIVE }, { where: { planId: id }, transaction: options.transaction });

            for(let item of prices) {
                if(item.id) {
                    await Models.Price.update({...item, status: PLAN.STATUS.ACTIVE}, { where: {id: item.id}, transaction: options.transaction });
                } else {
                    await Models.Price.create({...item, planId: id, status: PLAN.STATUS.ACTIVE}, { transaction: options.transaction });
                }
                // pricesArr.push({ ...item, planId: id });
            }

            // await Models.Price.bulkCreate(pricesArr, {transaction})

            // check if content exists in requested language
            let verification = await Models.PlanContent.findOne({ where: { planId: id, languageId: languages.requested.id }, transaction: options.transaction });
            if (verification) { // update if content exists in the requested language
                // generate revision for the existing state
                let planContent: PlanContentObject = { ...planContentObj, ...{ languageId: languages.requested.id, planId: id } }
                await Models.PlanContent.update(planContent, { where: { id: verification.id }, transaction: options.transaction })

            } else { // create if content does not exists in the requested language
                let planContent: PlanContentObject = { ...planContentObj, ...{ languageId: languages.requested.id, planId: id } }
                await Models.PlanContent.create(planContent, { transaction: options.transaction })
            }
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // delete plan
    delete = async (data: PlanDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id } = data;
            const getPlanByIdInput: PlanGetByIdDaoInput = { id, expanded: false };
            let plan = await this.getPlanById(getPlanByIdInput, options);
            let code = plan.code + '-' + Moment().toISOString();
            await Models.Plan.update({ updatedBy: this.userId, code: code }, { where: { id: id }, transaction: options.transaction });
            await Models.Plan.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 'name':
                return [[literal(`name`), direction]];
            case 'id':
                return [['id', direction]];
            default:
                // Fallback to default order
                return [
                    ['id', 'ASC']
                ];
        }
    }

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

    // list plans with pagination
    getPlanList = async (data: PlanGetPlanListDaoInput, options: DaoOptions = {}): Promise<PlanPaginatedData> => {
        try {
            const { listRequest } = data;
            const { page, perPage, searchText, sortBy, sortDirection, status } = listRequest;
            let offset = (page - 1) * perPage;

            let where: WhereOptions & { [Op.and]: any[] } = {
                accountId: {
                    [Op.or]: [null, this.accountId]
                }, isRevision: false, [Op.and]: []
            };

            if(status !== null) {
                where.status = status
            }

            let applyfilter = this.buildFilter(where, searchText);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const plans = await Models.Plan.findAndCountAll({
                attributes: planAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(false),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                subQuery: false,
                transaction: options.transaction
            });
            const normalizedPlans = JSON.parse(JSON.stringify(plans));
            normalizedPlans.rows = Array.isArray(normalizedPlans.rows)
                ? normalizedPlans.rows.map((plan: any) => this.normalizePlan(plan))
                : normalizedPlans.rows;
            return normalizedPlans as unknown as PlanPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // list all plans
    getAllPlans = async (data: PlanGetAllPlansDaoInput, options: DaoOptions = {}): Promise<PlanObjectSummaryInteface[]> => {
        try {
            const { listRequest } = data;
            const { searchText, sortBy, sortDirection } = listRequest;
            let where: WhereOptions & { [Op.and]: any[] } = {
                status: PLAN.STATUS.ACTIVE,
                accountId: {
                    [Op.or]: [null, this.accountId]
                }, isRevision: false, [Op.and]: []
            };

            let applyfilter = this.buildFilter(where, searchText);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const plans = await Models.Plan.findAll({
                attributes: planAttributes,
                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(plans)).map((plan: any) => this.normalizePlan(plan)) as unknown as PlanObjectSummaryInteface[];
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // list plans revisions
    getPlanRevisionList = async (data: PlanGetPlanRevisionListDaoInput, options: DaoOptions = {}): Promise<PlanPaginatedData> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage, searchText, sortBy, sortDirection } = listRequest;
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = { isRevision: true, revisionId: id, [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const plans = await Models.Plan.findAndCountAll({
                attributes: planAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(false),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                transaction: options.transaction
            });
            const normalizedPlans = JSON.parse(JSON.stringify(plans));
            normalizedPlans.rows = Array.isArray(normalizedPlans.rows)
                ? normalizedPlans.rows.map((plan: any) => this.normalizePlan(plan))
                : normalizedPlans.rows;
            return normalizedPlans as unknown as PlanPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // restore revision for plan
    restoreRevision = async (data: PlanRestoreRevisionDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { id } = data;
            const checkPlanRevisionInput: PlanDoExistsByIdDaoInput = { id, includeRevision: true };
            if (await this.doExistsById(checkPlanRevisionInput, options)) {
                // get full revision Data
                let Object: PlanInterface = await this.getFullObject(id, options);
                if (Object && Object.revisionId) {
                    // create new revision from existing state
                    await this.storeRevision(Object.revisionId, options);
                    let revisonObject = JSON.parse(JSON.stringify(Object));
                    // remove unnecessary data from object
                    revisonObject = _.omit(revisonObject, ['id', 'createdAt', 'updatedAt', 'deletedAt']);
                    // remove revision updates
                    // revisonObject.code = revisonObject.code.split('-').slice(0, -1).join('-');
                    // update entity type
                    await Models.Plan.update({ lastUpdatedBy: this.userId }, { where: { id: revisonObject.revisionId }, transaction: options.transaction });
                    // update entity type content
                    if (revisonObject.planContents && revisonObject.planContents.length) {
                        for (let content of revisonObject.planContents) {
                            await Models.PlanContent.upsert({ name: content.name, description: content.description, descriptionText: content.descriptionText, languageId: content.languageId, planId: 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, 'PLAN_NOT_FOUND', { id: 'PLAN_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get plan id from plan code
    getIdFromCode = async (data: PlanGetIdFromCodeDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            let plan = await Models.Plan.findOne({ attributes: ['id'], where: { code: data.code }, transaction: options.transaction });
            if (plan) {
                return plan.id;
            } else {
                throw new AppError(404, 'PLAN_NOT_FOUND', { code: 'PLAN_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: PlanUpdateStatusDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.Plan.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);
        }
    }

    // update question status
    updatePriceStatus = async (data: PlanUpdatePriceStatusDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.Price.update({ status: data.status }, { where: { id: data.id }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new price for plan
    getExternalPriceById = async (data: PlanGetExternalPriceByIdDaoInput | number, options: DaoOptions = {}): Promise<string> => {
        try {
            const priceId = typeof data === "number" ? data : data.id;
            let price = await Models.Price.findOne({ where: { id: priceId }, transaction: options.transaction });
            if (price?.externalPriceId) {
                return price.externalPriceId;
            } else {
                throw new AppError(404, 'PRICE_NOT_FOUND', { externalPriceId: 'PRICE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new price for plan
    getPriceById = async (data: PlanGetPriceByIdDaoInput | number, options: DaoOptions = {}): Promise<PriceInterface> => {
        try {
            const priceId = typeof data === "number" ? data : data.id;
            let price = await Models.Price.findOne({
                attributes: priceAttributes,
                where: { id: priceId },
                transaction: options.transaction
            });
            if (price) {
                return this.normalizePrice(JSON.parse(JSON.stringify(price)));
            } else {
                throw new AppError(404, 'PRICE_NOT_FOUND', { externalPriceId: 'PRICE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new price for plan
    getPriceByIdFullObject = async (data: PlanGetPriceByIdDaoInput | number, options: DaoOptions = {}): Promise<PriceInterface> => {
        try {
            const priceId = typeof data === "number" ? data : data.id;
            let price = await Models.Price.findOne({
                where: { id: priceId },
                transaction: options.transaction
            });
            if (price) {
                return this.normalizePrice(JSON.parse(JSON.stringify(price)));
            } else {
                throw new AppError(404, 'PRICE_NOT_FOUND', { externalPriceId: 'PRICE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
    // create a new price for plan
    createPrice = async (data: PlanCreatePriceDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            let createdPrice = await Models.Price.create(data.price, { transaction: options.transaction });
            return createdPrice.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
}
