import {HttpStatusError} from "./errors/httpStatusError";
import {BaseError} from "./errors/baseError";
import qs from "qs"
import urijs from "urijs"

export declare interface FetchAdapterConfig {
    baseUrl?: string | Array<string>
    enableCancelRepeatRequest?: boolean,
    enableResponseHidden?: boolean,//在浏览器的Response隐藏后端返回的数据
    requestInitConfig?: RequestInit,
    requestInterceptor?: (params: RequestInterceptorParams) => Promise<RequestInterceptorParams>

    //超时
    timeout?: number
    pingTestTimeout?: number

    responseInterceptor?: (
        {
            response,
            request,
            requestMeta
        }: { response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }
    ) => Promise<{ response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }>

    responseErrorInterceptor?: (error: BaseError | Error | any) => Promise<BaseError | Error | any>,
    successStatusCode?: Array<number>
}

export enum FetchAdapterMethod {
    GET = "GET",
    POST = "POST",
    PUT = "PUT",
    DELETE = "DELETE",
    SEARCH = "SEARCH",
    PATCH = "PATCH",
}

export declare interface FetchAdapterRequestConfig {
    url: string,
    requestInitConfig?: RequestInit,
    params?: BodyInit | null,
    enableCancelRepeatRequest?: boolean,
}

export declare interface RequestMeta {
    requestSentAt?: number //创建请求的时间戳 单位:毫秒
    requestCompleteAt?: number //结束请求的时间戳 单位:毫秒
    requestWaitingTime?: number //http连接时间戳 单位:毫秒
}

export declare interface RequestInterceptorParams {
    url: string,
    config: RequestInit,
    requestMeta?: RequestMeta
}

const getHost = (url: string) => {
    //为了兼容RN端
    const parseURL = URL ?? urijs
    const _url = new parseURL(url)
    // @ts-ignore
    return typeof _url.host === "string" ? _url.host : _url.host()
}

/**
 * ## FetchAdapterResponse **http响应体**
 * **/
export class FetchAdapterResponse extends Response {
    constructor(body?: BodyInit | null, init?: ResponseInit) {
        super(body, init);
    }

    /** 将headers转换成json形式 **/
    public async getTransformHeaders(): Promise<Object> {
        const headerEntries = this.headers.entries()
        const headers = {}
        for (let [key, value] of headerEntries) {
            headers[key] = value
        }
        return headers
    }
}

/**
 * ## FetchAdapter **http适配器**
 * **/
export class FetchAdapter {
    public readonly baseRequestConfig: RequestInit = {
        headers: {
            "content-type": "application/json; charset=utf-8"
        }
    }
    public readonly requestTaskControllerList: Map<string, AbortController> = new Map()
    public readonly enableCancelRepeatRequest: boolean = true

    public baseUrl: Array<string> = []

    public readonly successStatusCode: Array<number> = [200, 201]

    public readonly enableResponseHidden: boolean = false

    public readonly timeout: number = 30000
    public readonly pingTestTimeout: number = 3000

    private readonly httpQualityStore = {}

    constructor(config: FetchAdapterConfig = {}) {
        const {
            baseUrl,
            requestInitConfig,
            requestInterceptor,
            responseInterceptor,
            responseErrorInterceptor,
            enableCancelRepeatRequest,
            successStatusCode,
            enableResponseHidden,

            timeout,
            pingTestTimeout,
        } = config

        this.baseRequestConfig = {...this.baseRequestConfig, ...requestInitConfig}

        if (typeof baseUrl === "string") {
            this.baseUrl = [baseUrl]
        } else {
            this.baseUrl = baseUrl ?? []
        }

        this.requestInterceptor = requestInterceptor ?? this.requestInterceptor
        this.responseInterceptor = responseInterceptor ?? this.responseInterceptor
        this.responseErrorInterceptor = responseErrorInterceptor ?? this.responseErrorInterceptor

        this.enableCancelRepeatRequest = enableCancelRepeatRequest ?? this.enableCancelRepeatRequest

        this.successStatusCode = successStatusCode ?? this.successStatusCode

        this.enableResponseHidden = enableResponseHidden ?? this.enableResponseHidden

        this.timeout = timeout ?? this.timeout
        this.pingTestTimeout = pingTestTimeout ?? this.pingTestTimeout
    }

