const aws4 = require('aws4')
const AWS = require('aws-sdk')
const path = require('path')
const os = require('os')
const crypto = require('crypto')
let credentials // lazily loaded, see below
let isAwsCredentials = false

const calculateSHA256 = (body) => {
  if (!body) return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' // SHA256 hash of an empty string
  return crypto.createHash('sha256').update(body).digest('hex')
}

const aws4signer = async (esRequest, parent) => {
  // Consider deprecating - insecure to use on command line and credentials can be found by default at ~/.aws/credentials or as environment variables
  const useAwsCredentials = ((typeof parent.options.awsAccessKeyId === 'string') && (typeof parent.options.awsSecretAccessKey === 'string'))
  // Consider deprecating - can be achieved with awsChain and setting AWS_PROFILE and AWS_CONFIG_FILE environment variables as needed
  const useAwsProfile = (typeof parent.options.awsIniFileProfile === 'string')
  const useAwsChain = (parent.options.awsChain === true)
  const awsUrlRegex = new RegExp(parent.options.awsUrlRegex || /^https?:\/\/.*\.amazonaws\.com.*$/)

  if (!awsUrlRegex.test(esRequest.url) && !awsUrlRegex.test(esRequest.uri)) {
    return
  }

  if (useAwsCredentials || useAwsProfile || useAwsChain) {
    // Lazy load credentials object depending on our flavor of credential loading
    // Assumption is that loading only needs to happen once per execution and if refreshing is
    // needed, credentials object should implement credentials.refresh() callback
    if (!credentials) {
      if (useAwsChain) {
        isAwsCredentials = true
        credentials = await new AWS.CredentialProviderChain().resolvePromise()
      } else if (useAwsCredentials) {
        credentials = {
          accessKeyId: parent.options.awsAccessKeyId,
          secretAccessKey: parent.options.awsSecretAccessKey,
          sessionToken: parent.options.sessionToken
        }
      } else if (useAwsProfile) {
        isAwsCredentials = true
        credentials = new AWS.SharedIniFileCredentials({
          profile: parent.options.awsIniFileProfile,
          filename: path.join(os.homedir(), '.aws', parent.options.awsIniFileName ? parent.options.awsIniFileName : 'config')
        })
      }
    }

    // get aws required stuff from uri or url
    let esURL = ''
    if ((esRequest.uri !== undefined) && (esRequest.uri !== null)) {
      esURL = esRequest.uri
    } else if ((esRequest.url !== undefined) && (esRequest.url !== null)) {
      esURL = esRequest.url
    }

    const urlObj = new URL(esURL)

    if (parent.options.awsService) {
      esRequest.service = parent.options.awsService
    }

    if (parent.options.awsRegion) {
      esRequest.region = parent.options.awsRegion
    }

    // checks if the token needs to be refreshed
    // if it does it's refreshed
    if (isAwsCredentials) {
      await credentials.getPromise()
    }

    esRequest.headers = Object.assign({ host: urlObj.hostname, 'Content-Type': 'application/json' }, esRequest.headers)
    esRequest.path = `${urlObj.pathname}?${urlObj.searchParams.toString()}`

    if (parent.ESDistribution === 'opensearch-serverless') {
      esRequest.headers = Object.assign({ 'X-Amz-Content-SHA256': calculateSHA256(esRequest.body) }, esRequest.headers)
    }
    return aws4.sign(esRequest, credentials)
  }
}

module.exports = aws4signer
