import {createStore} from 'vuex';
import router from '@/router';
import FallBackResolver from "@/dns";
import NeighborsCache from "@/neigbors";
import {createNullAuth, createSignAuth, createTokenAuth, ServerSet, ServerSetUnion} from "@/federation";


export default createStore({
    state: {
        local_loaded: false,
        last_load: {},
        user: null,
        token: null,
        keypair: null,
        remember: false,
        friends: [],
        item_map: {},
        home_servers: null,
        all_friends_servers: null,
        resolver: new FallBackResolver(),
        unreachable_neighbors: new NeighborsCache(),
        availability_policies: [],
    },
    mutations: {
        setUser(state, user) {
            state.user = user;
            if (state.remember)
                localStorage.setItem('user', user);
        },
        setToken(state, token) {
            state.token = token;
            if (state.remember)
                localStorage.setItem('token', token);
        },
        setKey(state, keypair) {
            state.keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(keypair))
            if (state.remember)
                localStorage.setItem('keypair', nacl.to_hex(state.keypair.signSk).slice(0, 64))
        },
        setRemember(state, remember) {
            state.remember = remember;
            if (!remember) {
                localStorage.removeItem('user');
                localStorage.removeItem('token');
                localStorage.removeItem('keypair');
            }
            localStorage.setItem('remember', remember);
        },
        setInventoryItems(state, {url, items}) {
            state.item_map[url] = items;
        },
        setFriends(state, friends) {
            state.friends = friends;
        },
        setHomeServers(state, home_servers) {
            state.home_servers = home_servers;
        },
        setAllFriendsServers(state, servers) {
            state.all_friends_servers = servers;
        },
        setAvailabilityPolicies(state, availability_policies) {
            state.availability_policies = availability_policies;
        },
        logout(state) {
            state.user = null;
            state.token = null;
            state.keypair = null;
            localStorage.removeItem('user');
            localStorage.removeItem('token');
            localStorage.removeItem('keypair');
            router.push('/login');
        },
        load_local(state) {
            if (state.local_loaded)
                return;
            const remember = localStorage.getItem('remember');
            const user = localStorage.getItem('user');
            const token = localStorage.getItem('token');
            const keypair = localStorage.getItem('keypair');
            if (user && token) {
                this.commit('setUser', user);
                this.commit('setToken', token);
                if (keypair) {
                    this.commit('setKey', keypair)
                }
            }
            state.cache_loaded = true;
        }
    },
    actions: {
        async login({commit, dispatch, state, getters}, {username, password, remember}) {
            commit('setRemember', remember);
            const data = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
                .then(set => set.post(getters.nullAuth, '/auth/token/', {username, password}))
            if (data.token && data.key) {
                commit('setToken', data.token);
                commit('setUser', username);
                commit('setKey', data.key);
                const s = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
                commit('setHomeServers', s)
                return true;
            } else {
                return false;
            }
        },
        async lookupServer({state}, {username}) {
            const domain = username.split('@')[1]
            const request = '_toolshed-server._tcp.' + domain + '.'
            return await state.resolver.query(request, 'SRV').then(
                (result) => result.map(
                    (answer) => answer.target + ':' + answer.port))
        },
        async getHomeServers({state, dispatch, commit}) {
            if (state.home_servers)
                return state.home_servers
            const promise = dispatch('lookupServer', {username: state.user}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
            commit('setHomeServers', promise)
            return promise
        },
        async getAllKnownServers({state, dispatch, commit}) {
            const friends = await dispatch('fetchFriends')
            if (state.all_friends_servers)
                return state.all_friends_servers
            const promise = (async () => {
                const servers = new ServerSetUnion([])
                const home = await dispatch('getHomeServers')
                servers.add(home)
                for (const friend of friends) {
                    const s = await dispatch('lookupServer', {username: friend.username})
                    servers.add(new ServerSet(s, state.unreachable_neighbors))
                }
                return servers
            })()
            commit('setAllFriendsServers', promise)
            return promise
        },
        async getFriendServers({state, dispatch, commit}, {username}) {
            return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
        },
        async fetchInventoryItems({commit, dispatch, getters}) {
            const servers = await dispatch('getHomeServers')
            const items = await servers.get(getters.signAuth, '/api/inventory_items/')
            items.map(item => item.files.map(file => file.owner = item.owner))
            commit('setInventoryItems', {url: '/', items})
            return items
        },
        async fetchFriends({commit, dispatch, getters, state}) {
            const servers = await dispatch('getHomeServers')
            const data = await servers.get(getters.signAuth, '/api/friends/')
            commit('setFriends', data)
            return data
        },
        async fetchFriendRequests({state, dispatch, getters}) {
            const servers = await dispatch('getHomeServers')
            return await servers.get(getters.signAuth, '/api/friendrequests/')
        },
        async requestFriend({state, dispatch, getters}, {username}) {
            if (username in state.friends) {
                return true;
            }
            const home_servers = await dispatch('getHomeServers')
            const home_reply = await home_servers.post(getters.signAuth, '/api/friendrequests/', {
                befriender: state.user,
                befriendee: username
            })
            if (home_reply.status !== 'pending' || !home_reply.secret)
                return false;

            const befriendee_servers = await dispatch('getFriendServers', {username})
            const ext_reply = await befriendee_servers.post(getters.signAuth, '/api/friendrequests/', {
                befriender: state.user,
                befriendee: username,
                befriender_key: nacl.to_hex(state.keypair.signPk),
                secret: home_reply.secret
            })
            return true;
        },
        async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) {
            const home_servers = await dispatch('getHomeServers')
            const home_reply = await home_servers.post(getters.signAuth, '/api/friends/', {
                friend_request_id: id, secret: secret
            })
            const ext_servers = await dispatch('getFriendServers', {username: befriender})
            const ext_reply = await ext_servers.post(getters.signAuth, '/api/friendrequests/', {
                befriender: state.user,
                befriendee: befriender,
                befriender_key: nacl.to_hex(state.keypair.signPk),
                secret: secret
            })
            return true
        },
        async declineFriend({state, dispatch, getters}, {id}) {
            const servers = await dispatch('getHomeServers')
            return await servers.delete(getters.signAuth, '/api/friendrequests/' + id + '/')
        },
        async dropFriend({state, dispatch, getters}, {id}) {
            const servers = await dispatch('getHomeServers')
            return await servers.delete(getters.signAuth, '/api/friends/' + id + '/')
        },
    },
    getters: {
        isLoggedIn(state) {
            if (!state.local_loaded) {
                state.remember = localStorage.getItem('remember') === 'true'
                state.user = localStorage.getItem('user')
                state.token = localStorage.getItem('token')
                const keypair = localStorage.getItem('keypair')
                if (keypair)
                    state.keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(keypair))
                state.local_loaded = true
            }

            return state.user !== null && state.token !== null;
        },
        signAuth(state) {
            return createSignAuth(state.user, state.keypair.signSk)
        },
        tokenAuth(state) {
            return createTokenAuth(state.token)
        },
        nullAuth(state) {
            return createNullAuth({})
        },
        inventory_items(state) {
            return state.item_map['/'] || []
        },
        loaded_items(state) {
            return Object.entries(state.item_map).reduce((acc, [url, items]) => {
                return acc.concat(items)
            }, [])
        },
    }
})