    /** 测速 **/
    public async runPingTest({isIgnoreHttpErrors = false}: { isIgnoreHttpErrors?: boolean } = {}): Promise<void> {
        if (!this.baseUrl || this.baseUrl.length <= 1) return
        const baseConfig: any = {mode: "no-cors", redirect: "follow"}
        const queue: Array<Promise<any>> = []

        for (let url of this.baseUrl) {
            queue.push(new Promise(async (resolve, reject) => {
                const startTime = new Date().getTime()
                const abortController = new AbortController()

                setTimeout(async () => {
                    abortController.abort()
                }, this.pingTestTimeout)

                return await fetch(url, {...baseConfig, signal: abortController.signal})
                    .then(res => {
                        const endTime = new Date().getTime()
                        resolve({url, time: endTime - startTime,})
                    })
                    .catch(e => {
                        if (isIgnoreHttpErrors) {
                            const endTime = new Date().getTime()
                            resolve({url, time: endTime - startTime,})
                        } else {
                            const endTime = new Date().getTime()
                            reject({url, time: endTime - startTime,})
                        }
                    })
            }))
        }

        const resultsList = await Promise.allSettled(queue)

        let successList = resultsList.filter(value => value.status === "fulfilled")
        successList.sort((a, b) => {
            // @ts-ignore
            return a.value.time - b.value.time
        })

        //全部测试地址都死亡的话  则排列顺序按照用户的最初设定
        if (successList.length !== 0) {
            // @ts-ignore
            this.baseUrl = successList.map(value => value.value.url)
        }
    }

    //达到一定条件 根据每个baseurl的请求分数 重新排序
    private async resortBaseUrl() {
        if (this.baseUrl.length <= 1) return
        const baseUrlToString = JSON.stringify(this.baseUrl)

        //排序条件阀值 当totalTimes可以被threshold整除的时候 就会重新排序
        const threshold = 100
        let totalTimes = 0

        let httpQualityStoreToBaseUrl: string[] = []

        for (let host of Object.keys(this.httpQualityStore)) {
            if (baseUrlToString.includes(host)) {
                const val = this.httpQualityStore[host]
                totalTimes += val.successesTime

                httpQualityStoreToBaseUrl.push(host)
            }
        }

        if (totalTimes >= threshold && Math.round(totalTimes % threshold) === 0) {
            httpQualityStoreToBaseUrl = [...new Set(httpQualityStoreToBaseUrl)]

            httpQualityStoreToBaseUrl.sort((a, b) => {
                return this.httpQualityStore[b].score - this.httpQualityStore[a].score
            })

            // @ts-ignore
            const parallel: string[] = httpQualityStoreToBaseUrl.map(value => this.baseUrl.find(value1 => value1.includes(value))).filter(value => value && typeof value === "string")

            this.baseUrl = [...new Set([...parallel, ...this.baseUrl])]
        }
    }

    /** 获取动态url **/
    public async getDynamicBaseUrl(): Promise<string> {
        if (this.baseUrl.length <= 1) {
            return this.baseUrl[0] ?? ""
        }

        await this.resortBaseUrl()

        const weightList: string[] = []

        for (let i = 0; i < this.baseUrl.length; i++) {
            const url = this.baseUrl[i]
            const count = this.baseUrl.length - i
            for (let j = 0; j < count; j++) {
                weightList.push(url)
            }
        }

        const index = Math.floor(Math.random() * weightList.length)

        return weightList[index] ?? ""
    }

    /** 对url以及query参数进行处理 **/
    public async makeUrl(originalUrl, params?: Object | null): Promise<string> {
        const url: string = await this.getDynamicBaseUrl()
        originalUrl = originalUrl.includes("http") ? originalUrl : `${url}${originalUrl}`

        let parsedOriginalQuery: any = ""

        if (originalUrl.includes("?")) {
            parsedOriginalQuery = originalUrl.slice(originalUrl.indexOf("?") + 1)
            parsedOriginalQuery = qs.parse(parsedOriginalQuery)
            params = {...parsedOriginalQuery, ...params}
            originalUrl = originalUrl.slice(0, originalUrl.indexOf("?"))
        }

        params = qs.stringify(params)
        if ((params as string).length !== 0) {
            params = `?${params}`
        }

        return `${originalUrl}${params}`
    }

    /** 对body参数进行处理 **/
    public async makeBody(params?: BodyInit | null): Promise<BodyInit | null> {
        if (!params) return null
        if (Object.prototype.toString.call(params) === "[object Object]") {
            return JSON.stringify(params)
        }
        return params
    }

