import { defineStore } from 'pinia';

import transactionConstants from '@/consts/transaction.js';
import templateConstants from '@/consts/template.js';
import { isDefined, isObject, isArray, isEquals } from '@/lib/common.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';

function loadTransactionTemplateList(state, templateType, templates) {
    state.allTransactionTemplates[templateType] = templates;
    state.allTransactionTemplatesMap[templateType] = {};

    for (let i = 0; i < templates.length; i++) {
        const template = templates[i];
        state.allTransactionTemplatesMap[templateType][template.id] = template;
    }
}

function addTemplateToTransactionTemplateList(state, templateType, template) {
    if (isArray(state.allTransactionTemplates[templateType])) {
        state.allTransactionTemplates[templateType].push(template);
    }

    if (isObject(state.allTransactionTemplatesMap[templateType])) {
        state.allTransactionTemplatesMap[templateType][template.id] = template;
    }
}

function updateTemplateInTransactionTemplateList(state, templateType, template) {
    if (isArray(state.allTransactionTemplates[templateType])) {
        for (let i = 0; i < state.allTransactionTemplates[templateType].length; i++) {
            if (state.allTransactionTemplates[templateType][i].id === template.id) {
                state.allTransactionTemplates[templateType].splice(i, 1, template);
                break;
            }
        }
    }

    if (isObject(state.allTransactionTemplatesMap[templateType])) {
        state.allTransactionTemplatesMap[templateType][template.id] = template;
    }
}

function updateTemplateDisplayOrderInTransactionTemplateList(state, templateType, { from, to }) {
    if (isArray(state.allTransactionTemplates[templateType])) {
        state.allTransactionTemplates[templateType].splice(to, 0, state.allTransactionTemplates[templateType].splice(from, 1)[0]);
    }
}

function updateTemplateVisibilityInTransactionTemplateList(state, templateType, { template, hidden }) {
    if (isObject(state.allTransactionTemplatesMap[templateType])) {
        if (state.allTransactionTemplatesMap[templateType][template.id]) {
            state.allTransactionTemplatesMap[templateType][template.id].hidden = hidden;
        }
    }
}

function removeTemplateFromTransactionTemplateList(state, templateType, template) {
    if (isArray(state.allTransactionTemplates[templateType])) {
        for (let i = 0; i < state.allTransactionTemplates[templateType].length; i++) {
            if (state.allTransactionTemplates[templateType][i].id === template.id) {
                state.allTransactionTemplates[templateType].splice(i, 1);
                break;
            }
        }
    }

    if (isObject(state.allTransactionTemplatesMap[templateType])) {
        if (state.allTransactionTemplatesMap[templateType][template.id]) {
            delete state.allTransactionTemplatesMap[templateType][template.id];
        }
    }
}

