import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'

let lastToastKey = ''
let lastToastAt = 0

const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])
const MAX_RETRY_COUNT = 1
const RETRY_BASE_DELAY_MS = 300

function toastErrorOnce(key, message, minIntervalMs = 1500) {
  const now = Date.now()
  if (key === lastToastKey && now - lastToastAt < minIntervalMs) return
  lastToastKey = key
  lastToastAt = now
  ElMessage.error(message)
}

function getCookie(name) {
  const escaped = String(name || '').replace(/([.*+?^${}()|[\]\\])/g, '\\$1')
  const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`))
  return match ? decodeURIComponent(match[1]) : ''
}

function isIdempotentMethod(method) {
  return ['GET', 'HEAD', 'OPTIONS'].includes(String(method || 'GET').toUpperCase())
}

function shouldRetryRequest(error) {
  const config = error?.config
  if (!config || config.__no_retry) return false
  if (!isIdempotentMethod(config.method)) return false

  const retried = Number(config.__retry_count || 0)
  if (retried >= MAX_RETRY_COUNT) return false

  const code = String(error?.code || '')
  if (code === 'ECONNABORTED' || code === 'ERR_NETWORK') return true

  const status = Number(error?.response?.status || 0)
  return RETRYABLE_STATUS.has(status)
}

function delay(ms) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, Math.max(0, Number(ms || 0)))
  })
}

async function retryRequestOnce(error, client) {
  const config = error?.config || {}
  const retried = Number(config.__retry_count || 0)
  config.__retry_count = retried + 1

  const backoffMs = RETRY_BASE_DELAY_MS * (retried + 1)
  await delay(backoffMs)
  return client.request(config)
}

export const api = axios.create({
  baseURL: '/yuyx/api',
  timeout: 30_000,
  withCredentials: true,
})

let reauthPromise = null

async function ensureReauth() {
  if (reauthPromise) return reauthPromise
  reauthPromise = ElMessageBox.prompt('请输入管理员密码进行二次确认', '安全确认', {
    inputType: 'password',
    inputPlaceholder: '管理员密码',
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    inputValidator: (v) => Boolean(String(v || '').trim()),
    inputErrorMessage: '密码不能为空',
  })
    .then(async (res) => {
      const password = String(res.value || '').trim()
      await api.post('/admin/reauth', { password })
      ElMessage.success('已通过安全确认')
    })
    .finally(() => {
      reauthPromise = null
    })
  return reauthPromise
}

api.interceptors.request.use((config) => {
  const method = String(config?.method || 'GET').toUpperCase()
  if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
    const token = getCookie('csrf_token')
    if (token) {
      config.headers = config.headers || {}
      config.headers['X-CSRF-Token'] = token
    }
  }
  return config
})

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const status = error?.response?.status
    const payload = error?.response?.data
    const message = payload?.error || payload?.message || error?.message || '请求失败'
    const silent = Boolean(error?.config?.__silent)

    if (payload?.code === 'reauth_required' && error?.config && !error.config.__reauth_retry) {
      try {
        error.config.__reauth_retry = true
        await ensureReauth()
        return api.request(error.config)
      } catch {
        return Promise.reject(error)
      }
    }

    if (shouldRetryRequest(error)) {
      return retryRequestOnce(error, api)
    }

    if (status === 401) {
      if (!silent) {
        toastErrorOnce('401', message || '登录已过期，请重新登录', 3000)
      }
      const pathname = window.location?.pathname || ''
      if (!pathname.startsWith('/yuyx')) window.location.href = '/yuyx'
    } else if (status === 403) {
      if (!silent) {
        toastErrorOnce('403', message || '需要管理员权限', 5000)
      }
    } else if (status) {
      if (!silent) {
        toastErrorOnce(`http:${status}:${message}`, message)
      }
    } else if (error?.code === 'ECONNABORTED') {
      if (!silent) {
        toastErrorOnce('timeout', '请求超时', 3000)
      }
    } else {
      if (!silent) {
        toastErrorOnce(`net:${message}`, message, 3000)
      }
    }

    return Promise.reject(error)
  },
)