    /** 生成请求token 用来取消重复的请求 **/
    public async generateRequestToken(config: FetchAdapterRequestConfig): Promise<string> {
        const params = {...config.requestInitConfig, url: config.url}
        const bodyToString = (body: any) => {
            if (Object.prototype.toString.call(body) === "[object FormData]") {
                return JSON.stringify(Array.from(body.entries()))
            }
            if (Object.prototype.toString.call(body) === "[object File]") {
                return body.lastModified + body.lastModifiedDate + body.name + body.type + body.size
            }
            return JSON.stringify(body)
        }
        return params.url + params.method + params.headers + bodyToString(params.body)
    }

    /** 生成取消请求的控制信号 **/
    public async getRequestSignal(config: FetchAdapterRequestConfig, timeout?: number): Promise<AbortSignal | null> {

        timeout = timeout ?? this.timeout

        let signal: AbortSignal | null = null
        const requestToken = await this.generateRequestToken(config)
        const abortController = new AbortController()

        if (this.requestTaskControllerList.has(requestToken)) {
            // @ts-ignore
            this.requestTaskControllerList.get(requestToken).abort()
            this.requestTaskControllerList.set(requestToken, abortController)
            signal = abortController.signal
        } else {
            this.requestTaskControllerList.set(requestToken, abortController)
            signal = abortController.signal
        }

        setTimeout(async () => {
            abortController.abort()
        }, timeout)

        return signal
    }

    private async createHttpQualityInfo({request, requestMeta}: { request?: Request, requestMeta?: RequestMeta }) {
        if (!request || !requestMeta) return
        const host = getHost(request.url)

        if (host) {
            const maxNumber = Number.MAX_SAFE_INTEGER * 0.5

            if (!this.httpQualityStore[host]) this.httpQualityStore[host] = {
                successesTime: 0,
                waitingTime: 0,
                minimumWaitingTime: 0,
                maximumWaitingTime: 0,
                score: 0
            }

            //如果该库应用在服务端
            //避免数字超出系统最大运算范围造成未知问题
            if (this.httpQualityStore[host].successesTime > maxNumber ||
                this.httpQualityStore[host].waitingTime > maxNumber ||
                this.httpQualityStore[host].score > maxNumber) {

                this.httpQualityStore[host] = {
                    successesTime: 0,
                    waitingTime: 0,
                    minimumWaitingTime: 0,
                    maximumWaitingTime: 0,
                    score: 0
                }
            }

            const requestWaitingTime = Number(requestMeta.requestWaitingTime)

            if (requestWaitingTime && Number.isNaN(requestWaitingTime) === false) {
                this.httpQualityStore[host].waitingTime += requestWaitingTime
                this.httpQualityStore[host].successesTime++

                if (requestWaitingTime < this.httpQualityStore[host].minimumWaitingTime || this.httpQualityStore[host].minimumWaitingTime === 0) {
                    this.httpQualityStore[host].minimumWaitingTime = requestWaitingTime
                }

                if (requestWaitingTime > this.httpQualityStore[host].maximumWaitingTime || this.httpQualityStore[host].maximumWaitingTime === 0) {
                    this.httpQualityStore[host].maximumWaitingTime = requestWaitingTime
                }

                //请求耗时理想值
                const idealTime = 1000

                //每个请求平均耗时
                const averageTime = this.httpQualityStore[host].waitingTime / this.httpQualityStore[host].successesTime

                //分数公式
                //平均耗时占50%  最小耗时占30%  最大耗时占20%
                //耗时分数 （理想值-花费的时间）* 分数占比=分数
                //时间越比理想值小 则分数越高
                this.httpQualityStore[host].score = ((idealTime - averageTime) * 0.5) + ((idealTime - this.httpQualityStore[host].minimumWaitingTime) * 0.3) + ((idealTime - this.httpQualityStore[host].maximumWaitingTime) * 0.2)
            }
        }
    }

