import { Sequelize, WhereOptions, FindOptions, literal, fn, col, Op } from "sequelize";
import Models, { sequelize } 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';
import { CATEGORY } from "../config/constants"
import { SETTING } from "../config/constants"

const categoryTreeAttributes: AttributeElement[] = [
    'id', 'code', 'parentId',
    [literal('(case when `content`.name is not null then `content`.name else `defaultContent`.name END)'), 'name'],
]

const allSettingAttributes: AttributeElement[] = [
    'code',
    [literal('(case when `content`.label is not null then `content`.label else `defaultContent`.label END)'), 'label'],
    [
        literal(`
      JSON_EXTRACT(
        JSON_OBJECT(
          'value_string', content.value_string,
          'value_text', content.value_text,
          'value_boolean', CAST(content.value_boolean AS JSON),
          'value_numeric', CAST(content.value_numeric AS JSON)
        ),
        CONCAT(
          '$.',
          CASE
            WHEN content.value_string IS NOT NULL THEN 'value_string'
            WHEN content.value_text IS NOT NULL THEN 'value_text'
            WHEN content.value_boolean IS NOT NULL THEN 'value_boolean'
            WHEN content.value_numeric IS NOT NULL THEN 'value_numeric'
            ELSE 'value_string'
          END
        )
      )
    `),
        'value'
    ]
];

const settingAttributes: AttributeElement[] = [
    'id', 'code', 'status', 'valueType', 'createdAt', 'updatedAt',
    [literal('(case when `content`.label is not null then `content`.label else `defaultContent`.label END)'), 'label'],
    [
        literal(`
      JSON_EXTRACT(
        JSON_OBJECT(
          'value_string', content.value_string,
          'value_text', content.value_text,
          'value_boolean', CAST(content.value_boolean AS JSON),
          'value_numeric', CAST(content.value_numeric AS JSON)
        ),
        CONCAT(
          '$.',
          CASE
            WHEN content.value_string IS NOT NULL THEN 'value_string'
            WHEN content.value_text IS NOT NULL THEN 'value_text'
            WHEN content.value_boolean IS NOT NULL THEN 'value_boolean'
            WHEN content.value_numeric IS NOT NULL THEN 'value_numeric'
            ELSE 'value_string'
          END
        )
      )
    `),
        'value'
    ]
];

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,
                '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 settingCategoryAttributes: AttributeElement[] = [
    'id', 'code',
    [sequelize.literal('(case when `settingCategory->content`.name is not null then `settingCategory->content`.name else `settingCategory->defaultContent`.name END)'), 'name']
];

