// createstore

function createStore(reducer) {
    let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
    let cbs = []                     //массив подписчиков

    const getState = () => state            //функция, возвращающая переменную из замыкания
    const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
        () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка

    const dispatch = action => {
        if (typeof action === 'function') { //если action - не объект, а функция
            return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
        }
        const newState = reducer(state, action) //пробуем запустить редьюсер
        if (newState !== state) { //проверяем, смог ли редьюсер обработать action
            state = newState //если смог, то обновляем state 
            for (let cb of cbs) cb() //и запускаем подписчиков
        }
    }

    return {
        getState,
        dispatch,
        subscribe
    }
}

// ----------------------------------------------------------

// decode token

function jwtDecode(token) {
    try {
        return JSON.parse(atob(token.split('.')[1]))
    }
    catch (e) {
    }
}

// ----------------------------------------------------------

// reducers

function authReducer(state = {}, { type, token }) { //authorise reducer
    if (state === undefined) {
        if (localStorage.authToken) {
            type = "AUTH_LOGIN";
            token = localStorage.authToken;
        }
    }
    if (type === 'AUTH_LOGIN') { //то мы логинимся
        const payload = jwtDecode(token)
        if (payload) {
            return {
                token,
                payload
            }
        }
    }
    if (type === 'AUTH_LOGOUT') { //мы разлогиниваемся
        return {}
    }
    return state
}


function promiseReducer(state = {}, { type, name, status, payload, error }) { //promise reducer
    if (type === 'PROMISE') {
        return {
            ...state,
            [name]: { status, payload, error }
        }
    }
    return state
}

function cartReducer(state = {}, { type, good, count = 1 }) {
    if (type === 'CART_ADD') {
        return {
            ...state,
            [good._id]: { count: (state[good._id]?.count || 0) + count, good: good }
        }
    }
    if (type === 'CART_CHANGE') {
        return {
            ...state,
            [good._id]: { count, good }
        }
    }
    if (type === 'CART_DELETE') {
        delete state[good._id]
        return {
            ...state,
        }
    }
    if (type === 'CART_CLEAR') {
        return {}
    }
    return state
}

const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', good, count })
const actionCartChange = (good, count = 1) => ({ type: 'CART_CHANGE', good, count })
const actionCartDelete = (good) => ({ type: 'CART_DELETE', good })
const actionCartClear = () => ({ type: 'CART_CLEAR' })

function combineReducers(reducers) {
    function combinedReducer(combinedState = {}, action) {
        const newCombinedState = {}
        for (const [reducerName, reducer] of Object.entries(reducers)) {
            const newSubState = reducer(combinedState[reducerName], action)
            if (newSubState !== combinedState[reducerName]) {
                newCombinedState[reducerName] = newSubState
            }
        }
        if (Object.keys(newCombinedState).length === 0) {
            return combinedState
        }
        return { ...combinedState, ...newCombinedState }
    }

    return combinedReducer
}

const store = createStore(combineReducers({ promise: promiseReducer, auth: authReducer, cart: cartReducer }))
// store.subscribe(() => console.log(store.getState()))

// ----------------------------------------------------------

// GQL

const getGQL = url =>
    (query, variables) => fetch(url, {
        method: 'POST',
        headers: {
            "Content-Type": "application/json",
            ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
        },
        body: JSON.stringify({ query, variables })
    }).then(res => res.json())
        .then(data => {
            if (data.data) {
                return Object.values(data.data)[0]
            }
            else throw new Error(JSON.stringify(data.errors))
        })

const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'

const gql = getGQL(backendURL + 'graphql')

// ----------------------------------------------------------

// promises

const actionPromise = (name, promise) =>
    async dispatch => {
        dispatch(actionPending(name))
        try {
            let payload = await promise
            dispatch(actionFulfilled(name, payload))
            return payload
        }
        catch (e) {
            dispatch(actionRejected(name, e))
        }
    }

const actionPending = (name) => ({ type: 'PROMISE', status: 'PENDING', name })
const actionFulfilled = (name, payload) => ({ type: 'PROMISE', status: 'FULFILLED', name, payload })
const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })

// ----------------------------------------------------------

// actions

const actionFullRegister = (login, password) =>
    actionPromise('fullRegister', gql(`mutation UserUpsert($login: String, $password: String){UserUpsert(user: {login:$login,password:$password}){_id}}`, { login: login, password: password }))


const actionAuthLogin = (token) =>
    (dispatch, getState) => {
        const oldState = getState()
        dispatch({ type: 'AUTH_LOGIN', token })
        const newState = getState()
        if (oldState !== newState)
            localStorage.authToken = token
    }

const actionAuthLogout = () =>
    dispatch => {
        dispatch({ type: 'AUTH_LOGOUT' })
        localStorage.removeItem('authToken')
    }

