import axios, {AxiosError, AxiosRequestConfig, AxiosResponse} from 'axios';
import SafePromise from '../foundation/safe-promise.ts';
import util from '../util/index.ts';

axios.defaults.baseURL = '';
axios.defaults.withCredentials = true; // 允许携带Cookie
axios.defaults.timeout = util.build.isProduction() ? 3000 : 0; // 默认超时时间，生产模式：3秒，开发模式不限时，以便于服务端调试
Object.assign(axios.defaults.headers.common, {'X-Requested-With': 'XMLHttpRequest'}); // 标记为AJAX请求

type ApiRequestOptions = {
    url: string;
    method: 'GET' | 'POST';
    params?: Record<string, any>;
    body?: Record<string, any> | FormData;
    timeout?: number;
    onUploadProgress?: (ratio: number) => void;
}

type BusinessError = {
    code: string;
    message: string;
    type: string;
    field?: string;
    detail?: string;
}

type ReminderMessage = {
    type: 'ERROR' | 'WARNING' | 'INFO' | 'SUCCESS';
    content: string;
    seconds?: number;
}

type ApiResponse<R> = {
    successful: boolean;
    result?: R;
    errors?: BusinessError[];
    reminders?: ReminderMessage[];
}

export type ApiRequestErrorLevel = 'ALERT' | 'TOAST' | 'CONSOLE';

export type ApiRequestError = {
    url: string;
    status: number;
    level: ApiRequestErrorLevel | 'FORWARD';
    payload: any;
}

export function setBaseUrl(baseUrl: string) {
    axios.defaults.baseURL = baseUrl;
}

export function getBaseUrl(app?: string) {
    let baseUrl = axios.defaults.baseURL;
    if (app) {
        baseUrl += ':' + app + '/';
    }
    return baseUrl;
}

const APP_CONTEXT_URLS: Record<string, string> = {};

/**
 * 设置应用及其上下文根的对应关系
 * @param appContextUrls 应用及其上下文根的对应关系，key为应用名，value为应用对应的上下文根，以/开头，末尾没有/
 */
export function setAppContextUrls(appContextUrls: Record<string, string>) {
    Object.keys(appContextUrls).forEach((key: string) => {
        let contextUrl = appContextUrls[key];
        if (contextUrl.startsWith('/')) { // 只接受/开头的上下文根
            if (contextUrl.endsWith('/')) {
                contextUrl = contextUrl.substring(0, contextUrl.length - 1);
            }
            APP_CONTEXT_URLS[key] = contextUrl;
        }
    });
}

export function get<T>(url: string, params?: Record<string, any>, timeout?: number): Promise<T> {
    return request<T>({url, method: 'GET', params, timeout});
}

export function post<T>(url: string, body?: Record<string, any>, timeout?: number): Promise<T> {
    return request<T>({url, method: 'POST', body, timeout});
}

export function upload<T>(url: string, body: FormData, timeout?: number, onUploadProgress?: (ratio: number) => void): Promise<T> {
    return request<T>({url, method: 'POST', body, timeout, onUploadProgress});
}

export function toRequestUrl(url: string) {
    if (url.startsWith(':')) {
        let index = url.indexOf('/');
        // 含/则:到首个/之间为应用名称，否则:之后均为应用名称
        let appName = index > 0 ? url.substring(1, index) : url.substring(1);
        // 含/则/之后均为相对路径，否则相对路径为空
        let relativeUrl = index > 0 ? url.substring(index) : '';
        url = (APP_CONTEXT_URLS[appName] || ('/' + appName)) + relativeUrl;
    }
    return url;
}

export function request<T>(options: ApiRequestOptions): Promise<T> {
    const config: AxiosRequestConfig<Record<string, any> | FormData> = {
        url: toRequestUrl(options.url),
        method: options.method,
        params: options.params,
        data: options.body,
        timeout: options.timeout,
    };
    if (config.params) {
        config.paramsSerializer = params => {
            return util.net.toParameterString(params);
        };
    }
    if (config.data && !(config.data instanceof FormData)) {
        let keys = Object.keys(config.data);
        for (let key of keys) {
            let value = config.data[key];
            if (value instanceof Date) {
                config.data[key] = value.formatDateTime();
            }
        }
    }
    if (options.onUploadProgress) {
        config.onUploadProgress = event => {
            const ratio = event.total ? (event.loaded / event.total) : 0;
            options.onUploadProgress(ratio);
        }
    }
    return doRequest<T>(config);
}

