import { defineStore } from 'pinia';

import categoryConstants from '@/consts/category.js';
import iconConstants from '@/consts/icon.js';
import colorConstants from '@/consts/color.js';
import { isEquals } from '@/lib/common.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';

function loadTransactionCategoryList(state, allCategories) {
    state.allTransactionCategories = allCategories;
    state.allTransactionCategoriesMap = {};

    for (let categoryType in allCategories) {
        if (!Object.prototype.hasOwnProperty.call(allCategories, categoryType)) {
            continue;
        }

        const categories = allCategories[categoryType];

        for (let i = 0; i < categories.length; i++) {
            const category = categories[i];
            state.allTransactionCategoriesMap[category.id] = category;

            for (let j = 0; j < category.subCategories.length; j++) {
                const subCategory = category.subCategories[j];
                state.allTransactionCategoriesMap[subCategory.id] = subCategory;
            }
        }
    }
}

function addCategoryToTransactionCategoryList(state, category) {
    let categoryList = null;

    if (!category.parentId || category.parentId === '0') {
        categoryList = state.allTransactionCategories[category.type];
    } else if (state.allTransactionCategoriesMap[category.parentId]) {
        categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
    }

    if (categoryList) {
        categoryList.push(category);
    }

    state.allTransactionCategoriesMap[category.id] = category;
}

function updateCategoryInTransactionCategoryList(state, category, oldCategory) {
    if (oldCategory && category.parentId !== oldCategory.parentId) {
        return false;
    }

    let categoryList = null;

    if (!category.parentId || category.parentId === '0') {
        categoryList = state.allTransactionCategories[category.type];
    } else if (state.allTransactionCategoriesMap[category.parentId]) {
        categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
    }

    if (categoryList) {
        for (let i = 0; i < categoryList.length; i++) {
            if (categoryList[i].id === category.id) {
                if (!category.parentId || category.parentId === '0') {
                    category.subCategories = categoryList[i].subCategories;
                }

                categoryList.splice(i, 1, category);
                break;
            }
        }
    }

    state.allTransactionCategoriesMap[category.id] = category;
    return true;
}

function updateCategoryDisplayOrderInCategoryList(state, { category, from, to }) {
    let categoryList = null;

    if (!category.parentId || category.parentId === '0') {
        categoryList = state.allTransactionCategories[category.type];
    } else if (state.allTransactionCategoriesMap[category.parentId]) {
        categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
    }

    if (categoryList) {
        categoryList.splice(to, 0, categoryList.splice(from, 1)[0]);
    }
}

function updateCategoryVisibilityInTransactionCategoryList(state, { category, hidden }) {
    if (state.allTransactionCategoriesMap[category.id]) {
        state.allTransactionCategoriesMap[category.id].hidden = hidden;
    }
}

function removeCategoryFromTransactionCategoryList(state, category) {
    let categoryList = null;

    if (!category.parentId || category.parentId === '0') {
        categoryList = state.allTransactionCategories[category.type];
    } else if (state.allTransactionCategoriesMap[category.parentId]) {
        categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
    }

    if (categoryList) {
        for (let i = 0; i < categoryList.length; i++) {
            if (categoryList[i].id === category.id) {
                categoryList.splice(i, 1);
                break;
            }
        }
    }

    if (state.allTransactionCategoriesMap[category.id] && state.allTransactionCategoriesMap[category.id].subCategories) {
        const subCategories = state.allTransactionCategoriesMap[category.id].subCategories;

        for (let i = 0; i < subCategories.length; i++) {
            const subCategory = subCategories[i];
            if (state.allTransactionCategoriesMap[subCategory.id]) {
                delete state.allTransactionCategoriesMap[subCategory.id];
            }
        }
    }

    if (state.allTransactionCategoriesMap[category.id]) {
        delete state.allTransactionCategoriesMap[category.id];
    }
}