    /** 构造基本的请求方法 **/
    public request(config: FetchAdapterRequestConfig): Promise<{ response: FetchAdapterResponse, request: Request }> {
        return new Promise(async (resolve, reject) => {
            let url: string
            const enableCancelRepeatRequest: boolean = config.enableCancelRepeatRequest ?? this.enableCancelRepeatRequest

            config.requestInitConfig = config.requestInitConfig ?? {}

            config.requestInitConfig.method = config?.requestInitConfig?.method ?? FetchAdapterMethod.GET

            if ([FetchAdapterMethod.GET, FetchAdapterMethod.SEARCH].includes(config.requestInitConfig.method as FetchAdapterMethod)) {
                url = await this.makeUrl(config.url, config.params)
            } else {
                url = await this.makeUrl(config.url)

                if (config.params) {
                    config.requestInitConfig.body = await this.makeBody(config.params)
                }
            }

            if (enableCancelRepeatRequest) {
                config.requestInitConfig.signal = config.requestInitConfig.signal ?? await this.getRequestSignal(config)
            }

            let requestInterceptorResult = await this.requestInterceptorBySystem({
                url, config: {...this.baseRequestConfig, ...config.requestInitConfig}
            })

            requestInterceptorResult = await this.requestInterceptor(requestInterceptorResult)

            const requestConfig = new Request(requestInterceptorResult.url, requestInterceptorResult.config)

            return await fetch(requestConfig)
                .then(async (response: Response) => {
                    //*注意这里必须先转换成blob 否则react native端获取body.json()会出现空的情况
                    //*这里之所以要clone一个新对象 是因为如果在源对象转换成blob  会导致在浏览器的Response不显示后端的数据
                    //这意味着可以利用这个特性对Response进行轻度加密,这个特性至少可以在chrome100以下的浏览器生效
                    const cloneResponse = !this.enableResponseHidden ? response.clone() : response
                    const transformBody = await cloneResponse.blob()

                    const systemAfter = await this.responseInterceptorBySystem(
                        {
                            response: new FetchAdapterResponse(transformBody, response),
                            request: requestConfig,
                            requestMeta: requestInterceptorResult.requestMeta
                        }
                    )

                    resolve(await this.responseInterceptor(systemAfter))
                })
                .catch(async (error: Error) => {
                    const requestMeta = requestInterceptorResult.requestMeta as RequestMeta

                    //计算时间差
                    // @ts-ignore
                    requestMeta.requestCompleteAt = new Date().getTime()
                    // @ts-ignore
                    requestMeta.requestWaitingTime = requestMeta.requestCompleteAt - requestMeta.requestSentAt

                    const newError = new BaseError(
                        {
                            request: requestConfig,
                            requestMeta,
                            code: 0,
                            message: error.message,
                            response: new FetchAdapterResponse(),
                            stack: error.stack,
                            cause: error.cause
                        }
                    )

                    this.responseErrorInterceptorBySystem(newError)
                        .then(async error2 => {
                            try {
                                resolve(await this.responseErrorInterceptor(error2))
                            } catch (e) {
                                reject(e)
                            }
                        })
                        .catch(async error2 => {
                            try {
                                resolve(await this.responseErrorInterceptor(error2))
                            } catch (e) {
                                reject(e)
                            }
                        })
                })
        })
    }

    /** 系统默认请求拦截器 **/
    private async requestInterceptorBySystem(params: RequestInterceptorParams): Promise<RequestInterceptorParams> {
        params.requestMeta = {
            ...params.requestMeta, ...{
                requestSentAt: new Date().getTime()
            }
        }
        return params
    }

    /** 请求拦截器 **/
    private async requestInterceptor(params: RequestInterceptorParams): Promise<RequestInterceptorParams> {
        return params
    }

    /** 系统默认响应拦截器 **/
    private async responseInterceptorBySystem(
        {
            response,
            request,
            requestMeta
        }: { response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }
    ): Promise<{ response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }> {

        // @ts-ignore
        requestMeta.requestCompleteAt = new Date().getTime()
        // @ts-ignore
        requestMeta.requestWaitingTime = requestMeta.requestCompleteAt - requestMeta.requestSentAt

        if (this.successStatusCode.includes(response.status) === false) {
            throw new HttpStatusError({code: response.status, response, request, requestMeta})
        }

        this.createHttpQualityInfo({request, requestMeta})

        return {response, request, requestMeta}
    }

    /** 响应拦截器 **/
    private async responseInterceptor(
        {
            response,
            request,
            requestMeta
        }: { response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }
    ): Promise<{ response: FetchAdapterResponse, request: Request, requestMeta?: RequestMeta }> {
        return {response, request, requestMeta}
    }

    /** 系统默认错误拦截器 **/
    private async responseErrorInterceptorBySystem(error: BaseError | Error | any): Promise<BaseError | Error | any> {
        throw error
    }

    /** 错误拦截器 **/
    private async responseErrorInterceptor(error: BaseError | Error | any): Promise<BaseError | Error | any> {
        throw error
    }

    public async get(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.GET}
        })
    }

    public async post(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.POST}
        })
    }

    public async put(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.PUT}
        })
    }

    public async delete(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.DELETE}
        })
    }

    public async search(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.SEARCH}
        })
    }

    public async patch(url: string, params?: Object | BodyInit | null, enableCancelRepeatRequest?: boolean): Promise<any> {
        return await this.request({
            url,
            // @ts-ignore
            params,
            enableCancelRepeatRequest,
            requestInitConfig: {method: FetchAdapterMethod.PATCH}
        })
    }
}