const actionFullLogin = (login, password) =>
    actionPromise('fullLogin', gql(`query login($login:String,$password:String){login(login:$login,password:$password)}`, { login: login, password: password }))


const orderFind = () =>
    actionPromise('orderFind', gql(`query orderFind{
    OrderFind(query: "[{}]"){
        _id createdAt total orderGoods {_id price count good{name price images{url}}}
    }
}`, { q: JSON.stringify([{}]) }))

const actionAddOrder = (cart) =>
    actionPromise('actionAddOrder', gql(`mutation newOrder($cart: [OrderGoodInput])
{OrderUpsert(order: {orderGoods: $cart})
{_id total}}`, { cart: cart }))

const actionRootCats = () =>
    actionPromise('rootCats', gql(`query {
        CategoryFind(query: "[{\\"parent\\":null}]"){
            _id name
            
        }
    }`))

const actionCatById = (_id) => // √
    actionPromise('catById', gql(`query catById($q: String){
        CategoryFindOne(query: $q){
            _id name goods {
                _id name price images {
                    url
                }
                
            }
            subCategories{_id name}
            
        }
    }`, { q: JSON.stringify([{ _id }]) }))

const actionGoodById = (_id) =>
    actionPromise('goodById', gql(`query goodById($q: String){
        GoodFindOne(query: $q){
            _id name price description images {
                url
            }
        }
    }`, { q: JSON.stringify([{ _id }]) }))

store.dispatch(actionRootCats())

// ----------------------------------------------------------

// subscribe

store.subscribe(() => {

    const { rootCats } = (store.getState()).promise
    if (rootCats?.payload) {
        cat.innerHTML = `<li class="list-group-item"><b>Categories</b></li>`

        for (const { _id, name } of rootCats?.payload) {
            const categories = document.createElement('li')
            categories.innerHTML = `<a href='#/category/${_id}'>${name}</a>`
            categories.style = ' padding-left: 30px ; '

            cat.append(categories)
        }
    }
    else if (!rootCats) {
        cat.innerHTML = '<img src = img/preloader.gif>'
    }
})

store.subscribe(() => {
    const { catById } = (store.getState()).promise
    const [, route, _id] = location.hash.split('/')
    if (catById?.payload && route === 'category') {

        main.innerHTML = ``
        const { name } = catById.payload

        const card = document.createElement('div')
        card.id = 'sub-card'
        card.innerHTML = `<h2 id="cat-name"><b>${name}</b></h2><br>`

        const backPart = document.createElement('div')
        backPart.id = 'back'
        const backBtn = document.createElement('button')
        backBtn.setAttribute('type', 'button');
        backBtn.addEventListener('click', () => {
            history.back();
        });
        backBtn.innerText = '⬅'
        backPart.appendChild(backBtn)


        if (catById.payload.subCategories) {
            for (const { _id, name } of catById.payload?.subCategories) {
                card.innerHTML += `<a href='#/category/${_id}' class='subcategories'>${name}</a>`
            }
        }

        // card.append(backPart)
        main.append(card, backPart)
        for (const { _id, name, price, images } of catById.payload?.goods) {
            const card = document.createElement('div')
            card.id = 'card'
            card.innerHTML = `<h5><b>${name}</b></h5>
                              <div class='card-img'><img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}"/></div><br>
                              <h2 style='color: green; text-align:center'>Price: $${price}</h2><br><br>
                              <a class="" style="width: 100%;" href='#/good/${_id}'>More details -> </a><br><br>`
            let button = document.createElement('button')
            button.innerText = 'BUY'
            button.className = 'buy-btn'
            button.setAttribute('type', 'button');
            button.onclick = async () => {
                await store.dispatch(actionCartAdd({ _id: _id, name: name, price: price, images: images }))
                console.log('hi')
            }
            card.append(button)
            main.append(card)
        }
    }
})

store.subscribe(() => {
    const { goodById } = (store.getState()).promise
    const [, route, _id] = location.hash.split('/')
    if (goodById?.payload && route === 'good') {
        const { _id, name, description, images, price } = goodById.payload
        main.innerHTML = `<h1>${name}</h1>`

        const card = document.createElement('div')
        card.id = 'desc-card'

        const backPart = document.createElement('div')
        backPart.id = 'back'

        const backBtn = document.createElement('button')
        backBtn.setAttribute('type', 'button');
        backBtn.addEventListener('click', () => {
            history.back();
        });
        backBtn.innerText = '⬅'
        backPart.appendChild(backBtn)

        let block = document.createElement('div')
        block.id = 'price'
        block.innerHTML = `<h2>Price: <b class='price'>$${price}</b></h2>`
        let button = document.createElement('button')
        button.innerText = 'BUY'
        button.className = 'buy-btn'
        button.setAttribute('type', 'button');
        button.style = 'height:80px'
        button.onclick = async () => {
            await store.dispatch(actionCartAdd({ _id: _id, name: name, price: price, images: images }))
            console.log('hi')
        }

        card.innerHTML = `<img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}" /><br><br>`
        card.append(block)
        card.innerHTML += `<p><b>Description:</b> ${description}</p>`
        main.append(backPart, card, button)
    }
})

