let aside              = document.querySelector(".aside");
let content            = document.querySelector(".main__main-content");
let registration = document.querySelector(".header__registration");
let signIn       = document.querySelector(".header__sign-in");
let cart         = document.querySelector(".imaginary-shopping-cart--content");
let cartButton   = document.querySelector(".header__cart");
let dasboard     = document.querySelector(".header__dashboard");

registration.addEventListener("click", function() {
    location.href = "#/register";
});

signIn.addEventListener("click", function() {
    location.href = "#/login";
});

cartButton.addEventListener("click", function() {
    location.href = "#/cart";
});

dasboard.addEventListener("click", function() {
    location.href = "#/dasboard";
});

let createStore = function(reducer) {
    let state = reducer(undefined, {});
    let cbs = [];

    let getState = () => state;
    let subscribe = function(cb) {
        cbs.push(cb);
        return () => cbs = cbs.filter(c => c !== cb);
    };
    let dispatch = function(action) {
        if(typeof(action) == "function") {
            return action(dispatch, getState);
        };

        let newState = reducer(state, action);

        if (newState !== state){
            state = newState;

            for (let cb of cbs) {
                cb();
            };
        };
    };

    return {
        getState,
        subscribe,
        dispatch
    };
};

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

    return state;
};

const actionPending             = name => ({type: "PROMISE", name, status: 'PENDING'});
const actionFulfilled = (name,payload) => ({type: "PROMISE", name, status: 'FULFILLED', payload});
const actionRejected  = (name,error)   => ({type: "PROMISE", name, status: 'REJECTED', error});
const actionPromise = function(name, promise) {
    return async dispatch => {
        dispatch(actionPending(name));
        try {
            let payload = await promise
            dispatch(actionFulfilled(name, payload))
            return payload
        }
        catch(error){
            dispatch(actionRejected(name, error))
        };
    };
};

let jwtDecode = function(token) {
    let payloadInBase64;
    let payloadInJson;
    let payload;

    try {
        payloadInBase64 = token.split(".")[1];
        payloadInJson = atob(payloadInBase64);
        payload = JSON.parse(payloadInJson);

        return payload;
    }
    catch(err) {

    }
};

const authReducer = function(state = {}, {type, token}) {
    let payload;

    if (state == undefined) {
        if(localStorage.authToken) {
            type = "AUTH_LOGIN";
            token = localStorage.authToken;
        } else {
            type = "AUTH_LOGOUT";
        };
    };
    if (type == "AUTH_LOGIN") {
        payload = jwtDecode(token);

        if(payload) {
            localStorage.authToken = token;
        }

        return {
            token: token,
            payload: payload
        }
    };
    if (type == "AUTH_LOGOUT") {
        localStorage.removeItem("authToken");

        return {};
    };

    return state || {};
};

const actionAuthLogin  = token => ({type: "AUTH_LOGIN", token});
const actionAuthLogout = () => ({type: "AUTH_LOGOUT"});

