import debugIt from 'debug'
import type {Middleware} from 'get-it'

const SENSITIVE_HEADERS = ['cookie', 'authorization']

const hasOwn = Object.prototype.hasOwnProperty
const redactKeys = (source: any, redacted: any) => {
  const target: any = {}
  for (const key in source) {
    if (hasOwn.call(source, key)) {
      target[key] = redacted.indexOf(key.toLowerCase()) > -1 ? '<redacted>' : source[key]
    }
  }
  return target
}

/** @public */
export function debug(opts: any = {}) {
  const verbose = opts.verbose
  const namespace = opts.namespace || 'get-it'
  const defaultLogger = debugIt(namespace)
  const log = opts.log || defaultLogger
  const shortCircuit = log === defaultLogger && !debugIt.enabled(namespace)
  let requestId = 0

  return {
    processOptions: (options) => {
      options.debug = log
      options.requestId = options.requestId || ++requestId
      return options
    },

    onRequest: (event) => {
      // Short-circuit if not enabled, to save some CPU cycles with formatting stuff
      if (shortCircuit || !event) {
        return event
      }

      const options = event.options

      log('[%s] HTTP %s %s', options.requestId, options.method, options.url)

      if (verbose && options.body && typeof options.body === 'string') {
        log('[%s] Request body: %s', options.requestId, options.body)
      }

      if (verbose && options.headers) {
        const headers =
          opts.redactSensitiveHeaders === false
            ? options.headers
            : redactKeys(options.headers, SENSITIVE_HEADERS)

        log('[%s] Request headers: %s', options.requestId, JSON.stringify(headers, null, 2))
      }

      return event
    },

    onResponse: (res, context) => {
      // Short-circuit if not enabled, to save some CPU cycles with formatting stuff
      if (shortCircuit || !res) {
        return res
      }

      const reqId = context.options.requestId

      log('[%s] Response code: %s %s', reqId, res.statusCode, res.statusMessage)

      if (verbose && res.body) {
        log('[%s] Response body: %s', reqId, stringifyBody(res))
      }

      return res
    },

    onError: (err, context) => {
      const reqId = context.options.requestId
      if (!err) {
        log('[%s] Error encountered, but handled by an earlier middleware', reqId)
        return err
      }

      log('[%s] ERROR: %s', reqId, err.message)
      return err
    },
  } satisfies Middleware
}

function stringifyBody(res: any) {
  const contentType = (res.headers['content-type'] || '').toLowerCase()
  const isJson = contentType.indexOf('application/json') !== -1
  return isJson ? tryFormat(res.body) : res.body
}

// Attempt pretty-formatting JSON
function tryFormat(body: any) {
  try {
    const parsed = typeof body === 'string' ? JSON.parse(body) : body
    return JSON.stringify(parsed, null, 2)
  } catch {
    return body
  }
}
