export interface ApiClientConfig {
  baseUrl: string
  accessTokenKey: string
  refreshTokenKey: string
}

export class ApiError extends Error {
  status: number

  constructor(message: string, status: number) {
    super(message)
    this.name = 'ApiError'
    this.status = status
  }
}

export function isAbortError(err: unknown): boolean {
  return err instanceof DOMException && err.name === 'AbortError'
}

function parseErrorMessage(payload: unknown, fallback: string) {
  if (!payload || typeof payload !== 'object') {
    return fallback
  }

  const message = (payload as { message?: string | string[] }).message

  if (Array.isArray(message)) {
    return message.join(', ')
  }

  if (typeof message === 'string') {
    return message
  }

  return fallback
}

export interface ApiClient {
  getAccessToken: () => string | null
  getRefreshToken: () => string | null
  setTokens: (accessToken: string, refreshToken: string) => void
  clearTokens: () => void
  apiRequest: <T>(path: string, options?: ApiRequestOptions) => Promise<T>
}

interface ApiRequestOptions extends Omit<RequestInit, 'body'> {
  body?: unknown
  auth?: boolean
}

export function createApiClient(config: ApiClientConfig): ApiClient {
  const { baseUrl, accessTokenKey, refreshTokenKey } = config

  function getAccessToken() {
    return localStorage.getItem(accessTokenKey)
  }

  function getRefreshToken() {
    return localStorage.getItem(refreshTokenKey)
  }

  function setTokens(accessToken: string, refreshToken: string) {
    localStorage.setItem(accessTokenKey, accessToken)
    localStorage.setItem(refreshTokenKey, refreshToken)
  }

  function clearTokens() {
    localStorage.removeItem(accessTokenKey)
    localStorage.removeItem(refreshTokenKey)
  }

  async function refreshAccessToken() {
    const refreshToken = getRefreshToken()
    if (!refreshToken) {
      return false
    }

    const response = await fetch(`${baseUrl}/auth/refresh`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken }),
    })

    if (!response.ok) {
      clearTokens()
      return false
    }

    const data = await response.json()
    setTokens(data.accessToken, data.refreshToken)
    return true
  }

  async function apiRequest<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {
    const { body, auth = false, headers, ...rest } = options

    const requestHeaders = new Headers(headers)

    if (body !== undefined) {
      requestHeaders.set('Content-Type', 'application/json')
    }

    if (auth) {
      const accessToken = getAccessToken()
      if (accessToken) {
        requestHeaders.set('Authorization', `Bearer ${accessToken}`)
      }
    }

    const response = await fetch(`${baseUrl}${path}`, {
      ...rest,
      headers: requestHeaders,
      body: body !== undefined ? JSON.stringify(body) : undefined,
    })

    if (response.status === 401 && auth) {
      const refreshed = await refreshAccessToken()
      if (refreshed) {
        return apiRequest<T>(path, options)
      }
    }

    const payload = await response.json().catch(() => null)

    if (!response.ok) {
      throw new ApiError(
        parseErrorMessage(payload, `Request failed with status ${response.status}`),
        response.status,
      )
    }

    return payload as T
  }

  return {
    getAccessToken,
    getRefreshToken,
    setTokens,
    clearTokens,
    apiRequest,
  }
}
