import type { IncomingHttpHeaders } from 'node:http'
import { snakeCase } from 'lodash'
import z from 'zod'

import { IS_PLATFORM } from '@/lib/constants'

/**
 * Construct headers for api request.
 * For platform, it will include apiKey into the provided headers.
 *
 * To prevent relay frontend request headers like useragent, referrer... into the middleware requests.
 * We will only keep the header keys that are in this list: Accept, Authorization, Content-Type, x-connection-encrypted
 */
export function constructHeaders(headers: { [prop: string]: any }) {
  if (headers) {
    const cleansedHeaders = {
      Accept: headers.Accept,
      Authorization: headers.Authorization,
      cookie: headers.cookie,
      'Content-Type': headers['Content-Type'],
      'x-connection-encrypted': headers['x-connection-encrypted'],
    } as any
    // clean up key with underfined value
    Object.keys(cleansedHeaders).forEach((key) =>
      cleansedHeaders[key] === undefined ? delete cleansedHeaders[key] : {}
    )
    return {
      ...cleansedHeaders,
      // [Joshen] JFYI both Alaister and I checked on this and realised this might not be used actually
      // Could be safe to remove but leaving it here for now
      ...(!IS_PLATFORM && { apiKey: `${process.env.BRIVEN_SERVICE_KEY}` }),
    }
  } else {
    return {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    }
  }
}

// Typically for HTTP payloads
// @ts-ignore
export const toSnakeCase = (object) => {
  const snakeCaseObject = {}
  const snakeCaseArray = []

  if (!object) return null

  if (Array.isArray(object)) {
    for (const item of object) {
      if (typeof item === 'object') {
        snakeCaseArray.push(toSnakeCase(item))
      } else {
        snakeCaseArray.push(item)
      }
    }
    return snakeCaseArray
  } else if (typeof object === 'object') {
    for (const key of Object.keys(object)) {
      if (typeof object[key] === 'object') {
        // @ts-ignore
        snakeCaseObject[snakeCase(key)] = toSnakeCase(object[key])
      } else {
        // @ts-ignore
        snakeCaseObject[snakeCase(key)] = object[key]
      }
    }
    return snakeCaseObject
  } else {
    return object
  }
}

/**
 * Converts Node.js `IncomingHttpHeaders` to Fetch API `Headers`.
 */
export function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {
  const headers = new Headers()
  for (const [key, value] of Object.entries(nodeHeaders)) {
    if (Array.isArray(value)) {
      value.forEach((v) => headers.append(key, v))
    } else if (value !== undefined) {
      headers.append(key, value)
    }
  }
  return headers
}

/**
 * Zod transformer to parse boolean values from strings.
 *
 * Use when accepting a boolean value in a query parameter.
 */
export function zBooleanString(errorMsg?: string) {
  return z.string().transform((value, ctx) => {
    if (value === 'true') {
      return true
    }

    if (value === 'false') {
      return false
    }

    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: errorMsg || 'must be a boolean string',
    })
    return z.NEVER
  })
}

/**
 * Transform a comma-separated string into an array of strings.
 *
 * Use when accepting a list of values in a query parameter.
 */
export function commaSeparatedStringIntoArray(value: string): string[] {
  return value
    .split(',')
    .map((v) => v.trim())
    .filter(Boolean)
}

export class InternalServerError extends Error {
  constructor(
    message: string,
    public details?: Record<string, unknown>
  ) {
    super(message)
    this.name = 'InternalServerError'
  }
}
