type RequestConfig = {
  url: string
  method: string
  headers: Record<string, string>
  timeout?: number
  body?: BodyInit | null
}

type ResponseWrapper<T = unknown> = {
  data: T
  status: number
  statusText: string
  headers: Headers
}

type RequestInterceptor = {
  onFulfilled?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>
  onRejected?: (error: unknown) => RequestConfig | Promise<RequestConfig>
}

type ResponseInterceptor = {
  onFulfilled?: (
    response: ResponseWrapper<unknown>
  ) => ResponseWrapper<unknown> | Promise<ResponseWrapper<unknown>>
  onRejected?: (error: unknown) => unknown | Promise<unknown>
}

export class RequestExtend {
  private baseURL: string
  private timeout: number
  private headers: Record<string, string>

  private static defaultBaseURL = ''
  private static defaultTimeout = 30000
  private static defaultHeaders: Record<string, string> = {
    'Content-Type': 'application/json'
  }

  private static requestInterceptors: RequestInterceptor[] = []
  private static responseInterceptors: ResponseInterceptor[] = []

  constructor(config?: {
    baseURL?: string
    timeout?: number
    headers?: Record<string, string>
  }) {
    this.baseURL = config?.baseURL || RequestExtend.defaultBaseURL
    this.timeout = config?.timeout || RequestExtend.defaultTimeout
    this.headers = {
      ...RequestExtend.defaultHeaders,
      ...config?.headers
    }
  }

  static setDefaultConfig(config: {
    baseURL?: string
    timeout?: number
    headers?: Record<string, string>
  }) {
    if (config.baseURL) {
      this.defaultBaseURL = config.baseURL
    }

    if (config.timeout) {
      this.defaultTimeout = config.timeout
    }

    if (config.headers) {
      this.defaultHeaders = { ...this.defaultHeaders, ...config.headers }
    }
  }

  static addRequestInterceptor(interceptor: RequestInterceptor) {
    this.requestInterceptors.push(interceptor)
  }

  static addResponseInterceptor(interceptor: ResponseInterceptor) {
    this.responseInterceptors.push(interceptor)
  }

  private async executeRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {
    let result = config

    for (const interceptor of RequestExtend.requestInterceptors) {
      try {
        if (interceptor.onFulfilled) {
          result = await interceptor.onFulfilled(result)
        }
      } catch (error) {
        if (interceptor.onRejected) {
          result = await interceptor.onRejected(error)
        } else {
          throw error
        }
      }
    }

    return result
  }

  private async executeResponseInterceptors(
    response: ResponseWrapper<unknown>
  ): Promise<ResponseWrapper<unknown>> {
    let result = response

    for (const interceptor of RequestExtend.responseInterceptors) {
      try {
        if (interceptor.onFulfilled) {
          result = await interceptor.onFulfilled(result)
        }
      } catch (error) {
        if (interceptor.onRejected) {
          const interceptedError = await interceptor.onRejected(error)
          throw interceptedError
        }

        throw error
      }
    }

    return result
  }

  private async executeResponseErrorInterceptors(error: unknown): Promise<unknown> {
    let result = error

    for (const interceptor of RequestExtend.responseInterceptors) {
      if (!interceptor.onRejected) {
        continue
      }

      try {
        result = await interceptor.onRejected(result)
      } catch (interceptorError) {
        result = interceptorError
      }
    }

    return result
  }

  private buildURL(url: string, params?: Record<string, unknown>): string {
    let fullURL = url

    if (!url.startsWith('http://') && !url.startsWith('https://')) {
      fullURL = this.baseURL + url
    }

    if (params && Object.keys(params).length > 0) {
      const searchParams = new URLSearchParams()

      for (const key in params) {
        const value = params[key]
        if (value !== undefined && value !== null) {
          searchParams.append(key, String(value))
        }
      }

      const queryString = searchParams.toString()
      if (queryString) {
        fullURL += (fullURL.includes('?') ? '&' : '?') + queryString
      }
    }

    return fullURL
  }