store.subscribe(() => {
    const { orderFind } = (store.getState()).promise
    const [, route, _id] = location.hash.split('/')

    if (orderFind?.payload && route === 'orderFind') {
        main.innerHTML = '<h1>ORDER HISTORY</h1>'

        for (const { _id, createdAt, total, orderGoods } of orderFind.payload.reverse()) {
            const card = document.createElement('div')
            card.className = 'order-card'
            card.innerHTML = `<h3>Order: ${createdAt}</h3>`
            for (const { count, good } of orderGoods) {

                const divGood = document.createElement('div')
                divGood.style = "display:flex;margin-bottom: 20px;"

                divGood.innerHTML += `<div><b>${good.name}</b><br> Price: <b>$${good.price}</b><br> Amount: <b>${count} pt</b></b></div><img style="max-width: 80px;margin-right: 20px;display: block;margin-left: auto;" src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}"/><br><br>`
                card.append(divGood)
            }
            card.innerHTML += 'Date: <b>' + new Date(+createdAt).toLocaleString().replace(/\//g, '.') + '</b>'
            card.innerHTML += `<br><h2>Total: <b style="color:green;">$${total}</b></h2>`
            main.append(card)
        }
    }
})

// ----------------------------------------------------------

// window

function display() {
    let token = localStorage.authToken
    if (token) {
        form_yes.style.display = 'flex'
        form_no.style.display = 'none'
        UserNick.innerText = JSON.parse(window.atob(localStorage.authToken.split('.')[1])).sub.login
    } else {
        form_yes.style.display = 'none'
        form_no.style.display = 'flex'
    }
}
display()

window.onhashchange = () => {
    const [, route, _id] = location.hash.split('/')

    const routes = {
        category() {
            store.dispatch(actionCatById(_id))
        },

        good() {
            store.dispatch(actionGoodById(_id))
        },

        login() {
            main.innerHTML = ''
            let form = document.createElement('div')
            let div1 = document.createElement('div')
            let div2 = document.createElement('div')

            div1.innerHTML = `<h1>LOGIN</h1>`
            let loginInput = document.createElement('input')
            loginInput.placeholder = 'Type your username'
            loginInput.name = 'login'
            div1.style.display = 'flex'
            div1.style.flexDirection = 'column'
            let passwordInput = document.createElement('input')
            passwordInput.placeholder = 'Type your password'
            passwordInput.name = 'password'
            passwordInput.type = 'password';
            div1.append(loginInput)
            div2.append(passwordInput)

            let button = document.createElement('button')
            button.innerText = "LOGIN"
            button.id = 'login-btn'
            button.setAttribute('type', 'button');

            button.onclick = async () => {

                let tokenPromise = async () => await store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
                let token = await tokenPromise()

                if (token !== null) {
                    store.dispatch(actionAuthLogin(token))
                    display()
                    document.location.href = "#/orderFind";
                }
                else {
                    loginInput.value = ''
                    passwordInput.value = ''
                    alert("Incorrect username or password.")
                    store.dispatch(actionAuthLogout())
                }
            }

            form.append(div1, div2, button)
            main.append(form)
        },

        register() {
            main.innerHTML = ''
            let form = document.createElement('div')
            let div1 = document.createElement('div')
            let div2 = document.createElement('div')

            div1.innerHTML += `<h1>REGISTER</h1>`
            let loginInput = document.createElement('input')
            loginInput.placeholder = "Type your username"
            div1.append(loginInput)

            let passwordInput = document.createElement('input')
            passwordInput.placeholder = "Type your password"
            passwordInput.type = 'password'

            div2.append(passwordInput)

            let button = document.createElement('button')
            button.innerText = "CREATE ACCOUNT"
            button.id = 'reg-btn'
            button.setAttribute('type', 'button');

            let textAlert = document.createElement('div')
            let textAlert2 = document.createElement('div')

            let putInText = "Username and password required!"
            let userAlready = "An account with this username already exist!"
            textAlert.append(userAlready)
            textAlert2.append(putInText)

            textAlert2.style = 'display : none; color : red'
            textAlert.style = 'display : none; color : red'


            button.onclick = async () => {
                let register = await store.dispatch(actionFullRegister(loginInput.value, passwordInput.value))
                let tokenPromise = async () => await store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))

                if (loginInput.value == '' || passwordInput.value == '') {
                    textAlert2.style.display = 'block'

                } else {
                    if (register !== null) {
                        let token = await tokenPromise()
                        store.dispatch(actionAuthLogin(token))
                        // console.log(token)
                        display()
                        document.location.href = "#/orderFind";

                    } else {
                        textAlert.style.display = 'block'
                        textAlert2.style.display = 'none'
                    }
                }

            }

            form.append(div1, div2, button)
            form.append(textAlert, textAlert2)
            main.append(form)
        },

        orderFind() {
            store.dispatch(orderFind())
        },

        cart() {
            main.innerHTML = '<h1>CART</h1>'

            for (const [_id, obj] of Object.entries(store.getState().cart)) {
                const card = document.createElement('div')
                card.style = 'width: 33.33%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px;display: flex; flex-direction: column ; align-items: center ; justify-content: space-between'

                const { count, good } = obj

                card.innerHTML += `Products: <b>${good.name}</b> <br><img src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}" style="width: 100px"/> <br> Цена: <b>$${good.price}</b><br><br>`

                const calculation = document.createElement('div')

                const buttonAdd = document.createElement('button')
                buttonAdd.innerHTML = '+'
                buttonAdd.setAttribute('type', 'button');
                buttonAdd.onclick = async () => {
                    inputCount.value = +inputCount.value + 1
                    await store.dispatch(actionCartChange({ _id: _id, name: good.name, price: good.price, images: good.images }, +inputCount.value))

                    cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
                }
                calculation.append(buttonAdd)

                const inputCount = document.createElement('input')
                inputCount.value = +count
                inputCount.disabled = 'disabled'
                inputCount.className = 'inputCount'
                calculation.append(inputCount)

                const buttonLess = document.createElement('button')
                buttonLess.innerHTML = '-'
                buttonLess.setAttribute('type', 'button');
                buttonLess.onclick = async () => {
                    if ((+inputCount.value) > 1) {
                        inputCount.value = +inputCount.value - 1
                        await store.dispatch(actionCartChange({ _id: _id, name: good.name, price: good.price, images: good.images }, +inputCount.value))

                        cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
                    }

                }

                calculation.append(buttonLess)

                const buttonDelete = document.createElement('button')
                buttonDelete.innerText = 'Delete'
                buttonDelete.className = 'buttonDelete'
                buttonDelete.setAttribute('type', 'button');
                buttonDelete.onclick = async () => {
                    await store.dispatch(actionCartDelete({ _id: _id, name: good.name, price: good.price, images: good.images }))

                    card.style.display = 'none'
                    cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
                }
                card.append(calculation)
                card.append(buttonDelete)
                main.append(card)
            }
            const cardTotalDiv = document.createElement('div')
            cardTotalDiv.id = 'total'
            const cardTotal = document.createElement('div')

            cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2>`

            cardTotalDiv.append(cardTotal)

            let cartAlert = document.createElement('div')
            cartAlert.innerHTML = `<h2 style='color:orange;'>Your cart seems empty 😟</h2>`
            cartAlert.style.display = 'none'
            cartAlert.id = 'cart-alert'

            if (localStorage.authToken != '') {
                const button = document.createElement('button')
                button.innerHTML += '<strong>ORDER</strong>'
                button.setAttribute('type', 'button');

                if(goodPrice() != 0){
                    button.onclick = async () => {
                        await store.dispatch(actionAddOrder(Object.entries(store.getState().cart).map(([_id, count]) => ({ count: count.count, good: { _id } }))));
                        await store.dispatch(actionCartClear());
                        document.location.href = "#/orderFind";
                    }    
                }
                else{
                    cartAlert.style.display = 'flex'
                }

                // button.className = 'btn btn-primary'
                cardTotalDiv.append(button)

            }

            main.append(cardTotalDiv, cartAlert)


        }

    }

    if (route in routes) {
        routes[route]()
    }
}
window.onhashchange()

function localStoredReducer(reducer, localStorageKey) {
    function wrapperReducer(state, action) {
        if (state === undefined) { //если загрузка сайта
            try {
                return JSON.parse(localStorage[localStorageKey]) //пытаемся распарсить сохраненный 
                //в localStorage state и подсунуть его вместо результата редьюсера
            }
            catch (e) { } //если распарсить не выйдет, то код пойдет как обычно:
        }
        const newState = reducer(state, action)
        localStorage.setItem(localStorageKey, JSON.stringify(newState)) //сохраняем состояние в localStorage
        return newState
    }
    return wrapperReducer
}

window.onhashchange()

function goodPrice() {
    return Object.entries(store.getState().cart).map(i => x += i[1].count * i[1].good.price, x = 0).reverse()[0] || 0
}