export class SettingDao {
    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
    }
    // Get full entity object for manipulation
    private getFullObject = async (data: SettingGetFullObjectDaoInput, options: DaoOptions = {}): Promise<SettingInterface> => {
        const { id } = data;
        const { transaction } = options;
        try {
            let Setting = await Models.Setting.findOne({
                where: { id: id },
                include: [{ model: Models.SettingContent, as: 'settingContents' }],
                transaction
            });
            return JSON.parse(JSON.stringify(Setting))
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'ERROR_WHILE_GETTING_FULL_DATA', err);
        }
    }

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

    // Generate revision of category type prior to update and delete functions.
    private storeRevision = async (data: SettingStoreRevisionDaoInput, options: DaoOptions = {}): Promise<SettingInterface> => {
        const { id } = data;
        const { transaction } = options;
        try {
            const getFullObjectInput: SettingGetFullObjectDaoInput = { id };
            let Object: SettingInterface = await this.getFullObject(getFullObjectInput, 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.settingContents) {
                revisonObject.settingContents[key] = _.omit(revisonObject.settingContents[key], ['id', 'settingId'])
            }
            let revision = await Models.Setting.create(revisonObject, { include: [{ model: Models.SettingContent, as: 'settingContents' }], transaction: 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);
        }
    }
    // category association
    private includeCategoryAssociation = () => {
        const includeModels: IncludeOption[] = [
            {
                attributes: [],
                model: Models.CategoryContent,
                as: 'content',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: this.language } }]
            },
            {
                attributes: [],
                model: Models.CategoryContent,
                as: 'defaultContent',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
            }
        ]
        return includeModels;
    }

    // get association for the entity
    private includeAssociations = (fullInfo: boolean = false): IncludeOption[] => {
        const includeModels: IncludeOption[] = [
            {
                attributes: [],
                model: Models.SettingContent,
                as: 'content',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: this.language } }]
            },
            {
                attributes: [],
                model: Models.SettingContent,
                as: 'defaultContent',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
            }
        ];
        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" }] }]
                },
                {
                    attributes: settingCategoryAttributes,
                    model: Models.Category,
                    as: 'settingCategory',
                    include: [
                        {
                            attributes: [],
                            model: Models.CategoryContent, as: 'content',
                            include: [
                                { attributes: [], model: Models.Language, as: 'language', where: { code: this.language } }
                            ]
                        },
                        {
                            attributes: [],
                            model: Models.CategoryContent, as: 'defaultContent',
                            include: [
                                { attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }
                            ]
                        }
                    ]
                }
            );
        }
        return includeModels;
    }

    // get setting
    getSetting = async (data: SettingGetDaoInput, options: DaoOptions = {}): Promise<SettingObjectInteface> => {
        const { id = null, code = null, expanded = true, paranoid = true } = data;
        const { transaction } = options;
        try {
            const setting = await Models.Setting.findOne({
                attributes: settingAttributes,
                where: id ? { id } : code ? { code } : undefined,
                include: this.includeAssociations(expanded),
                paranoid: paranoid,
                transaction
            });
            if (setting) {
                return JSON.parse(JSON.stringify(setting)) as unknown as SettingObjectInteface;
            } else {
                throw new AppError(404, 'SETTING_NOT_FOUND', { id: 'SETTING_NOT_FOUND', code: 'SETTING_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get setting by id
    getSettingById = async (data: SettingGetByIdDaoInput, options: DaoOptions = {}): Promise<SettingObjectInteface> => {
        const { id, expanded = true, paranoid = true } = data;
        try {
            const getSettingInput: SettingGetDaoInput = { id, expanded, paranoid };
            return await this.getSetting(getSettingInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get setting by code
    getSettingByCode = async (data: SettingGetByCodeDaoInput, options: DaoOptions = {}): Promise<SettingObjectInteface> => {
        const { code, expanded = true, paranoid = true } = data;
        try {
            const getSettingInput: SettingGetDaoInput = { code, expanded, paranoid };
            return await this.getSetting(getSettingInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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

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

    // create a new category type
    create = async (data: SettingCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        const { settingObj, settingContentObj, languages } = data;
        const { transaction } = options;
        try {
            const settingContents: SettingContentObject[] = LocalizedContent.generate(settingContentObj, languages) as unknown as SettingContentObject[];
            const settingObject: SettingDataObject = { ...settingObj, userId: this.userId, accountId: this.accountId, settingContents: settingContents };
            let setting = await Models.Setting.create(settingObject, {
                include: [{ model: Models.SettingContent, as: 'settingContents' }],
                transaction
            });
            return setting.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // update content type
    update = async (data: SettingUpdateDaoInput, options: DaoOptions = {}): Promise<void> => {
        const { id, settingObj, settingContentObj, languages } = data;
        const { transaction } = options;
        try {
            // generate revision for the existing state
            const storeRevisionInput: SettingStoreRevisionDaoInput = { id };
            await this.storeRevision(storeRevisionInput, options);
            await Models.Setting.update({ ...settingObj, lastUpdatedBy: this.userId }, { where: { id: id, accountId: this.accountId }, transaction: transaction })
            // check if content exists in requested language
            let verification = await Models.SettingContent.findOne({ where: { settingId: id, languageId: languages.requested.id } });
            if (verification) { // update if content exists in the requested language
                let settingContent: SettingContentObject = { ...settingContentObj, ...{ languageId: languages.requested.id, settingId: id } }
                await Models.SettingContent.update(settingContent, { where: { id: verification.id }, transaction: transaction })

            } else { // create if content does not exists in the requested language
                let settingContent: SettingContentObject = { ...settingContentObj, ...{ languageId: languages.requested.id, settingId: id } }
                await Models.SettingContent.create(settingContent, { transaction: transaction })
            }
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // delete content type
    delete = async (data: SettingDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        const { id } = data;
        const { transaction } = options;
        try {
            const getSettingByIdInput: SettingGetByIdDaoInput = { id, expanded: false }
            let setting = await this.getSettingById(getSettingByIdInput, options)
            let code = setting.code + '-' + Moment().toISOString();
            await Models.Setting.update({ lastUpdatedBy: this.userId, code: code }, { where: { id: id, accountId: this.accountId }, transaction: transaction });
            await Models.Setting.destroy({ where: { id: id, accountId: this.accountId }, transaction: 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, status: number | 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`.`label`,`defaultContent`.`value_text`,`defaultContent`.`value_string`) AGAINST(:alphaString IN BOOLEAN MODE)'));
                (where[Op.and] as any[]).push(Sequelize.literal('MATCH(`content`.`label`,`content`.`value_text`,`content`.`value_string`) AGAINST(:alphaString IN BOOLEAN MODE)'));
            } if (specialString) {
                (where[Op.and] as any[]).push(Sequelize.literal('MATCH(`defaultContent`.`label`,`defaultContent`.`value_text`,`defaultContent`.`value_string`) AGAINST(:specialString IN BOOLEAN MODE)'));
                (where[Op.and] as any[]).push(Sequelize.literal('MATCH(`content`.`label`,`content`.`value_text`,`content`.`value_string`) AGAINST(:specialString IN BOOLEAN MODE)'));
            }
        }
        if (status != null) {
            where = { ...where, status: status }
        }
        return { where: where, replacements: { alphaString: alphaString, specialString: specialString } }
    }

    // list category types with pagination
    getSettingList = async (data: SettingGetListDaoInput, options: DaoOptions = {}): Promise<SettingPaginatedData> => {
        const { categoryId, listRequest } = data;
        const { transaction } = options;
        try {
            const { page, perPage, searchText, sortBy, sortDirection, status } = listRequest;
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = { categoryId: categoryId, accountId: this.accountId, isRevision: false, [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const settings = await Models.Setting.findAndCountAll({
                attributes: settingAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(false),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                transaction
            });
            return JSON.parse(JSON.stringify(settings)) as unknown as SettingPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // list all category types
    getAllSettings = async (data: SettingGetAllListDaoInput, options: DaoOptions = {}): Promise<SettingObjectSummaryInteface[]> => {
        const { categoryId, listRequest } = data;
        const { transaction } = options;
        try {
            const { searchText, sortBy, sortDirection, status } = listRequest
            let where: WhereOptions & { [Op.and]: any[] } = { categoryId: categoryId, isRevision: false, accountId: this.accountId, [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const settings = await Models.Setting.findAll({
                attributes: settingAttributes,
                where: applyfilter.where,
                replacements: applyfilter.replacements,
                include: this.includeAssociations(false),
                order: orderBy,
                transaction
            });
            return JSON.parse(JSON.stringify(settings)) as unknown as SettingObjectSummaryInteface[];
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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

    // restore revision for category types
    restoreRevision = async (data: SettingRestoreRevisionDaoInput, options: DaoOptions = {}): Promise<number> => {
        const { id } = data;
        const { transaction } = options;
        try {
            const doExistsByIdInput: SettingDoExistsByIdDaoInput = { id, includeRevision: true };
            if (await this.doExistsById(doExistsByIdInput, options)) {
                // get full revision Data
                const getFullObjectInput: SettingGetFullObjectDaoInput = { id };
                let Object: SettingInterface = await this.getFullObject(getFullObjectInput, options);
                if (Object && Object.revisionId) {
                    // create new revision from existing state
                    const storeRevisionInput: SettingStoreRevisionDaoInput = { id: Object.revisionId };
                    await this.storeRevision(storeRevisionInput, 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.Setting.update({ lastUpdatedBy: this.userId }, { where: { id: revisonObject.revisionId }, transaction: transaction });
                    // update entity type content
                    if (revisonObject.settingContents && revisonObject.settingContents.length) {
                        for (let content of revisonObject.settingContents) {
                            await Models.SettingContent.upsert({ label: content.label, valueString: content.valueString, valueText: content.valueText, valueNumeric: content.valueNumeric, languageId: content.languageId, settingId: Object.revisionId }, { transaction: 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, 'SETTING_NOT_FOUND', { id: 'SETTING_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get category type id from category type code
    getIdFromCode = async (data: SettingGetIdFromCodeDaoInput, options: DaoOptions = {}): Promise<number> => {
        const { code } = data;
        const { transaction } = options;
        try {
            let setting = await Models.Setting.findOne({ attributes: ['id'], where: { code: code, accountId: this.accountId }, transaction });
            if (setting) {
                return setting.id;
            } else {
                throw new AppError(404, 'SETTING_NOT_FOUND', { code: 'SETTING_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    private getSettingTree = async (data: SettingGetTreeDaoInput, options: DaoOptions = {}): Promise<SettingTree[]> => {
        const { categoryTypeId, parentId } = data;
        const { transaction } = options;
        try {
            let arrayIds: number[] = [];
            let tree: SettingTree[] = [];
            let parentFlag = "";
            let categories = await Models.Category.findAll({
                attributes: categoryTreeAttributes,
                include: this.includeCategoryAssociation(),
                where: {
                    parentId: parentId,
                    status: CATEGORY.STATUS.ACTIVE,
                    isRevision: false,
                    accountId: this.accountId
                },
                order: [['sortOrder', 'ASC']],
                transaction
            });
            if (categories.length > 0) {
                for (let category of categories) {
                    const settings = await Models.Setting.findAll({
                        attributes: settingAttributes,
                        where: { categoryId: category.id, isRevision: false },
                        replacements: {},
                        include: this.includeAssociations(false),
                        transaction,
                    });
                    category = JSON.parse(JSON.stringify(category));
                    if (category.name && category.id) {
                        let categoryObject: SettingTree = {
                            id: category.id,
                            name: category.name,
                            code: category.code,
                            categorySettings: settings,
                            childCategories: []
                        };
                        let childTree = await this.getSettingTree({ categoryTypeId, parentId: category.id }, options);
                        categoryObject.childCategories = childTree;
                        tree.push(categoryObject);
                    }
                }
                return tree;
            } else {
                return []
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    };

    public getSettingTreeStructure = async (data: SettingGetTreeStructureDaoInput, options: DaoOptions = {}): Promise<CategoryTree[]> => {
        const { categoryTypeId, parentId } = data;
        try {
            const getSettingTreeInput: SettingGetTreeDaoInput = { categoryTypeId, parentId };
            const responseData = await this.getSettingTree(getSettingTreeInput, options);
            if (responseData) {
                return JSON.parse(JSON.stringify(responseData)) as unknown as CategoryTree[];
            } else {
                throw new AppError(400, 'ERROR_WHILE_GENERATING_TREE', {});
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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

    // update setting value
    updateSettingValue = async (data: SettingUpdateSettingValueDaoInput, options: DaoOptions = {}): Promise<void> => {
        const { updatedSetting, languages } = data;
        try {
            for (let setting of updatedSetting) {
                let settingObj = _.chain(setting).pick(['code', 'categoryId']).value() as unknown as SettingObject;
                let settingContentObj = _.chain(setting).pick(['label', 'valueText', 'valueString', 'valueBoolean', 'valueNumeric']).value() as unknown as SettingContentObject;
                const updateInput: SettingUpdateDaoInput = { id: setting.settingId, settingObj, settingContentObj, languages };
                await this.update(updateInput, options);
            }
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get account sessting
    getAccountSettings = async (data: SettingGetAccountSettingsDaoInput, options: DaoOptions = {}): Promise<SettingData[]> => {
        const { accountId } = data;
        const { transaction } = options;
        try {
            let allAccountSettings = await Models.Setting.findAll({ attributes: allSettingAttributes, where: { status: SETTING.STATUS.ACTIVE, isRevision: false, accountId: accountId }, include: this.includeAssociations(false), transaction });
            return JSON.parse(JSON.stringify(allAccountSettings));
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
}
