import { constants } from 'crypto'
import type { KeyObject, SignKeyObjectInput } from 'crypto'

import getNamedCurve from './get_named_curve.js'
import { JOSENotSupported } from '../../util/errors.js'
import checkModulusLength from './check_modulus_length.js'

const [major, minor] = process.version
  .slice(1)
  .split('.')
  .map((str) => parseInt(str, 10))

const electron = 'electron' in process.versions
const rsaPssParams = !electron && (major >= 17 || (major === 16 && minor >= 9))

const PSS = {
  padding: constants.RSA_PKCS1_PSS_PADDING,
  saltLength: constants.RSA_PSS_SALTLEN_DIGEST,
}

const ecCurveAlgMap = new Map([
  ['ES256', 'P-256'],
  ['ES256K', 'secp256k1'],
  ['ES384', 'P-384'],
  ['ES512', 'P-521'],
])

export default function keyForCrypto(alg: string, key: KeyObject): KeyObject | SignKeyObjectInput {
  switch (alg) {
    case 'EdDSA':
      if (!['ed25519', 'ed448'].includes(key.asymmetricKeyType!)) {
        throw new TypeError(
          'Invalid key for this operation, its asymmetricKeyType must be ed25519 or ed448',
        )
      }

      return key

    case 'RS256':
    case 'RS384':
    case 'RS512':
      if (key.asymmetricKeyType !== 'rsa') {
        throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa')
      }
      checkModulusLength(key, alg)

      return key

    case rsaPssParams && 'PS256':
    case rsaPssParams && 'PS384':
    case rsaPssParams && 'PS512':
      if (key.asymmetricKeyType === 'rsa-pss') {
        const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails!

        const length = parseInt(alg.slice(-3), 10)

        if (
          hashAlgorithm !== undefined &&
          (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm)
        ) {
          throw new TypeError(
            `Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${alg}`,
          )
        }
        if (saltLength !== undefined && saltLength > length >> 3) {
          throw new TypeError(
            `Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${alg}`,
          )
        }
      } else if (key.asymmetricKeyType !== 'rsa') {
        throw new TypeError(
          'Invalid key for this operation, its asymmetricKeyType must be rsa or rsa-pss',
        )
      }
      checkModulusLength(key, alg)

      return { key, ...PSS }

    case !rsaPssParams && 'PS256':
    case !rsaPssParams && 'PS384':
    case !rsaPssParams && 'PS512':
      if (key.asymmetricKeyType !== 'rsa') {
        throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be rsa')
      }
      checkModulusLength(key, alg)

      return { key, ...PSS }

    case 'ES256':
    case 'ES256K':
    case 'ES384':
    case 'ES512': {
      if (key.asymmetricKeyType !== 'ec') {
        throw new TypeError('Invalid key for this operation, its asymmetricKeyType must be ec')
      }

      const actual = getNamedCurve(key)
      const expected = ecCurveAlgMap.get(alg)
      if (actual !== expected) {
        throw new TypeError(
          `Invalid key curve for the algorithm, its curve must be ${expected}, got ${actual}`,
        )
      }

      return { dsaEncoding: 'ieee-p1363', key }
    }
    default:
      throw new JOSENotSupported(
        `alg ${alg} is not supported either by JOSE or your javascript runtime`,
      )
  }
}
