import type { TRPCError } from '../error/TRPCError';
import type { TRPC_ERROR_CODES_BY_KEY, TRPCResponse } from '../rpc';
import { TRPC_ERROR_CODES_BY_NUMBER } from '../rpc';
import type { InvertKeyValue, ValueOf } from '../types';
import { isObject } from '../utils';

export const JSONRPC2_TO_HTTP_CODE: Record<
  keyof typeof TRPC_ERROR_CODES_BY_KEY,
  number
> = {
  PARSE_ERROR: 400,
  BAD_REQUEST: 400,
  UNAUTHORIZED: 401,
  PAYMENT_REQUIRED: 402,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  METHOD_NOT_SUPPORTED: 405,
  TIMEOUT: 408,
  CONFLICT: 409,
  PRECONDITION_FAILED: 412,
  PAYLOAD_TOO_LARGE: 413,
  UNSUPPORTED_MEDIA_TYPE: 415,
  UNPROCESSABLE_CONTENT: 422,
  PRECONDITION_REQUIRED: 428,
  TOO_MANY_REQUESTS: 429,
  CLIENT_CLOSED_REQUEST: 499,
  INTERNAL_SERVER_ERROR: 500,
  NOT_IMPLEMENTED: 501,
  BAD_GATEWAY: 502,
  SERVICE_UNAVAILABLE: 503,
  GATEWAY_TIMEOUT: 504,
};

export const HTTP_CODE_TO_JSONRPC2: InvertKeyValue<
  typeof JSONRPC2_TO_HTTP_CODE
> = {
  400: 'BAD_REQUEST',
  401: 'UNAUTHORIZED',
  402: 'PAYMENT_REQUIRED',
  403: 'FORBIDDEN',
  404: 'NOT_FOUND',
  405: 'METHOD_NOT_SUPPORTED',
  408: 'TIMEOUT',
  409: 'CONFLICT',
  412: 'PRECONDITION_FAILED',
  413: 'PAYLOAD_TOO_LARGE',
  415: 'UNSUPPORTED_MEDIA_TYPE',
  422: 'UNPROCESSABLE_CONTENT',
  428: 'PRECONDITION_REQUIRED',
  429: 'TOO_MANY_REQUESTS',
  499: 'CLIENT_CLOSED_REQUEST',
  500: 'INTERNAL_SERVER_ERROR',
  501: 'NOT_IMPLEMENTED',
  502: 'BAD_GATEWAY',
  503: 'SERVICE_UNAVAILABLE',
  504: 'GATEWAY_TIMEOUT',
} as const;

export function getStatusCodeFromKey(
  code: keyof typeof TRPC_ERROR_CODES_BY_KEY,
) {
  return JSONRPC2_TO_HTTP_CODE[code] ?? 500;
}

export function getStatusKeyFromCode(
  code: keyof typeof HTTP_CODE_TO_JSONRPC2,
): ValueOf<typeof HTTP_CODE_TO_JSONRPC2> {
  return HTTP_CODE_TO_JSONRPC2[code] ?? 'INTERNAL_SERVER_ERROR';
}

/**
 * The HTTP status for a response, or for a batch of them: `207` when the
 * responses' statuses differ.
 * @see https://trpc.io/docs/rpc#error-codes---http-status
 */
export function getHTTPStatusCode(
  json: TRPCResponse | TRPCResponse[],
  errors?: readonly (TRPCError | undefined)[],
) {
  const arr = Array.isArray(json) ? json : [json];
  const httpStatuses = new Set<number>(
    arr.map((res, index) => {
      if ('error' in res) {
        if (
          isObject(res.error.data) &&
          typeof res.error.data['httpStatus'] === 'number'
        ) {
          return res.error.data['httpStatus'];
        }
        // several error codes share `-32603`, so the formatted code cannot
        // recover the original status when a formatter strips `data`
        const original = errors?.[index];
        if (original) {
          return getStatusCodeFromKey(original.code);
        }
        const code = TRPC_ERROR_CODES_BY_NUMBER[res.error.code];
        return getStatusCodeFromKey(code);
      }
      return 200;
    }),
  );

  if (httpStatuses.size !== 1) {
    return 207;
  }

  const httpStatus = httpStatuses.values().next().value;

  // oxlint-disable-next-line typescript/no-non-null-assertion
  return httpStatus!;
}

/**
 * The HTTP status of a `TRPCError`'s code.
 * @see https://trpc.io/docs/rpc#error-codes---http-status
 */
export function getHTTPStatusCodeFromError(error: TRPCError) {
  return getStatusCodeFromKey(error.code);
}