function doRequest<T>(config: AxiosRequestConfig<Record<string, any>>): Promise<T> {
    return new SafePromise<T>((resolve, reject) => {
        axios.request<ApiResponse<T>>(config).then((response: AxiosResponse<ApiResponse<T>>) => {
            const data = response.data;
            if (data.successful) {
                resolve(data.result);
            } else {
                reject({
                    url: config.url,
                    status: 200,
                    level: 'ALERT',
                    payload: data.errors,
                } as ApiRequestError);
            }
        }).catch((error: AxiosError<any>) => {
            const response = error.response;
            if (response) {
                switch (response.status) {
                    case 401: {
                        reject({
                            url: config.url,
                            status: response.status,
                            level: 'FORWARD',
                        } as ApiRequestError);
                        break;
                    }
                    case 400: {
                        let errors = response.data.errors;
                        if (errors && errors.length) { // 字段格式异常
                            reject({
                                url: config.url,
                                status: response.status,
                                level: 'ALERT',
                                payload: errors,
                            } as ApiRequestError);
                        } else if (response.data.message) {
                            reject({
                                url: config.url,
                                status: response.status,
                                level: 'TOAST',
                                payload: response.data.message,
                            } as ApiRequestError);
                        }
                        break;
                    }
                    case 404: {
                        console.error('在服务器未启动时也会报该错误');
                        reject({
                            url: config.url,
                            status: response.status,
                            level: 'ALERT',
                            payload: '服务器正在更新，请稍候再试',
                        } as ApiRequestError);
                        break;
                    }
                    case 500: {
                        let message: string;
                        if (response.data && response.data.message) {
                            message = response.data.message;
                        } else {
                            message = response.data;
                        }
                        if (message && message.includes('time out')) {
                            console.error(message);
                            reject({
                                url: config.url,
                                status: response.status,
                                level: 'TOAST',
                                payload: '请求超时'
                            } as ApiRequestError);
                        } else {
                            reject({
                                url: config.url,
                                status: response.status,
                                level: 'CONSOLE',
                                payload: message,
                            } as ApiRequestError);
                        }
                        break;
                    }
                    default: {
                        reject({
                            url: config.url,
                            status: response.status,
                            level: 'CONSOLE',
                            payload: error,
                        } as ApiRequestError);
                    }
                }
            } else {
                reject({
                    url: config.url,
                    status: 0,
                    level: 'CONSOLE',
                    payload: error,
                } as ApiRequestError);
            }
        });
    }, handleRequestError);
}

const errorHandlers: Record<string, (message: string) => void> = {};

export function setErrorHandler(level: ApiRequestErrorLevel, handler: (message: string) => void): void {
    errorHandlers[level] = handler;
}

/**
 * 跳转到登录页的函数，由业务工程覆写
 * @param url 出错时的请求地址
 */
let fnToLoginPage: (url: string) => void;

export function ifToLoginPage(fn: (url: string) => void): void {
    fnToLoginPage = fn;
}

export function toLogin(url: string): void {
    if (fnToLoginPage) {
        fnToLoginPage(url);
    } else {
        console.error('未实现跳转到首页的方法，请初始化时调用:', ifToLoginPage);
    }
}

export function handleRequestError(error: ApiRequestError): void {
    const payload = error.payload;
    let message = '';
    if (Array.isArray(payload)) {
        for (let e of payload) {
            if (e.field) {
                message += e.field + ' ';
            }
            message += e.message + '\n';
        }
        message = message.trim();
    } else if (typeof payload === 'string') {
        message = payload;
    } else if (payload) {
        message = payload.message || payload.stack || payload.toString();
    }
    let handler = errorHandlers[error.level];
    if (handler) {
        handler(message);
    } else {
        switch (error.level) {
            case 'ALERT': {
                window.tnx.error(message);
                break;
            }
            case 'TOAST': {
                window.tnx.toast(message);
                break;
            }
            case 'CONSOLE': {
                console.error(message);
                break;
            }
            case 'FORWARD': {
                toLogin(error.url);
                break;
            }
        }
    }
}
