import axios, {
    type AxiosInstance,
    type AxiosRequestConfig,
    type AxiosResponse,
    type AxiosError,
    InternalAxiosRequestConfig,
} from 'axios';

import config from '@/config/request';

import {
    IRequest,
    IRequestInterceptorAxios,
    IRequestInterceptorRequest,
    IRequestInterceptorTuple,
    IRequestOptions,
    IResponseInterceptorTuple,
} from './types.ts';

export * from './types';

let requestInstance: AxiosInstance;

const getRequestInstance = (): AxiosInstance => {
    if (requestInstance) return requestInstance;
    requestInstance = axios.create(config);

    config?.requestInterceptors?.forEach((interceptor) => {
        if (interceptor instanceof Array) {
            requestInstance.interceptors.request.use(async (config) => {
                const { url } = config;
                if (interceptor[0].length === 2) {
                    const { url: newUrl, options } = await (
                        interceptor[0] as IRequestInterceptorRequest
                    )(url!, config);
                    return { ...options, url: newUrl } as InternalAxiosRequestConfig;
                }
                return (interceptor[0] as IRequestInterceptorAxios)(
                    config
                ) as InternalAxiosRequestConfig;
            }, interceptor[1]);
        } else {
            requestInstance.interceptors.request.use(async (config) => {
                const { url } = config;
                if (interceptor.length === 2) {
                    const { url: newUrl, options } = await (
                        interceptor as IRequestInterceptorRequest
                    )(url!, config);
                    return { ...options, url: newUrl } as InternalAxiosRequestConfig;
                }
                return (interceptor as IRequestInterceptorAxios)(
                    config
                ) as InternalAxiosRequestConfig;
            });
        }
    });

    config?.responseInterceptors?.forEach((interceptor) => {
        if (interceptor instanceof Array) {
            requestInstance.interceptors.response.use(interceptor[0], interceptor[1]);
        } else {
            requestInstance.interceptors.response.use(interceptor);
        }
    });

    // 当响应的数据 success 是 false 的时候，抛出 error 以供 errorHandler 处理。
    requestInstance.interceptors.response.use((response) => {
        const { data } = response;
        if (data?.success === false && config?.errorConfig?.errorThrower) {
            config.errorConfig.errorThrower(data);
        }
        return response;
    });
    return requestInstance;
};

const request: IRequest = (url: string, opts: IRequestOptions = { method: 'GET' }) => {
    const requestInstance = getRequestInstance();
    const { getResponse = false, requestInterceptors, responseInterceptors } = opts;
    const requestInterceptorsToEject = requestInterceptors?.map(
        (interceptor: IRequestInterceptorTuple) => {
            if (interceptor instanceof Array) {
                return requestInstance.interceptors.request.use(async (config) => {
                    const { url } = config;
                    if (interceptor[0].length === 2) {
                        const { url: newUrl, options } = await (
                            interceptor[0] as IRequestInterceptorRequest
                        )(url!, config);
                        return { ...options, url: newUrl } as InternalAxiosRequestConfig;
                    }
                    return (interceptor[0] as IRequestInterceptorAxios)(
                        config
                    ) as InternalAxiosRequestConfig;
                }, interceptor[1]);
            } else {
                return requestInstance.interceptors.request.use(async (config) => {
                    const { url } = config;
                    if (interceptor.length === 2) {
                        const { url: newUrl, options } = await (
                            interceptor as IRequestInterceptorRequest
                        )(url!, config);
                        return { ...options, url: newUrl } as InternalAxiosRequestConfig;
                    }
                    return (interceptor as IRequestInterceptorAxios)(
                        config
                    ) as InternalAxiosRequestConfig;
                });
            }
        }
    );
    const responseInterceptorsToEject = responseInterceptors?.map(
        (interceptor: IResponseInterceptorTuple) => {
            return interceptor instanceof Array
                ? requestInstance.interceptors.response.use(interceptor[0], interceptor[1])
                : requestInstance.interceptors.response.use(interceptor);
        }
    );
    return new Promise<AxiosResponse>((resolve, reject) => {
        requestInstance
            .request({ ...opts, url })
            .then((res) => {
                requestInterceptorsToEject?.forEach((interceptor) => {
                    requestInstance.interceptors.request.eject(interceptor);
                });
                responseInterceptorsToEject?.forEach((interceptor) => {
                    requestInstance.interceptors.response.eject(interceptor);
                });
                resolve(getResponse ? res : res.data);
            })
            .catch((error) => {
                requestInterceptorsToEject?.forEach((interceptor) => {
                    requestInstance.interceptors.request.eject(interceptor);
                });
                responseInterceptorsToEject?.forEach((interceptor) => {
                    requestInstance.interceptors.response.eject(interceptor);
                });
                try {
                    const handler = config?.errorConfig?.errorHandler;
                    if (handler) handler(error, Object.assign({}, config, opts));
                } catch (e) {
                    reject(e);
                }
                reject(error);
            });
    });
};

export { request, getRequestInstance };

export type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, IRequest as Request };
