import axios from 'axios'
import qs from 'qs'
import { useAuthStore } from 'stores/authStore'

export const API_BASE_URL = '/api'

const api = axios.create({
  baseURL: API_BASE_URL,
  timeout: 180000,
  paramsSerializer: params =>
    qs.stringify(params, { arrayFormat: 'repeat' }),
  withCredentials: true
})

api.interceptors.request.use((config) => {
  const auth = useAuthStore()
  const url = config.url || ''

  const isPublic =
    url.startsWith('/auth/login') ||
    url.startsWith('/auth/refresh') ||
    url.startsWith('/password/forgot') ||
    url.startsWith('/password/reset')

  if (!isPublic && auth?.token) {
    config.headers ||= {}
    config.headers.Authorization = `Bearer ${auth.token}`
  }

  return config
})

let isLoggingOut = false

api.interceptors.response.use(
  r => r,
  async (error) => {
    const status = error?.response?.status
    const requestUrl = String(error?.config?.url || '')
    const hasBlob = typeof Blob !== 'undefined' && error?.response?.data instanceof Blob
    const isPasswordChangeRequest =
      requestUrl.startsWith('/password/change') ||
      requestUrl.startsWith('/me/password')

    if ((status >= 500 || hasBlob) && error) {
      const method = String(error?.config?.method || 'GET').toUpperCase()
      const detail = await extractApiErrorDetail(error)
      error.parsedMessage = detail
      console.error(`API ${status || '-'} ${method} ${requestUrl}: ${detail}`)
    }

    // Password change endpoints may return 401 for business reasons
    // (for example current password mismatch). Keep session in that case.
    if (status === 401 && !isPasswordChangeRequest && !isLoggingOut) {
      isLoggingOut = true
      try {
        useAuthStore().clearSession()
      } finally {
        isLoggingOut = false
      }
    }

    return Promise.reject(error)
  }
)

export const get = (u, p = {}, c = {}) =>
  api.get(u, { params: p, ...c }).then(r => r.data)

export const post = (u, b = {}, c = {}) =>
  api.post(u, b, c).then(r => r.data)

export const put = (u, b = {}, c = {}) =>
  api.put(u, b, c).then(r => r.data)

export const del = (u, p = {}, c = {}) =>
  api.delete(u, { params: p, ...c }).then(r => r.data)

async function parseBlobErrorMessage(data) {
  if (!data) return ''

  if (typeof Blob !== 'undefined' && data instanceof Blob) {
    try {
      const text = (await data.text())?.trim()
      if (!text) return ''

      try {
        const json = JSON.parse(text)
        return (
          json?.detail ||
          json?.message ||
          json?.error ||
          text
        )
      } catch {
        return text
      }
    } catch {
      return ''
    }
  }

  if (typeof data === 'string') return data.trim()
  if (typeof data === 'object') {
    return (
      data?.detail ||
      data?.message ||
      data?.error ||
      ''
    )
  }

  return ''
}

export async function extractApiErrorDetail(err) {
  let detail =
    err?.parsedMessage ||
    err?.response?.data?.detail ||
    err?.response?.data?.message ||
    err?.response?.data?.error ||
    ''

  if (!detail) {
    detail = await parseBlobErrorMessage(err?.response?.data)
  }

  if (!detail) {
    detail = err?.message || 'Request failed'
  }

  return detail
}

export const download = async (u, p = {}, c = {}) => {
  try {
    const r = await api.get(u, { params: p, responseType: 'blob', ...c })
    return r.data
  } catch (err) {
    const detail = await extractApiErrorDetail(err)

    const wrapped = new Error(detail)
    wrapped.status = err?.response?.status
    wrapped.original = err
    throw wrapped
  }
}

export default api