export const useTransactionCategoriesStore = defineStore('transactionCategories', {
    state: () => ({
        allTransactionCategories: {},
        allTransactionCategoriesMap: {},
        transactionCategoryListStateInvalid: true,
    }),
    actions: {
        generateNewTransactionCategoryModel(type, parentId) {
            return {
                type: type || categoryConstants.allCategoryTypes.Income,
                name: '',
                parentId: parentId || '0',
                icon: iconConstants.defaultCategoryIconId,
                color: colorConstants.defaultCategoryColor,
                comment: '',
                visible: true
            };
        },
        updateTransactionCategoryListInvalidState(invalidState) {
            this.transactionCategoryListStateInvalid = invalidState;
        },
        resetTransactionCategories() {
            this.allTransactionCategories = {};
            this.allTransactionCategoriesMap = {};
            this.transactionCategoryListStateInvalid = true;
        },
        loadAllCategories({ force }) {
            const self = this;

            if (!force && !self.transactionCategoryListStateInvalid) {
                return new Promise((resolve) => {
                    resolve(self.allTransactionCategories);
                });
            }

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

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

                    if (!data.result[categoryConstants.allCategoryTypes.Income]) {
                        data.result[categoryConstants.allCategoryTypes.Income] = [];
                    }

                    if (!data.result[categoryConstants.allCategoryTypes.Expense]) {
                        data.result[categoryConstants.allCategoryTypes.Expense] = [];
                    }

                    if (!data.result[categoryConstants.allCategoryTypes.Transfer]) {
                        data.result[categoryConstants.allCategoryTypes.Transfer] = [];
                    }

                    for (let categoryType in data.result) {
                        if (!Object.prototype.hasOwnProperty.call(data.result, categoryType)) {
                            continue;
                        }

                        const categories = data.result[categoryType];

                        for (let i = 0; i < categories.length; i++) {
                            const category = categories[i];

                            if (!category.subCategories) {
                                category.subCategories = [];
                            }
                        }
                    }

                    if (self.transactionCategoryListStateInvalid) {
                        self.updateTransactionCategoryListInvalidState(false);
                    }

                    if (force && data.result && isEquals(self.allTransactionCategories, data.result)) {
                        reject({ message: 'Category list is up to date' });
                        return;
                    }

                    loadTransactionCategoryList(self, data.result);

                    resolve(data.result);
                }).catch(error => {
                    if (force) {
                        logger.error('failed to force load category list', error);
                    } else {
                        logger.error('failed to load category 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 category list' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        getCategory({ categoryId }) {
            return new Promise((resolve, reject) => {
                services.getTransactionCategory({
                    id: categoryId
                }).then(response => {
                    const data = response.data;

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

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to load category 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 category' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        saveCategory({ category, isEdit, clientSessionId }) {
            const self = this;

            const submitCategory = {
                type: category.type,
                name: category.name,
                parentId: category.parentId,
                icon: category.icon,
                color: category.color,
                comment: category.comment
            };

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

            if (isEdit) {
                submitCategory.id = category.id;
                submitCategory.hidden = !category.visible;
            }

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

                if (!submitCategory.id) {
                    promise = services.addTransactionCategory(submitCategory);
                } else {
                    promise = services.modifyTransactionCategory(submitCategory);
                }

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

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

                    if (!data.result.subCategories) {
                        data.result.subCategories = [];
                    }

                    if (!submitCategory.id) {
                        addCategoryToTransactionCategoryList(self, data.result);
                    } else {
                        const result = updateCategoryInTransactionCategoryList(self, data.result, self.allTransactionCategoriesMap[submitCategory.id]);

                        if (!result && !self.transactionCategoryListStateInvalid) {
                            self.updateTransactionCategoryListInvalidState(true);
                        }
                    }

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

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        if (!submitCategory.id) {
                            reject({ message: 'Unable to add category' });
                        } else {
                            reject({ message: 'Unable to save category' });
                        }
                    } else {
                        reject(error);
                    }
                });
            });
        },
        addCategories({ categories }) {
            const self = this;

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

                    if (!data || !data.success || !data.result) {
                        reject({ message: 'Unable to add preset categories' });
                        return;
                    }

                    if (!self.transactionCategoryListStateInvalid) {
                        self.updateTransactionCategoryListInvalidState(true);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to add preset categories', error);

                    if (error.response && error.response.data && error.response.data.errorMessage) {
                        reject({ error: error.response.data });
                    } else if (!error.processed) {
                        reject({ message: 'Unable to add preset categories' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        changeCategoryDisplayOrder({ categoryId, from, to }) {
            const self = this;
            const category = self.allTransactionCategoriesMap[categoryId];

            return new Promise((resolve, reject) => {
                if (!category) {
                    reject({ message: 'Unable to move category' });
                    return;
                }

                if (!category.parentId || category.parentId === '0') {
                    if (!self.allTransactionCategories[category.type] ||
                        !self.allTransactionCategories[category.type][to]) {
                        reject({ message: 'Unable to move category' });
                        return;
                    }
                } else {
                    if (!self.allTransactionCategoriesMap[category.parentId].subCategories ||
                        !self.allTransactionCategoriesMap[category.parentId].subCategories[to]) {
                        reject({ message: 'Unable to move category' });
                        return;
                    }
                }

                if (!self.transactionCategoryListStateInvalid) {
                    self.updateTransactionCategoryListInvalidState(true);
                }

                updateCategoryDisplayOrderInCategoryList(self, {
                    category: category,
                    from: from,
                    to: to
                });

                resolve();
            });
        },
        updateCategoryDisplayOrders({ type, parentId }) {
            const self = this;
            const newDisplayOrders = [];

            let categoryList = null;

            if (!parentId || parentId === '0') {
                categoryList = self.allTransactionCategories[type];
            } else if (self.allTransactionCategoriesMap[parentId]) {
                categoryList = self.allTransactionCategoriesMap[parentId].subCategories;
            }

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

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

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

                    if (self.transactionCategoryListStateInvalid) {
                        self.updateTransactionCategoryListInvalidState(false);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to save categories 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 category' });
                    } else {
                        reject(error);
                    }
                });
            });
        },
        hideCategory({ category, hidden }) {
            const self = this;

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

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

                        return;
                    }

                    updateCategoryVisibilityInTransactionCategoryList(self, {
                        category: category,
                        hidden: hidden
                    });

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to change category 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 category' });
                        } else {
                            reject({ message: 'Unable to unhide this category' });
                        }
                    } else {
                        reject(error);
                    }
                });
            });
        },
        deleteCategory({ category, beforeResolve }) {
            const self = this;

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

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

                    if (beforeResolve) {
                        beforeResolve(() => {
                            removeCategoryFromTransactionCategoryList(self, category);
                        });
                    } else {
                        removeCategoryFromTransactionCategoryList(self, category);
                    }

                    resolve(data.result);
                }).catch(error => {
                    logger.error('failed to delete category', 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 category' });
                    } else {
                        reject(error);
                    }
                });
            });
        }
    }
});
