const FIVE_MINUTES_MS = 5 * 60 * 1000
const STORAGE_KEY_PREFIX = 'asset-action-rate-limit'

export type AssetRateLimitedAction = 'refresh' | 'start' | 'stop'

export class FrontendRateLimitError extends Error {
  readonly remainingMs: number
  readonly retryAt: number

  constructor(remainingMs: number, retryAt: number) {
    super(`操作过于频繁，请${formatRemainingTime(remainingMs)}后再试`)
    this.name = 'FrontendRateLimitError'
    this.remainingMs = remainingMs
    this.retryAt = retryAt
  }
}

const getStorageKey = (action: AssetRateLimitedAction, identifier: string) =>
  `${STORAGE_KEY_PREFIX}:${action}:${identifier}`

const getTimestamp = (key: string): number => {
  const value = Number(localStorage.getItem(key))
  return Number.isFinite(value) ? value : 0
}

export const formatRemainingTime = (remainingMs: number): string => {
  const totalSeconds = Math.max(1, Math.ceil(remainingMs / 1000))
  const minutes = Math.floor(totalSeconds / 60)
  const seconds = totalSeconds % 60

  if (minutes <= 0) return `${seconds}秒`
  if (seconds === 0) return `${minutes}分钟`
  return `${minutes}分${seconds}秒`
}

export const getAssetActionRateLimitState = (
  action: AssetRateLimitedAction,
  identifier: string,
  now: number = Date.now()
) => {
  const key = getStorageKey(action, identifier)
  const lastCalledAt = getTimestamp(key)
  const retryAt = lastCalledAt + FIVE_MINUTES_MS
  const remainingMs = Math.max(0, retryAt - now)

  if (remainingMs <= 0 && lastCalledAt > 0) {
    localStorage.removeItem(key)
  }

  return {
    limited: remainingMs > 0,
    remainingMs,
    retryAt
  }
}

export const markAssetActionCalled = (
  action: AssetRateLimitedAction,
  identifier: string,
  now: number = Date.now()
) => {
  localStorage.setItem(getStorageKey(action, identifier), String(now))
}

export const assertAssetActionAllowed = (
  action: AssetRateLimitedAction,
  identifier: string,
  now: number = Date.now()
) => {
  const state = getAssetActionRateLimitState(action, identifier, now)
  if (state.limited) {
    throw new FrontendRateLimitError(state.remainingMs, state.retryAt)
  }
}
