import Taro from '@tarojs/taro'
import { BaseUrl, TenantId } from '@/config/app'
import { clearStorageByLoginUser } from '@/utils/server'

export interface RequestConfig {
  url: string
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
  data?: any
  header?: Record<string, string>
  timeout?: number
  retry?: number
  showLoading?: boolean
  showError?: boolean
  returnRaw?: boolean
}

interface ApiResponse<T = any> {
  code: number
  message?: string
  data?: T
}

export enum ErrorType {
  NETWORK_ERROR = 'NETWORK_ERROR',
  TIMEOUT_ERROR = 'TIMEOUT_ERROR',
  BUSINESS_ERROR = 'BUSINESS_ERROR',
  AUTH_ERROR = 'AUTH_ERROR',
  UNKNOWN_ERROR = 'UNKNOWN_ERROR',
}

export class RequestError extends Error {
  public type: ErrorType
  public code?: number
  public data?: any

  constructor(message: string, type: ErrorType, code?: number, data?: any) {
    super(message)
    this.name = 'RequestError'
    this.type = type
    this.code = code
    this.data = data
  }
}

const DEFAULT_CONFIG: Required<Pick<RequestConfig, 'timeout' | 'retry' | 'showLoading' | 'showError' | 'returnRaw'>> = {
  timeout: 10000,
  retry: 2,
  showLoading: false,
  showError: true,
  returnRaw: true,
}

const baseUrl = BaseUrl

const requestInterceptor = (config: RequestConfig): RequestConfig => {
  const token = Taro.getStorageSync('access_token')
  const tenantId = Taro.getStorageSync('TenantId') || TenantId

  const defaultHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    TenantId: String(tenantId),
  }

  if (token) {
    defaultHeaders.Authorization = token
  }

  config.header = { ...defaultHeaders, ...config.header }

  if (config.showLoading) {
    Taro.showLoading({ title: '加载中...' })
  }

  return config
}

// Redis 临时故障关键词 - 这类 401 是服务端问题，不应清除本地 token
const REDIS_ERROR_KEYWORDS = ['RedisSystemException', 'RedisException', 'Operation timed out', 'Redis']

const isRedisError = (message?: string): boolean => {
  if (!message) return false
  return REDIS_ERROR_KEYWORDS.some((keyword) => message.includes(keyword))
}

// 防抖：避免同一时间多个接口都触发跳登录页
let authErrorTimer: ReturnType<typeof setTimeout> | null = null

const handleAuthError = (message?: string) => {
  // Redis 临时故障导致的 401，不清除 token，静默处理
  if (isRedisError(message)) {
    console.warn('[Auth] Redis 临时故障导致 401，忽略处理:', message)
    return
  }

  clearStorageByLoginUser()

  // 防抖：多个接口同时 401 只弹一次 Toast
  if (authErrorTimer) return
  authErrorTimer = setTimeout(() => {
    authErrorTimer = null
  }, 3000)

  Taro.showToast({
    title: '登录已过期，请重新登录',
    icon: 'none',
    duration: 2000,
  })
}

const responseInterceptor = <T>(response: any, config: RequestConfig): T => {
  if (config.showLoading) {
    Taro.hideLoading()
  }

  const { statusCode, data } = response

  if (statusCode !== 200) {
    throw new RequestError(`HTTP错误: ${statusCode}`, ErrorType.NETWORK_ERROR, statusCode, data)
  }

  if (data === null || data === undefined) {
    throw new RequestError('响应数据为空', ErrorType.NETWORK_ERROR, statusCode, data)
  }

  if (typeof data === 'object' && data !== null && 'code' in data) {
    const apiResponse = data as ApiResponse<T>

    if (apiResponse.code === 0) {
      return (config.returnRaw ? data : apiResponse.data) as T
    }

    if (apiResponse.code === 401 || apiResponse.code === 403) {
      handleAuthError(apiResponse.message)
      throw new RequestError(apiResponse.message || '认证失败', ErrorType.AUTH_ERROR, apiResponse.code, apiResponse.data)
    }

    throw new RequestError(apiResponse.message || '请求失败', ErrorType.BUSINESS_ERROR, apiResponse.code, apiResponse.data)
  }

  return data as T
}