export const useTransactionTemplatesStore = defineStore('transactionTemplates', {
    state: () => ({
        allTransactionTemplates: {},
        allTransactionTemplatesMap: {},
        transactionTemplateListStatesInvalid: {},
    }),
    getters: {
        allVisibleTemplates(state) {
            const allVisibleTemplates = {};

            for (const templateType in state.allTransactionTemplates) {
                if (!Object.prototype.hasOwnProperty.call(state.allTransactionTemplates, templateType)) {
                    continue;
                }

                const visibleTemplates = [];

                for (let i = 0; i < state.allTransactionTemplates[templateType].length; i++) {
                    const template = state.allTransactionTemplates[templateType][i];

                    if (!template.hidden) {
                        visibleTemplates.push(template);
                    }
                }

                allVisibleTemplates[templateType] = visibleTemplates;
            }

            return allVisibleTemplates;
        },
        allAvailableTemplatesCount(state) {
            const allAvailableTemplateCounts = {};

            for (const templateType in state.allTransactionTemplates) {
                if (!Object.prototype.hasOwnProperty.call(state.allTransactionTemplates, templateType)) {
                    continue;
                }

                allAvailableTemplateCounts[templateType] = state.allTransactionTemplates[templateType].length;
            }

            return allAvailableTemplateCounts;
        },
        allVisibleTemplatesCount(state) {
            const allVisibleTemplateCounts = {};

            for (const templateType in state.allVisibleTemplates) {
                if (!Object.prototype.hasOwnProperty.call(state.allVisibleTemplates, templateType)) {
                    continue;
                }

                allVisibleTemplateCounts[templateType] = state.allVisibleTemplates[templateType].length;
            }

            return allVisibleTemplateCounts;
        }
    },
    actions: {
        updateTransactionTemplateListInvalidState(templateType, invalidState) {
            this.transactionTemplateListStatesInvalid[templateType] = invalidState;
        },
        resetTransactionTemplates() {
            this.allTransactionTemplates = {};
            this.allTransactionTemplatesMap = {};
            this.transactionTemplateListStatesInvalid = {};
        },
        loadAllTemplates({ templateType, force }) {
            const self = this;

            if (!force && isDefined(self.transactionTemplateListStatesInvalid[templateType]) && !self.transactionTemplateListStatesInvalid[templateType]) {
                return new Promise((resolve) => {
                    resolve(self.allTransactionTemplates[templateType] || []);
                });
            }

            return new Promise((resolve, reject) => {
                services.getAllTransactionTemplates({ templateType }).then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        reject({ message: 'Unable to retrieve template list' });
                        return;
                    }

                    if (!isDefined(self.transactionTemplateListStatesInvalid[templateType]) || self.transactionTemplateListStatesInvalid[templateType]) {
                        self.updateTransactionTemplateListInvalidState(templateType, false);
                    }

                    if (force && data.result && isEquals(self.allTransactionTemplates[templateType], data.result)) {
                        reject({ message: 'Template list is up to date' });
                        return;
                    }

                    loadTransactionTemplateList(self, templateType, data.result);

                    resolve(data.result);
                }).catch(error => {
                    if (force) {
                        logger.error('failed to force load template list', error);
                    } else {
                        logger.error('failed to load template list', error);
                    }

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        reject({ message: 'Unable to retrieve template list' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        getTemplate({ templateId }) {
            return new Promise((resolve, reject) => {
                services.getTransactionTemplate({
                    id: templateId
                }).then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        reject({ message: 'Unable to retrieve template' });
                        return;
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to load template info', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        reject({ message: 'Unable to retrieve template' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        saveTemplateContent({ template, isEdit, clientSessionId }) {
            const self = this;

            const submitTemplate = {
                templateType: template.templateType,
                name: template.name,
                type: template.type,
                sourceAccountId: template.sourceAccountId,
                sourceAmount: template.sourceAmount,
                destinationAccountId: '0',
                destinationAmount: 0,
                hideAmount: template.hideAmount,
                tagIds: template.tagIds,
                comment: template.comment
            };

            if (clientSessionId) {
                submitTemplate.clientSessionId = clientSessionId;
            }

            if (template.templateType === templateConstants.allTemplateTypes.Schedule) {
                submitTemplate.scheduledFrequencyType = template.scheduledFrequencyType;
                submitTemplate.scheduledFrequency = template.scheduledFrequency;
                submitTemplate.utcOffset = template.utcOffset;
            }

            if (template.type === transactionConstants.allTransactionTypes.Expense) {
                submitTemplate.categoryId = template.expenseCategory;
            } else if (template.type === transactionConstants.allTransactionTypes.Income) {
                submitTemplate.categoryId = template.incomeCategory;
            } else if (template.type === transactionConstants.allTransactionTypes.Transfer) {
                submitTemplate.categoryId = template.transferCategory;
                submitTemplate.destinationAccountId = template.destinationAccountId;
                submitTemplate.destinationAmount = template.destinationAmount;
            } else {
                return Promise.reject('An error occurred');
            }

            if (isEdit) {
                submitTemplate.id = template.id;
            }

            return new Promise((resolve, reject) => {
                let promise = null;

                if (!submitTemplate.id) {
                    promise = services.addTransactionTemplate(submitTemplate);
                } else {
                    promise = services.modifyTransactionTemplate(submitTemplate);
                }

                promise.then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        if (!submitTemplate.id) {
                            reject({ message: 'Unable to add template' });
                        } else {
                            reject({ message: 'Unable to save template' });
                        }
                        return;
                    }

                    if (!submitTemplate.id) {
                        addTemplateToTransactionTemplateList(self, template.templateType, data.result);
                    } else {
                        updateTemplateInTransactionTemplateList(self, template.templateType, data.result);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to save template', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        if (!submitTemplate.id) {
                            reject({ message: 'Unable to add template' });
                        } else {
                            reject({ message: 'Unable to save template' });
                        }
                    } else {
                        reject(error);
                    }
                });
            });
        },
        changeTemplateDisplayOrder({ templateType, templateId, from, to }) {
            const self = this;

            return new Promise((resolve, reject) => {
                let template = null;

                if (!isArray(self.allTransactionTemplates[templateType])) {
                    reject({ message: 'Unable to move template' });
                    return;
                }

                for (let i = 0; i < self.allTransactionTemplates[templateType].length; i++) {
                    if (self.allTransactionTemplates[templateType][i].id === templateId) {
                        template = self.allTransactionTemplates[templateType][i];
                        break;
                    }
                }

                if (!template || !self.allTransactionTemplates[templateType][to]) {
                    reject({ message: 'Unable to move template' });
                    return;
                }

                if (isDefined(self.transactionTemplateListStatesInvalid[templateType]) && !self.transactionTemplateListStatesInvalid[templateType]) {
                    self.updateTransactionTemplateListInvalidState(templateType, true);
                }

                updateTemplateDisplayOrderInTransactionTemplateList(self, templateType, {
                    template: template,
                    from: from,
                    to: to
                });

                resolve();
            });
        },
        updateTemplateDisplayOrders({ templateType }) {
            const self = this;
            const newDisplayOrders = [];

            if (isArray(self.allTransactionTemplates[templateType])) {
                for (let i = 0; i < self.allTransactionTemplates[templateType].length; i++) {
                    newDisplayOrders.push({
                        id: self.allTransactionTemplates[templateType][i].id,
                        displayOrder: i + 1
                    });
                }
            }

            return new Promise((resolve, reject) => {
                services.moveTransactionTemplate({
                    newDisplayOrders: newDisplayOrders
                }).then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        reject({ message: 'Unable to move template' });
                        return;
                    }

                    if (!isDefined(self.transactionTemplateListStatesInvalid[templateType]) || self.transactionTemplateListStatesInvalid[templateType]) {
                        self.updateTransactionTemplateListInvalidState(templateType, false);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to save templates display order', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        reject({ message: 'Unable to move template' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        hideTemplate({ template, hidden }) {
            const self = this;

            return new Promise((resolve, reject) => {
                services.hideTransactionTemplate({
                    id: template.id,
                    hidden: hidden
                }).then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        if (hidden) {
                            reject({ message: 'Unable to hide this template' });
                        } else {
                            reject({ message: 'Unable to unhide this template' });
                        }

                        return;
                    }

                    updateTemplateVisibilityInTransactionTemplateList(self, template.templateType, {
                        template: template,
                        hidden: hidden
                    });

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to change template visibility', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        if (hidden) {
                            reject({ message: 'Unable to hide this template' });
                        } else {
                            reject({ message: 'Unable to unhide this template' });
                        }
                    } else {
                        reject(error);
                    }
                });
            });
        },
        deleteTemplate({ template, beforeResolve }) {
            const self = this;

            return new Promise((resolve, reject) => {
                services.deleteTransactionTemplate({
                    id: template.id
                }).then(response => {
                    const data = response.data;

                    if (!data || !data.success || !data.result) {
                        reject({ message: 'Unable to delete this template' });
                        return;
                    }

                    if (beforeResolve) {
                        beforeResolve(() => {
                            removeTemplateFromTransactionTemplateList(self, template.templateType, template);
                        });
                    } else {
                        removeTemplateFromTransactionTemplateList(self, template.templateType, template);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to delete template', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        reject({ message: 'Unable to delete this template' });
                    } else {
                        reject(error);
                    }
                });
            });
        }
    }
});
