import axios, {AxiosRequestConfig, AxiosResponse, AxiosError} from "axios";
import {toastError} from "../services/toaster";
import {GenericAbortSignal} from "axios";


const cache = require('js-cache');
const CACHE_LIFE = 600000 //10 mins = 600000 ms
export const cacheClear = () => cache.clear()

const {
    REACT_APP_ERROR_HANDLING: ERROR_HANDLING,
    REACT_APP_BFF: BFF,
    NODE_ENV
} = process.env

type AxiosErrorData = {
    message: string; /** User friendly message*/
    error?: {
        message: string  /** Message for debugging*/
        stackTrace: {
            /** error trace from laravel*/
            args: any[]
            class: string
            file: string
            function: string
            line: number
            type: string
        }[]
    }
}

declare module 'axios' {
    export interface AxiosRequestConfig {
        withCache?: boolean;
        cancelable?: boolean
    }
}

export let abortSignal: GenericAbortSignal | undefined;

export function setAbortSignal(signal: GenericAbortSignal | undefined) {
    abortSignal = signal
}

/**
 * Attaches correct signal before each call
 * Used with combination with useAbortControlled
 * The hooks needs to be used in the components u want the calls to be abortable
 *
 * If response is cached it aborts the call and returns the cached response
 */
export const onRequestFulfilled = (config: AxiosRequestConfig) => {

    let url = config.url;
    if (config.withCache && config.method == 'get' && !config.params) {

        const _cached = cache.get(url);
        if (_cached) {
            _cached.__fromCache = true;
            config.data = _cached;
            // Set the request adapter to send the cached response and prevent the request from actually running
            // @ts-ignore
            config.adapter = () => {
                return Promise.resolve({
                    data: _cached,
                    status: 200,
                    statusText: 'OK',
                    headers: config.headers,
                    config: config,
                    request: config
                });
            };
        } else {
        }
    } else {
    }


    return {
        ...config,
        // @ts-ignore
        signal: config.cancelable ? abortSignal : undefined,
    };
}

/**
 * Caches response before returning it
 * Cachable calls are only et which have no params
 */
export const onResponseFulfilled = (response: AxiosResponse) => {

    const config = response.config
    const isCacheable = config.withCache && config.method == 'get' && !config.params;

    if (isCacheable) {
        let url = response.config.url;

        // if (response.config.params)
        //     url += '?' + $.param(response.config.params);

        if (response.config.method === 'get') {
            // On get request, store the response in the cache
            cache.set(url, response.data, CACHE_LIFE);
        } else {
            // For post, put or delete, just delete the cached version of the url
            // e.g. posting to `/users` would delete the `/users` cache, so when you ask for users again you get the real version
            cache.del(response.config.url);

            // // also, when making a post,put or delete request to `/users/1`, would try to delete the `/users` for the same reason
            // const uri = url.replace(config.http.api.base_url, ''),
            //     parentUri = /(.*)\/([a-z0-9\-]+)\/?$/ig.exec(uri);
            //
            // if (parentUri)
            //     cache.del(`${config.http.api.base_url}${parentUri[1]}`);
            //
            // // Delete similar url that just have query string diferences
            // // Specially useful for things like Laravel's `with` param
            // // e.g. `/users?with=permissions` will get cached but the post to `/users` wont remove it from the cache
            // // so I look all the cached url's and try to match it without the querystrings
            // const urls = Object.keys(cache.debug());
            //
            // for (const _url of urls) {
            //     if (_url.match(/^[^?]+/)[0] === response.config.url)
            //         cache.del(_url);
            // }
        }
    }
    return response;
}

/**
 * Global Error handling
 * Errors are returned so they can more specifically handled where they are called?
 *
 * Any status codes that falls outside the range of 2xx cause this function to trigger
 */
export const onResponseRejected = (error: AxiosError<AxiosErrorData, any>) => {
    /**
     * HANDLING ERROR IN RESPONSE
     */

    if (error.response) {

        if (ERROR_HANDLING === 'strict') {


            toastError(error.response.status + ": " + error.response.data.message)
            // toastError(error.data.message)
        }

        console.error("ERROR IN RESPONSE >>>>>");
        console.error(error.response);

        /**
         * 515 returns when the login has been disabled
         * we force reload to get the new Setting call
         */
        if (error.response.status === 515) {
            toastError('Η είσοδος στο D.U.A Platform προσωρινά δεν είναι διαθέσιμη. Δοκιμάστε ξανά αργότερα.')
            setTimeout(() => {
                document.location.reload()
            }, 3000)
        }

        if (error.response.status === 500) {
            toastError('Υλοποιούνται εργασίες συντήρησης στον διακομιστή. Η είσοδος στο D.U.A Platform προσωρινά δεν είναι διαθέσιμη. Δοκιμάστε ξανά αργότερα.')
        }
    }

    /**
     * HANDLING ERROR IN REQUEST
     * (WE RECEIVED NO RESPONSE)
     */
    else if (error.request) {


        if (NODE_ENV === "development") {
            toastError(error.message)
            // toastError(error.data.message)
        }

        /**
         * ERROR WAS CANCELLED FROM COMPONENT UNMOUNT
         */
        if (error.code === 'ERR_CANCELED') {
            return Promise.reject(error);
        }

        /**
         * TIMEOUT WAS EXCEEDED
         */
        if (error.code === 'ECONNABORTED') {
            toastError('Υλοποιούνται εργασίες συντήρησης στο δίκτυο. Η είσοδος στο D.U.A Platform προσωρινά δεν είναι διαθέσιμη. Δοκιμάστε ξανά αργότερα.')
            return Promise.reject(error);
        }

        toastError('Υλοποιούνται εργασίες συντήρησης στο δίκτυο. Η είσοδος στο D.U.A Platform προσωρινά δεν είναι διαθέσιμη. Δοκιμάστε ξανά αργότερα.')
        console.error("ERROR IN REQUEST >>>>>");
        console.error(error.request);


    }
    /**
     * HANDLING OTHER ERROS
     */
    else {
        console.error("UNKNOWN ERROR IN REQUEST", error.request);
        console.error(error);
        // Something happened in setting up the request that triggered an Error
    }

    return Promise.reject(error);
}


export const instance = axios.create({
    // timeout: 15000,
    withCredentials: true,
    baseURL: BFF,
    withCache: false,
    cancelable: true
});

instance.interceptors.request.use(
    onRequestFulfilled
);

instance.interceptors.response.use(
    onResponseFulfilled
    ,
    onResponseRejected
);

export function sleep(ms = 2000): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