const handleError = (error: RequestError, config: RequestConfig) => {
  if (config.showLoading) {
    Taro.hideLoading()
  }

  if (!config.showError) return

  const titleMap: Record<ErrorType, string> = {
    [ErrorType.NETWORK_ERROR]: '网络连接失败',
    [ErrorType.TIMEOUT_ERROR]: '请求超时',
    [ErrorType.BUSINESS_ERROR]: error.message || '操作失败',
    [ErrorType.AUTH_ERROR]: '认证失败',
    [ErrorType.UNKNOWN_ERROR]: '未知错误',
  }

  Taro.showToast({
    title: titleMap[error.type] || '请求失败',
    icon: 'none',
    duration: 2000,
  })
}

const executeRequest = <T>(config: RequestConfig): Promise<T> =>
  new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new RequestError('请求超时', ErrorType.TIMEOUT_ERROR))
    }, config.timeout || DEFAULT_CONFIG.timeout)

    Taro.request({
      url: config.url,
      method: config.method || 'GET',
      data: config.data || {},
      header: config.header || {},
      success: (res) => {
        clearTimeout(timer)
        try {
          resolve(responseInterceptor<T>(res, config))
        } catch (error) {
          reject(error)
        }
      },
      fail: (err) => {
        clearTimeout(timer)
        reject(new RequestError(err.errMsg || '网络请求失败', ErrorType.NETWORK_ERROR, undefined, err))
      },
    })
  })

const retryRequest = async <T>(config: RequestConfig, retryCount = 0): Promise<T> => {
  try {
    return await executeRequest<T>(config)
  } catch (error) {
    const requestError = error as RequestError
    if (requestError.type === ErrorType.AUTH_ERROR || requestError.type === ErrorType.BUSINESS_ERROR) {
      throw error
    }
    if (retryCount < (config.retry || DEFAULT_CONFIG.retry)) {
      await new Promise((resolve) => setTimeout(resolve, 1000 * (retryCount + 1)))
      return retryRequest<T>(config, retryCount + 1)
    }
    throw error
  }
}

export async function request<T>(options: RequestConfig): Promise<T> {
  try {
    const config = requestInterceptor({ ...DEFAULT_CONFIG, ...options })
    return await retryRequest<T>(config)
  } catch (error) {
    const requestError = error as RequestError
    handleError(requestError, options)
    throw requestError
  }
}

const buildUrl = (url: string): string => {
  if (url.startsWith('http://') || url.startsWith('https://')) {
    return url
  }
  return `${baseUrl}${url}`
}

const buildQueryString = (params: Record<string, any>): string => {
  const queryString = Object.keys(params)
    .filter((key) => params[key] !== undefined && params[key] !== null)
    .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
    .join('&')
  return queryString ? `?${queryString}` : ''
}

export function get<T>(url: string, params?: any, config?: Partial<RequestConfig>): Promise<T> {
  const fullUrl = buildUrl(url) + (params ? buildQueryString(params) : '')
  return request<T>({ url: fullUrl, method: 'GET', returnRaw: true, ...config })
}

export function post<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'POST', data, returnRaw: true, ...config })
}

export function put<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'PUT', data, returnRaw: true, ...config })
}

export function patch<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'PATCH', data, returnRaw: true, ...config })
}

export function del<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'DELETE', data, returnRaw: true, ...config })
}

export function getData<T>(url: string, params?: any, config?: Partial<RequestConfig>): Promise<T> {
  const fullUrl = buildUrl(url) + (params ? buildQueryString(params) : '')
  return request<T>({ url: fullUrl, method: 'GET', returnRaw: false, ...config })
}

export function postData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'POST', data, returnRaw: false, ...config })
}

export function putData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'PUT', data, returnRaw: false, ...config })
}

export function delData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
  return request<T>({ url: buildUrl(url), method: 'DELETE', data, returnRaw: false, ...config })
}

export default {
  request,
  get,
  post,
  put,
  patch,
  del,
  getData,
  postData,
  putData,
  delData,
  ErrorType,
  RequestError,
}
