import Api, { isCancelError } from 'Framework/Api';
import isObject from 'lodash/isObject';
import { createAsyncManager } from '../../Utils/Async/AsyncManager';

export const REQUEST_DATA = 'REQUEST_DATA';
export const POST_DATA = 'POST_DATA';
export const NETWORK_ERROR = 'NETWORK_ERROR';
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const RECEIVE_DATA = 'RECEIVE_DATA';
export const REQUEST_ERROR = 'REQUEST_ERROR';

const requestData = source => ({
    type: REQUEST_DATA,
    source,
});

export const postRequest = (source, data) => ({
    type: POST_DATA,
    source,
    data,
});

const networkError = (source, error) => {
    // it has to be logged because it can swallow different kind of errors
    // eslint-disable-next-line no-console
    console.error(error);

    return {
        type: NETWORK_ERROR,
        source,
        error,
        receivedAt: Date.now(),
    };
};

const cancelRequest = source => ({
    type: CANCEL_REQUEST,
    source,
});

const receiveData = (source, data) => ({
    type: RECEIVE_DATA,
    source,
    data,
    receivedAt: Date.now(),
});

const requestError = (source, error) => ({
    type: REQUEST_ERROR,
    source,
    error,
    receivedAt: Date.now(),
});

const handleResponse = (source, request, receiver, extraData = {}) => dispatch =>
    request
        .then(response => {
            dispatch(receiveData(source, response.data));
            return dispatch(receiver(response.data, extraData, response));
        })
        .catch(error => {
            if (isObject(error.response)) {
                if (error.response.status === 404) {
                    return dispatch(requestError(source, error));
                }
                if (isObject(error.response.data)) {
                    // request error (generated by the server)
                    dispatch(receiveData(source, error));
                    return dispatch(receiver(error.response.data, extraData, error.response));
                }
                if (error.response.status >= 400 && error.response.status < 500) {
                    return dispatch(requestError(source, error));
                }
            }
            if (isCancelError(error)) {
                return dispatch(cancelRequest(source));
            } else {
                return dispatch(networkError(source, error));
            }
        });

export const fetchData = (source, receiver, extraData = {}) => dispatch => {
    dispatch(requestData(source));
    return dispatch(handleResponse(source, Api.request(source), receiver, extraData));
};

export const postData = (source, data, receiver, config = {}, extraData = {}) => dispatch => {
    dispatch(postRequest(source, data));
    return dispatch(
        handleResponse(
            source,
            Api.request({ url: source, method: 'post', data: data, ...config }),
            receiver,
            extraData
        )
    );
};

const defaultRequestManager = createAsyncManager((...args) => Api.request(...args), {
    getKey: sourceConfig => {
        if (typeof sourceConfig === 'string') {
            return sourceConfig;
        } else if (typeof sourceConfig === 'object' && sourceConfig.url) {
            return sourceConfig.url;
        } else {
            throw new TypeError('The sourceConfig has to be a string or an object with a url property!');
        }
    },
});

export const asyncRequest = (source, receiver, extraData = {}, options = {}) => async dispatch => {
    const { requestManager = defaultRequestManager, useLocalErrorHandling = false } = options;
    const request = requestManager.create(source);
    try {
        const response = await request.call();
        dispatch(receiveData(source, response.data));
        return receiver ? dispatch(receiver(response.data, extraData, response)) : response;
    } catch (error) {
        if (request.isOutdated()) {
            return;
        }
        if (useLocalErrorHandling) {
            throw error;
        }
        if (isObject(error.response)) {
            if (error.response.status === 404) {
                dispatch(requestError(source, error));
            }
            if (isObject(error.response.data)) {
                // request error (generated by the server)
                dispatch(receiveData(source, error));
                return receiver
                    ? dispatch(receiver(error.response.data, extraData, error.response))
                    : error.response;
            }
            if (error.response.status >= 400 && error.response.status < 500) {
                dispatch(requestError(source, error));
            }
        }
        if (isCancelError(error)) {
            dispatch(cancelRequest(source));
        } else {
            dispatch(networkError(source, error));
        }
        throw error;
    }
};

export const fetchDataAsync = (source, receiver, extraData = {}) => dispatch => {
    dispatch(requestData(source));
    return dispatch(asyncRequest(source, receiver, extraData));
};

export const postDataAsync = (
    source,
    data,
    receiver,
    config = {},
    extraData = {},
    options = {}
) => dispatch => {
    dispatch(postRequest(source, data));
    return dispatch(
        asyncRequest({ url: source, method: 'post', data: data, ...config }, receiver, extraData, options)
    );
};

export const createDefaultReceiver = (actionType, actionProps) => (data, extraData, response) => ({
    ...actionProps,
    type: actionType,
    data,
    extraData,
    response,
    receivedAt: Date.now(),
});
