
import { SettingDao } from "../dao/setting.doa";
import { LanguageService } from "../services/language.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { CategoryService } from "../services/category.service";
import { CategoryTypeService } from "../services/categoryType.service";
import AppCache from "../../utils/appCache";
import { SETTING } from "../config/constants";

export class SettingService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private settingDao: SettingDao;
    private languageService: LanguageService;
    private categoryService: CategoryService;
    private categoryTypeService: CategoryTypeService;
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                scope: null,
                config: null,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.settingDao = new SettingDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.categoryService = new CategoryService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.categoryTypeService = new CategoryTypeService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }
    // revision settings
    public isRevisionEnabled: boolean = (process.env.REVISION_ENABLED !== 'true' ? true : false) || false

    private buildValueContent = (valueType: string, value: string | Text | number | boolean): Omit<SettingContentObject, 'label'> => {
        let valueString: string | null = null;
        let valueText: Text | null = null;
        let valueBoolean: boolean | null = null;
        let valueNumeric: number | null = null;
        switch (valueType) {
            case 'string':
                valueString = value as unknown as string;
                break;
            case 'text':
                valueText = value as unknown as Text;
                break;
            case 'boolean':
                valueBoolean = value as unknown as boolean;
                break;
            case 'number':
                valueNumeric = value as unknown as number;
                break;
            default:
                throw new AppError(400, 'UNSUPPORTED_VALUE_TYPE', { value: 'UNSUPPORTED_VALUE_TYPE' });
        }
        return { valueText, valueString, valueBoolean, valueNumeric };
    }

    // create a new content type
    createSetting = async (data: SettingCreateServiceInput): Promise<SettingObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { categoryCode, code, label, valueType, value, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            const getCategoryIdInput: SettingGetCategoryIdServiceInput = { categoryCode: categoryCode as string };
            const categoryId = categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: categoryId as number, categoryTypeCode: 'setting' };
            let categoryVerification = categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_SETTING_CATEGORY', { categoryCode: 'INVALID_SETTING_CATEGORY' });
            }

            const normalizedCode = Common.slugify(code);
            const doExistsByCodeInput: SettingDoExistsByCodeDaoInput = { code: normalizedCode };
            const exists = await this.settingDao.doExistsByCode(doExistsByCodeInput, { transaction });
            if (exists) throw new AppError(400, 'SETTINGALREADY_EXISTS', { name: 'SETTINGALREADY_EXISTS' });

            const valueContent = this.buildValueContent(valueType, value);
            const settingObj: SettingObject = { code: normalizedCode, categoryId, status: status ?? SETTING.STATUS.ACTIVE };
            const settingContentObj: SettingContentObject = { label, ...valueContent };

            const createSettingInput: SettingCreateDaoInput = { settingObj, settingContentObj, languages };
            let settingIdentifier: number = await this.settingDao.create(createSettingInput, { transaction });
            const setSortOrderInput: SettingSetSortOrderDaoInput = { id: settingIdentifier };
            await this.settingDao.setSortOrder(setSortOrderInput, { transaction });
            const getSettingByIdInput: SettingGetByIdDaoInput = { id: settingIdentifier };
            let setting: SettingObjectInteface = await this.settingDao.getSettingById(getSettingByIdInput, { transaction });
            await transaction.commit();
            return setting;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing content type
    updateSetting = async (data: SettingUpdateServiceInput): Promise<SettingObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, categoryCode, code, label, valueType, value, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            const normalizedCode = Common.slugify(code);
            const getCategoryIdInput: SettingGetCategoryIdServiceInput = { categoryCode: categoryCode as string };
            const categoryId = categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: categoryId as number, categoryTypeCode: 'setting' };
            let categoryVerification = categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) throw new AppError(400, 'INVALID_SETTING_CATEGORY', { categoryCode: 'INVALID_SETTING_CATEGORY' });

            const doExistsByIdInput: SettingDoExistsByIdDaoInput = { id };
            const exists = await this.settingDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) throw new AppError(404, 'SETTING_NOT_FOUND', { name: 'SETTING_NOT_FOUND' });

            const codeInUseInput: SettingDoExistsByCodeDaoInput = { code: normalizedCode, excludeId: id };
            let codeInUse = await this.settingDao.doExistsByCode(codeInUseInput, { transaction });
            if (codeInUse) throw new AppError(400, 'SETTING_ALREADY_EXISTS', { name: 'SETTING_ALREADY_EXISTS' });

            const valueContent = this.buildValueContent(valueType, value);
            const settingObj: SettingObject = { code: normalizedCode, categoryId, status: status ?? SETTING.STATUS.ACTIVE };
            const settingContentObj: SettingContentObject = { label, ...valueContent };
            const updateSettingInput: SettingUpdateDaoInput = { id, settingObj, settingContentObj, languages };
            await this.settingDao.update(updateSettingInput, { transaction });
            const getSettingByIdInput: SettingGetByIdDaoInput = { id };
            let setting: SettingObjectInteface = await this.settingDao.getSettingById(getSettingByIdInput, { transaction });
            await transaction.commit();
            return setting;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing content type
    deleteSetting = async (data: SettingDeleteServiceInput): Promise<SettingObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const deleteSettingInput: SettingDeleteDaoInput = { id };
            await this.settingDao.delete(deleteSettingInput, { transaction });
            const getSettingByIdInput: SettingGetByIdDaoInput = { id, expanded: true, paranoid: false };
            let setting: SettingObjectInteface = await this.settingDao.getSettingById(getSettingByIdInput, { transaction });
            await transaction.commit();
            return setting;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get setting by id
    getById = async (data: SettingGetByIdServiceInput): Promise<SettingObjectInteface> => {
        try {
            const { id, expanded = true } = data;
            const getSettingByIdInput: SettingGetByIdDaoInput = { id, expanded };
            let setting: SettingObjectInteface = await this.settingDao.getSettingById(getSettingByIdInput);
            return setting;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get setting by code
    getByCode = async (data: SettingGetByCodeServiceInput): Promise<SettingObjectInteface> => {
        try {
            const { code, expanded = true } = data;
            const getSettingByCodeInput: SettingGetByCodeDaoInput = { code, expanded };
            let setting: SettingObjectInteface = await this.settingDao.getSettingByCode(getSettingByCodeInput);
            return setting;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list settings
    getSettings = async (data: SettingGetSettingsServiceInput): Promise<SettingPaginatedList> => {
        try {
            const { categoryCode, listRequest } = data;
            const getCategoryIdInput: SettingGetCategoryIdServiceInput = { categoryCode };
            const categoryId = await this.getCategoryId(getCategoryIdInput);
            const { page, perPage } = listRequest;
            const getSettingListInput: SettingGetListDaoInput = { categoryId, listRequest };
            let settings: SettingPaginatedData = await this.settingDao.getSettingList(getSettingListInput);
            let totalPages = Common.getTotalPages(settings.count, perPage);
            return {
                data: settings.rows,
                page: page,
                perPage: perPage,
                totalRecords: settings.count,
                totalPages: totalPages
            } as unknown as SettingPaginatedList;

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

    // list all settings
    getAllSettings = async (data: SettingGetAllSettingsServiceInput): Promise<SettingObjectSummaryInteface[]> => {
        try {
            const { categoryCode, listRequest } = data;
            const getCategoryIdInput: SettingGetCategoryIdServiceInput = { categoryCode };
            const categoryId = await this.getCategoryId(getCategoryIdInput);
            const getAllSettingsInput: SettingGetAllListDaoInput = { categoryId, listRequest };
            let settings: SettingObjectSummaryInteface[] = await this.settingDao.getAllSettings(getAllSettingsInput);
            return settings;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list setting revisions
    getSettingsRevisions = async (data: SettingGetSettingsRevisionsServiceInput): Promise<SettingPaginatedList> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage } = listRequest
            const getSettingRevisionListInput: SettingGetRevisionListDaoInput = { id, listRequest };
            let settings: SettingPaginatedData = await this.settingDao.getSettingRevisionList(getSettingRevisionListInput);
            let totalPages = Common.getTotalPages(settings.count, perPage);
            return {
                data: settings.rows,
                page: page,
                perPage: perPage,
                totalRecords: settings.count,
                totalPages: totalPages
            } as unknown as SettingPaginatedList;

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

    // Restore revision
    restoreRevision = async (data: SettingRestoreRevisionServiceInput): Promise<SettingObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restoreRevisionInput: SettingRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.settingDao.restoreRevision(restoreRevisionInput, { transaction });
            const getSettingByIdInput: SettingGetByIdDaoInput = { id: restoredEntiryId };
            let setting = await this.settingDao.getSettingById(getSettingByIdInput, { transaction });
            await transaction.commit();
            return setting;

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

    // get setting id from code
    getSettingId = async (data: SettingGetSettingIdServiceInput): Promise<number> => {
        try {
            const getIdFromCodeInput: SettingGetIdFromCodeDaoInput = { code: data.code };
            let categoryId = await this.settingDao.getIdFromCode(getIdFromCodeInput);
            return categoryId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get setting tree
    getSettingTree = async (data: SettingGetSettingTreeServiceInput): Promise<CategoryTree[]> => {
        try {
            const { categoryTypeCode, code } = data;
            const getCategoryTypeIdInput: CategoryTypeGetIdServiceInput = { code: categoryTypeCode };
            const categoryTypeId = await this.categoryTypeService.getCategoryTypeId(getCategoryTypeIdInput);
            const getCategoryIdInput: CategoryGetCategoryIdServiceInput = { code: code as string };
            const categoryId = code ? await this.categoryService.getCategoryId(getCategoryIdInput) : null;
            const getSettingTreeStructureInput: SettingGetTreeStructureDaoInput = { categoryTypeId, parentId: categoryId };
            let categoryTree: CategoryTree[] = await this.settingDao.getSettingTreeStructure(getSettingTreeStructureInput);
            if (categoryTree) {
                return categoryTree
            }
            throw new AppError(400, 'CATEGORY_RESTORE_REVISION_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (data: SettingSetSortOrderServiceInput): Promise<boolean> => {
        try {
            const setSortOrderInput: SettingSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            await this.settingDao.setSortOrder(setSortOrderInput);
            return true;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update setting status
    updateStatus = async (data: SettingUpdateStatusServiceInput): Promise<SettingObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, status } = data;
            const getSettingByIdInput: SettingGetByIdDaoInput = { id };
            let setting = await this.settingDao.getSettingById(getSettingByIdInput, { transaction });
            if (setting) {
                const updateStatusInput: SettingUpdateStatusDaoInput = { id, status };
                await this.settingDao.updateStatus(updateStatusInput, { transaction });
                let settingObject = await this.getById({ id, expanded: true });
                await transaction.commit();
                return settingObject
            } else {
                throw new AppError(404, 'QUOTE_NOT_FOUND', { id: 'QUOTE_NOT_FOUND' });
            }
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // 
    getCategoryId = async (data: SettingGetCategoryIdServiceInput) => {
        try {
            const getCategoryIdInput: CategoryGetCategoryIdServiceInput = { code: data.categoryCode };
            return await this.categoryService.getCategoryId(getCategoryIdInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    private getUpdatedSettings = (settings: SettingObjectSummaryInteface[], updates: SettingUpdateObject[]): SettingObjectSummaryInteface[] => {
        const settingsMap = new Map(settings.map(s => [s.code, s]));
        for (const upd of updates) {
            const setting = settingsMap.get(upd.code);
            if (!setting) {
                throw new Error(`Code not found: ${upd.code}`);
            }
            const value = upd.value;
            let isValid = false;

            switch (setting.valueType) {
                case 'string':
                    isValid = typeof value === 'string';
                    if (isValid) setting.value = value as string;
                    break;
                case 'text':
                    isValid = typeof value === 'string'; // assuming Text means long string
                    if (isValid) setting.value = value as string;
                    break;
                case 'boolean':
                    isValid = typeof value === 'boolean';
                    if (isValid) setting.value = value as unknown as boolean;
                    break;
                case 'number':
                    isValid = typeof Number(value) === 'number';
                    if (isValid) setting.value = value as unknown as number;
                    break;
                default:
                    throw new Error(`Unsupported valueType: ${setting.valueType}`);
            }
            if (!isValid) {
                throw new Error(`Invalid value type for ${upd.code}, expected ${setting.valueType}`);
            }
        }
        return settings;
    }

    // update settings for a category
    updateCategorySettings = async (data: SettingUpdateCategorySettingsServiceInput) => {
        const transaction = await sequelize.transaction();
        try {
            const { categoryCode, settings } = data;
            const getCategoryIdInput: SettingGetCategoryIdServiceInput = { categoryCode };
            const categoryId = categoryCode ? await this.getCategoryId(getCategoryIdInput) : null;
            if (!categoryId) throw new AppError(404, 'INVALID_SETTING_CATEGORY', {});
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: categoryId, categoryTypeCode: 'setting' };
            let categoryVerification = this.categoryService.verifyCategoryId(verifyCategoryIdInput);
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_SETTING_CATEGORY', { categoryCode: 'INVALID_SETTING_CATEGORY' });
            }
            const listAllRequest: SettingListAllRequestObject = { sortBy: 'sortOrder', sortDirection: 'asc', searchText: null, status: null };
            const getAllSettingsInput: SettingGetAllListDaoInput = { categoryId, listRequest: listAllRequest };
            let categorySettings = await this.settingDao.getAllSettings(getAllSettingsInput, { transaction });
            let updatedSettingArray: SettingUpdateValueObject[] = []
            let updatedSettings = this.getUpdatedSettings(categorySettings, settings);
            for (let setting of updatedSettings) {
                let valueString: string | null = null;
                let valueText: Text | null = null;
                let valueBoolean: boolean | null = null;
                let valueNumeric: number | null = null;

                switch (setting.valueType) {
                    case 'string':
                        valueString = setting.value as unknown as string;
                        break;
                    case 'text':
                        valueText = setting.value as unknown as Text;
                        break;
                    case 'boolean':
                        valueBoolean = setting.value as unknown as boolean;
                        break;
                    case 'number':
                        valueNumeric = setting.value as unknown as number;
                        break;
                    default:
                        throw new AppError(400, 'UNSUPPORTED_VALUE_TYPE', { value: 'UNSUPPORTED_VALUE_TYPE' });
                }
                let updatedValues = { settingId: setting.id, code: setting.code, categoryId: categoryId, status: null, label: setting.label, valueString: valueString, valueText: valueText, valueBoolean: valueBoolean, valueNumeric: valueNumeric } as SettingUpdateValueObject
                updatedSettingArray.push(updatedValues);

            }
            const updateSettingValueInput: SettingUpdateSettingValueDaoInput = { updatedSetting: updatedSettingArray, languages };
            await this.settingDao.updateSettingValue(updateSettingValueInput, { transaction })
            let updtaedatedSettings = await this.settingDao.getAllSettings(getAllSettingsInput, { transaction });
            await transaction.commit();
            return updtaedatedSettings;

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

    // set all account settings
    setAccountSettings = async (): Promise<void> => {
        try {
            const getAccountSettingsInput: SettingGetAccountSettingsDaoInput = { accountId: this.accountId };
            let accountSettings = await this.settingDao.getAccountSettings(getAccountSettingsInput);
            AppCache.set("setting_" + this.accountId, Common.arrayToObject(accountSettings));
            await Common.EmitEvent(null, 'updateCache', { index: "setting_" + this.accountId, value: Common.arrayToObject(accountSettings) })
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get all account settings
    getAccountSettings = async (accountId: number | null): Promise<SettingData[]> => {
        try {
            const getAccountSettingsInput: SettingGetAccountSettingsDaoInput = { accountId };
            let accountSettings = this.settingDao.getAccountSettings(getAccountSettingsInput);
            return accountSettings;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get account setting
    getSetting = (key: string): string | number | boolean | Text | null => {
        try {
            let accountSetting: any = AppCache.get("setting_" + this.accountId);
            return (accountSetting && accountSetting[key]) ? accountSetting[key] : null
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
