import {createStore} from 'vuex';
import router from './router';

import * as base64 from 'base-64';
import * as utf8 from 'utf8';
import {ticketStateColorLookup, ticketStateIconLookup, http, http_session} from "@/utils";
import sharedStatePlugin from "@/shared-state-plugin";
import persistentStatePlugin from "@/persistent-state-plugin";

const store = createStore({
    state: {
        keyIncrement: 0,
        events: [],
        items: [],
        loadedBoxes: [],
        toasts: [],
        users: [],
        groups: [],
        state_options: [],
        shippingVouchers: [],

        loadedItems: {},
        loadedTickets: {},

        lastEvent: 'all',
        lastUsed: {},
        searchQuery: '',
        remember: false,
        user: {
            username: null,
            password: null,
            permissions: [],
            token: null,
            expiry: null,
        },

        thumbnailCache: {},
        fetchedData: {
            events: 0,
            items: 0,
            boxes: 0,
            tickets: 0,
            users: 0,
            groups: 0,
            states: 0,
            shippingVouchers: 0,
        },
        persistent_loaded: false,
        shared_loaded: false,
        afterInitHandlers: [],

        showAddBoxModal: false,
        showAddEventModal: false,

        shippingVoucherTypes: {
            '2kg-de': '2kg Paket (DE)',
            '5kg-de': '5kg Paket (DE)',
            '10kg-de': '10kg Paket (DE)',
            '2kg-eu': '2kg Paket (EU)',
            '5kg-eu': '5kg Paket (EU)',
            '10kg-eu': '10kg Paket (EU)',
        }
    },
    getters: {
        route: state => router.currentRoute.value,
        session: state => http_session(state.user.token),
        getEventSlug: state => router.currentRoute.value.params.event ? router.currentRoute.value.params.event : state.lastEvent,
        getAllItems: state => Object.values(state.loadedItems).flat(),
        getAllTickets: state => Object.values(state.loadedTickets).flat(),
        getEventItems: (state, getters) => getters.getEventSlug === 'all' ? getters.getAllItems : getters.getAllItems.filter(t => t.event === getters.getEventSlug || (t.event == null && getters.getEventSlug === 'none')),
        getEventTickets: (state, getters) => getters.getEventSlug === 'all' ? getters.getAllTickets : getters.getAllTickets.filter(t => t.event === getters.getEventSlug || (t.event == null && getters.getEventSlug === 'none')),
        isItemsLoaded: (state, getters) => (getters.getEventSlug === 'all' || getters.getEventSlug === 'none') ? !!state.loadedItems : Object.keys(state.loadedItems).includes(getters.getEventSlug),
        isTicketsLoaded: (state, getters) => (getters.getEventSlug === 'all' || getters.getEventSlug === 'none') ? !!state.loadedTickets : Object.keys(state.loadedTickets).includes(getters.getEventSlug),
        getActiveView: state => router.currentRoute.value.name || 'items',
        getFilters: state => router.currentRoute.value.query,
        getBoxes: state => state.loadedBoxes,
        checkPermission: state => (event, perm) => state.user.permissions &&
            (state.user.permissions.includes(`${event}:${perm}`) || state.user.permissions.includes(`*:${perm}`)),
        hasPermissions: state => state.user.permissions && state.user.permissions.length > 0,
        activeUser: state => state.user.username || 'anonymous',
        stateInfo: state => (slug) => {
            const obj = state.state_options.filter((s) => s.value === slug)[0];
            if (obj) {
                return {
                    color: ticketStateColorLookup(obj.value),
                    icon: ticketStateIconLookup(obj.value),
                    slug: obj.value,
                    text: obj.text,
                }
            } else {
                return {
                    color: 'danger',
                    icon: 'exclamation',
                    slug: slug,
                    text: 'Unknown'
                }
            }
        },
        availableShippingVoucherTypes: state => {
            return Object.keys(state.shippingVoucherTypes).map(key => {
                var count = state.shippingVouchers.filter(voucher => voucher.type === key && voucher.issue_thread === null).length;
                return {id: key, count: count, name: state.shippingVoucherTypes[key]};
            });
        },
        layout: (state, getters) => {
            if (router.currentRoute.value.query.layout)
                return router.currentRoute.value.query.layout;
            if (getters.getActiveView === 'items')
                return 'cards';
            if (getters.getActiveView === 'tickets')
                return 'tasks';
        },
        isLoggedIn(state) {
            return state.user && state.user.username !== null && state.user.token !== null;
        },
        getThumbnail: (state) => (url) => {
            if (!url) return null;
            if (!(url in state.thumbnailCache))
                return null;
            return state.thumbnailCache[url];
        },
    },
    mutations: {
        updateLastUsed(state, diff) {
            state.lastUsed = {...state.lastUsed, ...diff};
        },
        updateLastEvent(state, slug) {
            state.lastEvent = slug;
        },
        replaceEvents(state, events) {
            state.events = events;
            state.fetchedData = {...state.fetchedData, events: Date.now()};
        },
        replaceTicketStates(state, states) {
            state.state_options = states;
            state.fetchedData = {...state.fetchedData, states: Date.now()};
        },
        changeView(state, {view, slug}) {
            router.push({path: `/${slug}/${view}`});
        },
        replaceBoxes(state, loadedBoxes) {
            state.loadedBoxes = loadedBoxes;
            state.fetchedData = {...state.fetchedData, boxes: Date.now()};
        },
        setItems(state, {slug, items}) {
            state.loadedItems[slug] = items;
            state.loadedItems = {...state.loadedItems};
            console.log(state.loadedItems)
        },
        replaceItems(state, items) {
            const groups = Object.groupBy(items, i => i.event ? i.event : 'none')
            for (const [key, value] of Object.entries(groups)) state.loadedItems[key] = value;
            state.loadedItems = {...state.loadedItems};
            console.log(state.loadedItems)
        },
        updateItem(state, updatedItem) {
            const item = state.loadedItems[updatedItem.event?updatedItem.event:'none'].filter(
                ({uid}) => uid === updatedItem.uid)[0];
            Object.assign(item, updatedItem);
        },
        removeItem(state, item) {
            state.loadedItems[item.event?item.event:'none'] = state.loadedItems[item.event].filter(it => it !== item);
        },
        appendItem(state, item) {
            state.loadedItems[item.event?item.event:'none'].push(item);
        },
        setTickets(state, {slug, tickets}) {
            state.loadedTickets[slug] = tickets;
            state.loadedTickets = {...state.loadedTickets};
            console.log(state.loadedTickets)
        },
        replaceTickets(state, tickets) {
            const groups = Object.groupBy(tickets, t => t.event ? t.event : 'none')
            for (const [key, value] of Object.entries(groups)) state.loadedTickets[key] = value;
            state.loadedTickets = {...state.loadedTickets};
            console.log(state.loadedTickets)
        },
        updateTicket(state, updatedTicket) {
            const ticket = state.loadedTickets[updatedTicket.event?updatedTicket.event:'none'].filter(
                ({id}) => id === updatedTicket.id)[0];
            Object.assign(ticket, updatedTicket);
            state.loadedTickets = {...state.loadedTickets};
        },
        replaceUsers(state, users) {
            state.users = users;
            state.fetchedData = {...state.fetchedData, users: Date.now()};
        },
        replaceGroups(state, groups) {
            state.groups = groups;
            state.fetchedData = {...state.fetchedData, groups: Date.now()};
        },
        openAddBoxModal(state) {
            state.showAddBoxModal = true;
        },
        closeAddBoxModal(state) {
            state.showAddBoxModal = false;
        },
        openAddEventModal(state) {
            state.showAddEventModal = true;
        },
        closeAddEventModal(state) {
            state.showAddEventModal = false;
        },
        createToast(state, {title, message, color}) {
            var toast = {title, message, color, key: state.keyIncrement}
            state.toasts.push(toast);
            state.keyIncrement += 1;
            return toast;
        },
        removeToast(state, key) {
            state.toasts = state.toasts.filter(toast => toast.key !== key);
        },
        setRemember(state, remember) {
            state.remember = remember;
        },
        setUser(state, user) {
            state.user.username = user;
        },
        setPassword(state, password) {
            state.user.password = password;
        },
        setPermissions(state, permissions) {
            state.user.permissions = permissions;
        },
        setToken(state, {token, expiry}) {
            const user = {...state.user};
            user.token = token;
            user.expiry = expiry;
            state.user = user;
        },
        setUserInfo(state, user) {
            state.user = user;
        },
        logout(state) {
            const user = {...state.user};
            user.user = null;
            user.password = null;
            user.token = null;
            user.expiry = null;
            user.permissions = null;
            state.user = user;
        },
        setThumbnail(state, {url, data}) {
            state.thumbnailCache[url] = data;
        },
        setShippingVouchers(state, codes) {
            state.shippingVouchers = codes;
            state.fetchedData = {...state.fetchedData, shippingVouchers: Date.now()};
        },
    },
    actions: {
        async login({commit}, {username, password, remember}) {
            commit('setRemember', remember);
            try {
                const data = await fetch('/api/2/login/', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({username: username, password: password}),
                    credentials: 'omit'
                }).then(r => r.json())
                if (data && data.token) {
                    const {data: {permissions}} = await http.get('/2/self/', data.token);
                    commit('setUserInfo', {...data, permissions, username, password});
                    return true;
                } else {
                    return false;
                }
            } catch (e) {
                console.error(e);
                return false;
            }
        },
        async reloadToken({commit, state}) {
            try {
                if (state.user.username && state.user.password) {
                    const data = await fetch('/api/2/login/', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({username: state.user.username, password: state.user.password}),
                        credentials: 'omit'
                    }).then(r => r.json()).catch(e => console.error(e))
                    if (data && data.token) {
                        commit('setToken', data);
                        return true;
                    }
                }
            } catch (e) {
                console.error(e);
            }
            //credentials failed, logout
            store.commit('logout');
        },
        //async verifyToken({commit, state}) {
        async afterLogin({dispatch, state}) {
            let promises = [];
            promises.push(dispatch('loadBoxes'));
            promises.push(dispatch('fetchTicketStates'));
            promises.push(dispatch('loadEventItems'));
            promises.push(dispatch('loadTickets'));
            if (!state.user.permissions) {
                promises.push(dispatch('loadUserInfo'));
            }
            await Promise.all(promises);
        },
        async afterSharedInit({dispatch, state}) {
            const handlers = state.afterInitHandlers;
            state.afterInitHandlers = [];
            await Promise.all(handlers.map(h => h()).flat());
        },
        scheduleAfterInit({dispatch, state}, handler) {
            if (state.shared_loaded) {
                Promise.all(handler()).then(() => {
                });
            } else {
                state.afterInitHandlers.push(handler);
            }
        },
        async fetchImage({state}, url) {
            return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
        },
        async loadUserInfo({commit, getters}) {
            const {data, success} = await getters.session.get('/2/self/');
            commit('setPermissions', data.permissions);
        },
        async loadEvents({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.events > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/events/');
            if (data && success) commit('replaceEvents', data);
        },
        async createEvent({commit, dispatch, state, getters}, event) {
            const {data, success} = await getters.session.post('/2/events/', event);
            if (data && success) commit('replaceEvents', [...state.events, data]);
        },
        async deleteEvent({commit, dispatch, state, getters}, event_id) {
            const {data, success} = await getters.session.delete(`/2/events/${event_id}/`);
            if (success) {
                await dispatch('loadEvents')
                commit('replaceEvents', [...state.events.filter(e => e.eid !== event_id)])
            }
        },
        async fetchTicketStates({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.states > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/tickets/states/');
            if (data && success) commit('replaceTicketStates', data);
        },
        async changeEvent({dispatch, getters, commit}, eventName) {
            await router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
            //dispatch('loadEventItems');
        },
        async changeView({getters}, link) {
            await router.push({path: `/${getters.getEventSlug}/${link.path}/`});
        },
        async showBoxContent({getters}, box) {
            await router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
        },
        async loadEventItems({commit, getters, state}) {
            if (!state.user.token) return;
            if (state.fetchedData.items > Date.now() - 1000 * 60 * 60 * 24) return;
            try {
                const slug = getters.getEventSlug;
                const {data, success} = await getters.session.get(`/2/${slug}/items/`);
                if (data && success) {
                    commit('setItems', {slug, items: data});
                }
            } catch (e) {
                console.error("Error loading items");
            }
        },
        async searchEventItems({commit, getters, state}, query) {
            const encoded_query = base64.encode(utf8.encode(query));
            const slug = getters.getEventSlug;
            const {
                data, success
            } = await getters.session.get(`/2/${slug}/items/${encoded_query}/`);
            if (data && success) {
                commit('setItems', {slug, items: data});
            }
        },
        async loadBoxes({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.boxes > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/boxes/');
            if (data && success) commit('replaceBoxes', data);
        },
        async createBox({commit, dispatch, state, getters}, box) {
            const {data, success} = await getters.session.post('/2/boxes/', box);
            commit('replaceBoxes', data);
            dispatch('loadBoxes').then(() => {
                commit('closeAddBoxModal');
            });
        },
        async deleteBox({commit, dispatch, state, getters}, box_id) {
            await getters.session.delete(`/2/boxes/${box_id}/`);
            dispatch('loadBoxes');
        },
        async updateItem({commit, getters, state}, item) {
            const {
                data, success
            } = await getters.session.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
            commit('updateItem', data);
        },
        async markItemReturned({commit, getters, state}, item) {
            await getters.session.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true},
                state.user.token);
            commit('removeItem', item);
        },
        async deleteItem({commit, getters, state}, item) {
            await getters.session.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
            commit('removeItem', item);
        },
        async postItem({commit, getters, state}, item) {
            commit('updateLastUsed', {box: item.box, cid: item.cid});
            const {data, success} = await getters.session.post(`/2/${getters.getEventSlug}/item/`, item);
            commit('appendItem', data);
        },
        async loadTickets({commit, state, getters}) {
            if (!state.user.token) return;
            //if (state.fetchedData.tickets > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/tickets/');
            if (data && success) commit('replaceTickets', data);
        },
        async searchEventTickets({commit, getters, state}, query) {
            const encoded_query = base64.encode(utf8.encode(query));

            const {
                data, success
            } = await getters.session.get(`/2/${getters.getEventSlug}/tickets/${encoded_query}/`);
            if (data && success) commit('replaceTickets', data);
        },
        async sendMail({commit, dispatch, state, getters}, {id, message}) {
            const {data, success} = await getters.session.post(`/2/tickets/${id}/reply/`, {message},
                state.user.token);
            if (data && success) {
                state.fetchedData.tickets = 0;
                await dispatch('loadTickets');
            }
        },
        async postManualTicket({commit, dispatch, state, getters}, {sender, message, title,}) {
            const {data, success} = await getters.session.post(`/2/tickets/manual/`, {
                name: title, sender, body: message, recipient: 'mail@c3lf.de'
            });
            await dispatch('loadTickets');
        },
        async postComment({commit, dispatch, state, getters}, {id, message}) {
            const {data, success} = await getters.session.post(`/2/tickets/${id}/comment/`, {comment: message});
            if (data && success) {
                state.fetchedData.tickets = 0;
                await dispatch('loadTickets');
            }
        },
        async loadUsers({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.users > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/users/');
            if (data && success) commit('replaceUsers', data);
        },
        async loadGroups({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.groups > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/groups/');
            if (data && success) commit('replaceGroups', data);
        },
        async updateTicket({commit, state, getters}, ticket) {
            const {data, success} = await getters.session.put(`/2/tickets/${ticket.id}/`, ticket);
            commit('updateTicket', data);
        },
        async updateTicketPartial({commit, state, getters}, {id, ...ticket}) {
            const {data, success} = await getters.session.patch(`/2/tickets/${id}/`, ticket);
            commit('updateTicket', data);
        },
        async fetchShippingVouchers({commit, state, getters}) {
            if (!state.user.token) return;
            if (state.fetchedData.shippingVouchers > Date.now() - 1000 * 60 * 60 * 24) return;
            const {data, success} = await getters.session.get('/2/shipping_vouchers/');
            if (data && success) {
                commit('setShippingVouchers', data);
            }
        },
        async createShippingVoucher({dispatch, state, getters}, code) {
            const {data, success} = await getters.session.post('/2/shipping_vouchers/', code);
            if (data && success) {
                state.fetchedData.shippingVouchers = 0;
                dispatch('fetchShippingVouchers');
            }
        },
        async claimShippingVoucher({dispatch, state, getters}, {ticket, shipping_voucher_type}) {
            const id = state.shippingVouchers.filter(voucher => voucher.type === shipping_voucher_type && voucher.issue_thread === null)[0].id;
            const {data, success} = await getters.session.patch(`/2/shipping_vouchers/${id}/`, {issue_thread: ticket});
            if (data && success) {
                state.fetchedData.shippingVouchers = 0;
                state.fetchedData.tickets = 0;
                await Promise.all([dispatch('loadTickets'), dispatch('fetchShippingVouchers')]);
            }
        }
    },
    plugins: [persistentStatePlugin({ // TODO change remember to some kind of enable field
        prefix: "lf_",
        debug: false,
        isLoadedKey: "persistent_loaded",
        state: ["remember", "user", "events", "lastUsed",]
    }), sharedStatePlugin({
        debug: true,
        isLoadedKey: "shared_loaded",
        clearingMutation: "logout",
        afterInit: "afterSharedInit",
        state: ["test", "state_options", "fetchedData", "loadedItems", "users", "groups", "loadedBoxes", "loadedTickets", "shippingVouchers",],
        watch: ["test", "state_options", "fetchedData", "loadedItems", "users", "groups", "loadedBoxes", "loadedTickets", "shippingVouchers",],
        mutations: [//"replaceTickets",
        ],
    }),],
});

store.watch((state) => state.user, (user) => {
    if (store.getters.isLoggedIn) {
        if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect) {
            router.push(router.currentRoute.value.query.redirect);
        } else if (router.currentRoute.value.name === 'login') {
            router.push('/');
        }
    } else {
        if (router.currentRoute.value.name !== 'login') {
            router.push({
                name: 'login',
                query: {redirect: router.currentRoute.value.fullPath},
            });
        }
    }
});

export default store;