let cartReducer = function(state={}, {type, good, count=1}) {
    if(type == "CART_ADD") {
        let newState = {...state};

        if(good["_id"] in state) {
            newState[good._id].count = newState[good._id].count + count;
        }
        else {
            newState = {
                ...state,
                [good._id]: {count, good} 
            };
        };

        return newState;
    };
    if(type == "CART_CHANGE") {
        let newState = {...state,
            [good._id]: {count, good}
        };

        return newState;
    };
    if(type == "CART_DELETE") {
        let newState = {...state};

        delete newState[good._id];

        return newState;
    };
    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'})

const combineReducers = function(reducers) {
    return function (state={}, action) {
        const newState = {};
        
        for(let [reducerName, reducer] of Object.entries(reducers)) {
            let checkState = reducer(state[reducerName], action);

            if(state[reducerName] != checkState) {
                newState[reducerName] = checkState;
            };
        };

        if(Object.keys(newState).length == 0) {
            return state;
        };

        return {...Object.assign(state, newState)};
    };
};

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

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

const backendURL = 'http://shop-roles.asmer.fs.a-level.com.ua';

const gql = getGQL(backendURL + '/graphql');

let actionRootCats = function() {
    return actionPromise("rootCats", gql(`query {
        CategoryFind(query: "[{\\"parent\\":null}]"){
            _id name
      }
    }`));
};

store.dispatch(actionRootCats());

let actionCatById = function(_id) {
    return actionPromise("catById", gql(`query catById($q: String){
        CategoryFindOne(query: $q){
            _id name goods {
                _id name price images {
                    url
                }
            }
        }
    }`,
    {q: JSON.stringify([{_id}])}
    ));
};

let actionGoodById = function(_id) {
    return actionPromise("goodById", gql(`query findGood($goodQuery: String) {
        GoodFindOne(query:$goodQuery) {
            _id name price images {
                url
            }
        }
    }`,
    {goodQuery: JSON.stringify([{"_id": _id}])}
    ));
};

let actionFullLogin = async function(login, password) {
    let token = await gql("query userLogin($login: String, $password: String) {login(login: $login, password: $password)}", {"login": login, "password": password});

    store.dispatch(actionAuthLogin(token));
};

let actionFullRegister = function(login, password, nick) {
    return actionPromise("userRegister", gql(`mutation userRegister($login:String, $password:String, $nick:String) {
        UserUpsert(user: {login:$login, password:$password, nick:$nick}) {
          _id login nick
        }
      }`,
      {
        "login": login,
        "password": password,
        "nick": nick
      }))
};

let actionOrders = async function() {
    let order = await gql(`mutation makeOrder($order:OrderInput){
        OrderUpsert(order: $order){
          _id
        }
      }`, {
          "order": {
              orderGoods: Object.entries(store.getState().cart).map(([_id, count]) =>({"count": count.count, "good": {_id}}))
            }
      });

    store.dispatch(actionCartClear());
}

let createCart = function() {
    const [,route, _id] = location.hash.split('/');

    if(route == "cart") {
        let str = "<h2>Корзина</h2><table id='table'>";

        for(let goodId of Object.entries(store.getState().cart)) {
            str += `<tr>
                        <td class="visually-hidden">${goodId[0]}</td>
                        <td>${goodId[1].good.name}</td>
                        <td><img src="${backendURL}/${goodId[1].good.images[0].url}"/></td>
                        <td>Цена: <span id="price">${goodId[1].good.price}</span></td>
                        <td>Кол-во: <input id="count" type="number" value="${goodId[1].count}"></td>
                        <td><button id="clear">Удалить товар</button></td>
                    </tr>`;
        };
        str += "</table><button id='order'>Сделать заказ</button>";

        content.innerHTML = str;

        table.addEventListener("click", function(evt) {
            if(evt.target.tagName == "BUTTON") {
                store.dispatch(actionCartDelete(store.getState().cart[evt.path[2].firstElementChild.textContent]?.good))
            };
        });

        order.addEventListener("click", function() {
            actionOrders();
        });
    }
};

let createDashbord = async function() {
    const [,route, _id] = location.hash.split('/');

    if(route == "dasboard") {
        let orders = await gql(`query ordersFind($query:String) {
            OrderFind(query: $query) {
              createdAt orderGoods {
                count good {
                  name price images {
                    url
                  }
                }
              }
            }
          }`,
            {
            "query": JSON.stringify([{}])
            });
        let str = "<h2>История заказов</h2><table>";
    
        for(let order of orders) {
            str += "<tr>";
            str += `<td>${(new Date(+order.createdAt)).toLocaleString()}</td>`;
            str += "<td>"
            for(let orderGood of order.orderGoods) {
                str += `<ul>
                            <li>${orderGood.good.name}</li>
                            <li><img src="${backendURL}/${orderGood.good.images[0].url}"/></li>
                            <li>Цена: ${orderGood.good.price}</li>
                        </ul>`;
            }
            str += "</td>"
            str += "</tr>";
        };

        str += "</table>";

        content.innerHTML = str;
    }
};

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

    const routes = {
        category(){
            store.dispatch(actionCatById(_id));
        },
        good(){
            store.dispatch(actionGoodById(_id));
        },
        login(){
            content.innerHTML = `<h2>Вход на сайт</h2>
                                 <input id="login" type="text" name="login" placeholder="Введите ваш логин">
                                 <input id="password" type="password" name="password" placeholder="Введите ваш пароль">
                                 <button id="sign_in">Войти</button>`;
            sign_in.addEventListener("click", function() {
                actionFullLogin(login.value, password.value);
                // store.dispatch(actionFullLogin(login.value, password.value));
            });
        },
        register(){
            content.innerHTML = `<h2>Регистрация</h2>
                                 <input id="login" type="text" name="reg-login" placeholder="Введите ваш будущий логин">
                                 <input id="password" type="text" name="reg-password" placeholder="Введите ваш будущий пароль">
                                 <input id="nick" type="text" name="reg-nick" placeholder="Введите ваш nick">
                                 <button id="registr">Зарегистрироваться</button>`;
            registr.addEventListener("click", function() {
                store.dispatch(actionFullRegister(login.value, password.value, nick.value));
            });
        },
        cart() {
            createCart();        
        },
        dasboard() {
            createDashbord();
        }
    };

    if (route in routes) {
        routes[route]();
    };
};

window.onhashchange();

store.subscribe(() => {
    const {rootCats} = store.getState().promise;
    if (rootCats?.payload){
        let ul = document.createElement("ul");

        aside.innerHTML = '';

        for (const {_id, name} of rootCats?.payload){
            let li = document.createElement("li");
            let link = document.createElement('a');
            link.href       = `#/category/${_id}`;
            link.innerText  = name;

            li.append(link);
            ul.append(li);
        }

        aside.append(ul);
    };
});

store.subscribe(() => {
    const {catById} = store.getState().promise;
    const [,route, _id] = location.hash.split('/')
    if (catById?.payload && route === 'category'){
        const {name} = catById.payload 
        content.innerHTML = `<h1>${name}</h1> ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ`
        for (const {_id, name, price, images} of catById.payload.goods){
            const card      = document.createElement('div')
            card.innerHTML = `<h2>${name}</h2>
                              <img src="${backendURL}/${images[0].url}" />
                              <strong>${price}</strong>
                              <a href="#/good/${_id}">ССЫЛКА НА СТРАНИЦУ ТОВАРА</a>`
            content.append(card);
        }
    }
});

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

    if (goodById?.payload && route == "good") {
        const {name} = goodById.payload;

        content.innerHTML = `<h1>${name}</h1>`;

        const card = document.createElement('div');

        card.innerHTML = `<h2>${goodById.payload.name}</h2>
                          <img src="${backendURL}/${goodById.payload.images[0].url}" />
                          <strong>${goodById.payload.price}</strong>
                          <button id="goodAdd">Добавить в корзину</button>`;
        content.append(card);
        goodAdd.addEventListener("click", function() {
            store.dispatch(actionCartAdd(goodById.payload));
        });
    };
});

store.subscribe(createCart);