import axios from 'axios'
import qs from 'qs'
import { useAuthStore } from 'stores/authStore'
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
import { slog } from 'src/utils/slog'
import { formatDataSourceLabel } from 'src/utils/formatters'

const rawBaseUrl =
  (typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'

export const API_BASE_URL = String(rawBaseUrl).trim().replace(/\/+$/, '')
const AUTH_REFRESH_PATH = '/auth/refresh'
const LOCALE_STORAGE_KEY = 'bss.locale'

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

function isPublicPath(url) {
  return (
    url.startsWith('/auth/login') ||
    url.startsWith(AUTH_REFRESH_PATH) ||
    url.startsWith('/password/forgot') ||
    url.startsWith('/password/reset')
  )
}

function extractToken(payload) {
  return (
    payload?.token ||
    payload?.access_token ||
    payload?.data?.token ||
    payload?.data?.access_token ||
    ''
  )
}

function isHtmlLike(value) {
  const text = String(value || '').trim().toLowerCase()
  return text.startsWith('<!doctype html') || text.startsWith('<html')
}

function sanitizeApiErrorDetail(detail, status) {
  const normalized = String(detail || '').trim()

  if (!normalized) {
    if (status === 504) {
      return 'Gateway timeout: origin server did not respond in time.'
    }
    return ''
  }

  if (isHtmlLike(normalized)) {
    if (status === 504) {
      return 'Gateway timeout: origin server did not respond in time.'
    }
    if (status >= 500) {
      return `Upstream server error (${status}).`
    }
    return `Unexpected HTML error response (${status || '-'})`
  }

  const compact = normalizeTechnicalSourceLabels(normalized.replace(/\s+/g, ' ').trim())
  if (compact.length > 320) {
    return `${compact.slice(0, 320)}...`
  }

  return compact
}

function normalizeTechnicalSourceLabels(text) {
  return String(text || '')
    .replace(/\bPostgreSQL\b/gi, formatDataSourceLabel('postgresql'))
    .replace(/\bPostgres\b/gi, formatDataSourceLabel('postgres'))
    .replace(/\bpostgre\b/gi, formatDataSourceLabel('postgre'))
    .replace(/\bPG\b/g, formatDataSourceLabel('pg'))
    .replace(/\bMSSQL\b/gi, formatDataSourceLabel('mssql'))
    .replace(/\bBAGGI_V3\b/gi, formatDataSourceLabel('BAGGI_V3'))
    .replace(/\bURETIM\b/g, formatDataSourceLabel('URETIM'))
}

function redirectToLogin() {
  if (typeof window === 'undefined') return
  if (window.location.hash === '#/login') return
  window.location.hash = '/login'
}

function getRequestLocale() {
  if (typeof window === 'undefined') return DEFAULT_LOCALE
  return normalizeLocale(window.localStorage.getItem(LOCALE_STORAGE_KEY))
}

function extractTraceIdFromConfig(config) {
  const rawTraceId =
    config?.params?.trace_id ||
    config?.headers?.['X-Trace-ID'] ||
    config?.headers?.['x-trace-id'] ||
    ''

  return String(rawTraceId || '').trim()
}

function shouldStructuredLogRequest(config) {
  return extractTraceIdFromConfig(config) !== ''
}

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

  if (!isPublicPath(url) && auth?.token) {
    config.headers ||= {}
    config.headers.Authorization = `Bearer ${auth.token}`
  }
  config.headers ||= {}
  config.headers['Accept-Language'] = getRequestLocale()

  const traceId = extractTraceIdFromConfig(config)
  config.__traceId = traceId
  config.__startedAt = Date.now()
  if (traceId) {
    config.headers['X-Trace-ID'] = traceId
  }

  if (shouldStructuredLogRequest(config)) {
    slog.info('production-product-costing.api', 'request:start', {
      trace_id: traceId,
      method: String(config.method || 'GET').toUpperCase(),
      url,
      params: config.params || {},
      timeout_ms: typeof config.timeout === 'number' ? config.timeout : null
    })
  }

  return config
})