  private async parseResponseBody(response: Response): Promise<unknown> {
    const contentType = response.headers.get('content-type') || ''

    if (contentType.includes('application/json')) {
      return response.json()
    }

    if (contentType.includes('application/octet-stream')) {
      return response.blob()
    }

    const text = await response.text()
    if (!text) {
      return text
    }

    try {
      return JSON.parse(text)
    } catch {
      return text
    }
  }

  private getErrorMessage(result: unknown, fallback: string): string {
    if (typeof result === 'string' && result.trim()) {
      return result
    }

    if (result && typeof result === 'object') {
      const payload = result as { msg?: string; message?: string }
      return payload.msg || payload.message || fallback
    }

    return fallback
  }

  private async request<T = unknown>(
    method: string,
    url: string,
    options: {
      params?: Record<string, unknown>
      data?: unknown
      headers?: Record<string, string>
    } = {}
  ): Promise<T> {
    const { params, data, headers } = options

    const config: RequestConfig = {
      method: method.toUpperCase(),
      headers: {
        ...this.headers,
        ...headers
      },
      timeout: this.timeout,
      url: params && method.toUpperCase() === 'GET'
        ? this.buildURL(url, params)
        : this.buildURL(url)
    }

    if (data && method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD') {
      if (data instanceof FormData) {
        config.body = data
        delete config.headers['Content-Type']
      } else {
        config.body = JSON.stringify(data)
      }
    }

    const interceptedConfig = await this.executeRequestInterceptors(config)

    try {
      const response = await fetch(interceptedConfig.url, {
        method: interceptedConfig.method,
        headers: interceptedConfig.headers,
        body: interceptedConfig.body,
        signal: interceptedConfig.timeout
          ? AbortSignal.timeout(interceptedConfig.timeout)
          : undefined
      })

      const result = await this.parseResponseBody(response)
      const wrappedResponse: ResponseWrapper<unknown> = {
        data: result,
        status: response.status,
        statusText: response.statusText,
        headers: response.headers
      }

      if (!response.ok) {
        const handledError = await this.executeResponseErrorInterceptors({
          ...wrappedResponse,
          message: this.getErrorMessage(result, response.statusText || 'Request failed')
        })
        throw handledError
      }

      const interceptedResponse = await this.executeResponseInterceptors(wrappedResponse)
      return interceptedResponse.data as T
    } catch (error) {
      if (error && typeof error === 'object' && ('status' in error || 'code' in error)) {
        throw error
      }

      const handledError = await this.executeResponseErrorInterceptors({
        message: error instanceof Error ? error.message : '网络请求失败',
        code:
          error && typeof error === 'object' && 'code' in error
            ? (error as { code?: string }).code || 'NETWORK_ERROR'
            : 'NETWORK_ERROR',
        status:
          error && typeof error === 'object' && 'status' in error
            ? Number((error as { status?: number }).status || 0)
            : 0
      })

      throw handledError
    }
  }

  async get<T = unknown>(url: string, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    return this.request<T>('GET', url, options)
  }

  async post<T = unknown>(url: string, data?: unknown, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    return this.request<T>('POST', url, { ...options, data })
  }

  async put<T = unknown>(url: string, data?: unknown, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    return this.request<T>('PUT', url, { ...options, data })
  }

  async delete<T = unknown>(url: string, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    return this.request<T>('DELETE', url, options)
  }

  async patch<T = unknown>(url: string, data?: unknown, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    return this.request<T>('PATCH', url, { ...options, data })
  }

  async upload<T = unknown>(url: string, file: File | FormData, options?: {
    params?: Record<string, unknown>
    headers?: Record<string, string>
  }): Promise<T> {
    const formData = file instanceof FormData ? file : new FormData()

    if (file instanceof File) {
      formData.append('file', file)
    }

    return this.request<T>('POST', url, {
      ...options,
      data: formData
    })
  }

  async download(url: string, filename?: string): Promise<void> {
    const response = await fetch(this.buildURL(url), {
      method: 'GET',
      headers: this.headers
    })

    if (!response.ok) {
      throw new Error('下载失败')
    }

    const blob = await response.blob()
    const downloadURL = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = downloadURL
    link.download = filename || 'download'
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(downloadURL)
  }
}
