const axios = require('axios')
const util = require('util')

class BildError extends Error {
  constructor(message, code) {
    super(message)
    this.name = 'BildError'
    this.code = code
  }
}

class AuthenticationError extends BildError {
  constructor(message) {
    super(message, 'AUTH_ERROR')
    this.name = 'AuthenticationError'
  }
}

class ValidationError extends BildError {
  constructor(message) {
    super(message, 'VALIDATION_ERROR')
    this.name = 'ValidationError'
  }
}

class APIError extends BildError {
  /**
   * @param {string} message
   * @param {number} status
   * @param {unknown} data
   * @param {{ requestUrl?: string, responseHeaders?: Record<string, unknown> }} [meta]
   */
  constructor(message, status, data, meta) {
    super(message, 'API_ERROR')
    this.name = 'APIError'
    this.status = status
    this.data = data
    if (meta && typeof meta === 'object') {
      if (meta.requestUrl) this.requestUrl = meta.requestUrl
      if (meta.responseHeaders) this.responseHeaders = meta.responseHeaders
    }
  }
}

/**
 * CMS responses sometimes surface ID-token verify errors (e.g. expired token, malformed JWT)
 * with HTTP 4xx/5xx and long third-party doc links. Replace with short CLI-facing messages.
 * @param {string} message
 * @param {object} [data] - parsed JSON error body from axios response
 * @returns {string}
 */
function enhanceFirebaseAuthUserMessage(message, data) {
  const msg = String(message || '')
  const code =
    (data && (data.code || data.error?.code)) != null
      ? String(data.code || data.error?.code)
      : ''
  const looksInvalidIdTokenFormat = /Decoding Firebase ID token failed/i.test(msg)
  if (looksInvalidIdTokenFormat && !/\bbild login\b/i.test(msg)) {
    return 'Could not decode the ID token. Make sure you passed the complete JWT string from your sign-in flow.'
  }
  const looksExpired =
    /id-token-expired/i.test(code) ||
    /id-token-expired/i.test(msg) ||
    /Firebase ID token has expired/i.test(msg) ||
    (/ID token has expired/i.test(msg) && /firebase/i.test(msg))
  const looksRevoked =
    /id-token-revoked/i.test(code) ||
    /id-token-revoked/i.test(msg) ||
    /Firebase ID token has been revoked/i.test(msg)

  if (!looksExpired && !looksRevoked) {
    return msg
  }
  if (/\bbild login\b/i.test(msg)) {
    return msg
  }
  if (looksExpired) {
    return 'Your authentication token has expired. Please run "bild login" again.'
  }
  return 'Your authentication token was revoked. Please run "bild login" again.'
}

/**
 * @param {import('axios').AxiosError} axError
 * @returns {{ requestUrl?: string, responseHeaders?: Record<string, unknown> }|undefined}
 */
function collectAxiosErrorMeta(axError) {
  if (!axError || !axError.response) return undefined
  const res = axError.response
  const cfg = axError.config
  let requestUrl
  if (cfg) {
    try {
      requestUrl = axios.getUri(cfg)
    } catch {
      const base = String(cfg.baseURL || '').replace(/\/$/, '')
      const u = String(cfg.url || '')
      requestUrl = `${base}${u.startsWith('/') ? '' : '/'}${u}`
    }
  }
  const responseHeaders = res.headers && typeof res.headers === 'object' ? { ...res.headers } : undefined
  return { requestUrl, responseHeaders }
}

/**
 * @param {unknown} data
 * @returns {string}
 */
function formatHttpResponseBodyForLog(data) {
  if (data === undefined) return '(no body)'
  if (data === null) return 'null'
  if (typeof data === 'string') return data
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) return data.toString('utf8')
  try {
    return JSON.stringify(data, null, 2)
  } catch {
    return util.inspect(data, { depth: 12, maxArrayLength: 100 })
  }
}

/**
 * Print status, request URL, response headers, and body to stderr (APIError or raw axios error).
 * @param {unknown} error
 * @param {{ error?: (...args: unknown[]) => void }} [io]
 */
function logHttpErrorDetails(error, io = console) {
  const log = typeof io.error === 'function' ? io.error.bind(io) : console.error

  if (error && error.name === 'APIError') {
    log(`HTTP status: ${error.status}`)
    if (error.requestUrl) log(`Request URL: ${error.requestUrl}`)
    if (error.responseHeaders && Object.keys(error.responseHeaders).length > 0) {
      try {
        log(`Response headers:\n${JSON.stringify(error.responseHeaders, null, 2)}`)
      } catch {
        log(`Response headers: ${util.inspect(error.responseHeaders)}`)
      }
    }
    log(`Response body:\n${formatHttpResponseBodyForLog(error.data)}`)
    return
  }

  if (error && error.response) {
    const { status, data, headers } = error.response
    log(`HTTP status: ${status}`)
    const cfg = error.config
    if (cfg) {
      try {
        log(`Request URL: ${axios.getUri(cfg)}`)
      } catch {
        /* ignore */
      }
    }
    if (headers && typeof headers === 'object' && Object.keys(headers).length > 0) {
      try {
        log(`Response headers:\n${JSON.stringify({ ...headers }, null, 2)}`)
      } catch {
        log(`Response headers: ${util.inspect(headers)}`)
      }
    }
    log(`Response body:\n${formatHttpResponseBodyForLog(data)}`)
  }
}

module.exports = {
  BildError,
  AuthenticationError,
  ValidationError,
  APIError,
  enhanceFirebaseAuthUserMessage,
  collectAxiosErrorMeta,
  formatHttpResponseBodyForLog,
  logHttpErrorDetails,
}