let isLoggingOut = false
let refreshPromise = null

async function refreshAccessToken() {
  if (refreshPromise) {
    return refreshPromise
  }

  refreshPromise = (async () => {
    const auth = useAuthStore()
    const response = await api.post(
      AUTH_REFRESH_PATH,
      {},
      {
        skipAuthRefresh: true,
        skipAutoLogout: true
      }
    )

    const rawToken = extractToken(response?.data)
    const token = typeof rawToken === 'string' ? rawToken.trim() : ''
    const isJwt = token.split('.').length === 3

    if (!token || !isJwt) {
      throw new Error('Invalid refresh token response')
    }

    auth.setSession({
      token,
      user: auth.user || null
    })

    return token
  })().finally(() => {
    refreshPromise = null
  })

  return refreshPromise
}

function clearSessionAndRedirect() {
  if (isLoggingOut) return

  isLoggingOut = true
  try {
    useAuthStore().clearSession()
  } finally {
    isLoggingOut = false
    redirectToLogin()
  }
}

api.interceptors.response.use(
  (response) => {
    const requestConfig = response?.config || {}
    if (shouldStructuredLogRequest(requestConfig)) {
      slog.info('production-product-costing.api', 'request:ok', {
        trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
        method: String(requestConfig.method || 'GET').toUpperCase(),
        url: String(requestConfig.url || ''),
        status: response?.status || 200,
        duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now()))
      })
    }
    return response
  },
  async (error) => {
    const requestConfig = error?.config || {}
    const status = error?.response?.status
    const requestUrl = String(requestConfig.url || '')
    const hasBlob = typeof Blob !== 'undefined' && error?.response?.data instanceof Blob
    const isPasswordChangeRequest =
      requestUrl.startsWith('/password/change') ||
      requestUrl.startsWith('/me/password')
    const isPublicRequest = isPublicPath(requestUrl)

    normalizeErrorResponsePayload(error)

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

    if (shouldStructuredLogRequest(requestConfig)) {
      const detail = sanitizeApiErrorDetail(
        await extractApiErrorDetail(error),
        status
      )
      error.parsedMessage ||= detail
      slog.error('production-product-costing.api', 'request:error', {
        trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
        method: String(requestConfig.method || 'GET').toUpperCase(),
        url: requestUrl,
        status: status || 0,
        duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now())),
        detail
      })
    }

    const shouldTryRefresh =
      status === 401 &&
      !requestConfig._retry &&
      !requestConfig.skipAuthRefresh &&
      !isPasswordChangeRequest &&
      !isPublicRequest

    if (shouldTryRefresh) {
      requestConfig._retry = true
      try {
        const newToken = await refreshAccessToken()
        requestConfig.headers ||= {}
        requestConfig.headers.Authorization = `Bearer ${newToken}`
        return api(requestConfig)
      } catch {
        // fall through to logout
      }
    }

    // Password change endpoints may return 401 for business reasons
    // (for example current password mismatch). Keep session in that case.
    if (
      status === 401 &&
      !isPasswordChangeRequest &&
      !requestConfig.skipAutoLogout
    ) {
      clearSessionAndRedirect()
    }

    return Promise.reject(error)
  }
)

function normalizeErrorResponsePayload(error) {
  const data = error?.response?.data
  if (!data || typeof Blob !== 'undefined' && data instanceof Blob) return
  if (typeof data === 'string') {
    error.response.data = normalizeTechnicalSourceLabels(data)
    return
  }
  if (typeof data === 'object') {
    for (const key of ['detail', 'message', 'error']) {
      if (typeof data[key] === 'string') {
        data[key] = normalizeTechnicalSourceLabels(data[key])
      }
    }
  }
}

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) {
  const status = err?.response?.status
  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 sanitizeApiErrorDetail(detail, status)
}

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
