#!/usr/bin/env node

'use strict'

require('essentials')
require('log-node')()

const log = require('log').get('serverless')
const awsRequest = require('@serverless/test/aws-request')
const fsp = require('fs').promises
const path = require('path')
const CloudFormationService = require('aws-sdk').CloudFormation
const SecretsManagerService = require('aws-sdk').SecretsManager
const KafkaService = require('aws-sdk').Kafka

const {
  SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
  SHARED_INFRA_TESTS_ACTIVE_MQ_CREDENTIALS_NAME,
  SHARED_INFRA_TESTS_RABBITMQ_CREDENTIALS_NAME,
} = require('../../../test/utils/cloudformation')

const ensureActiveMQCredentialsSecret = async () => {
  const ssmMqCredentials = {
    username: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_USER,
    password: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_PASSWORD,
  }
  log.notice('Creating SecretsManager ActiveMQ Credentials secret...')
  try {
    await awsRequest(SecretsManagerService, 'createSecret', {
      Name: SHARED_INFRA_TESTS_ACTIVE_MQ_CREDENTIALS_NAME,
      SecretString: JSON.stringify(ssmMqCredentials),
    })
  } catch (e) {
    if (!e.code === 'ResourceExistsException') {
      throw e
    }
  }
}

const ensureRabbitMQCredentialsSecret = async () => {
  const ssmMqCredentials = {
    username: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_USER,
    password: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_PASSWORD,
  }
  log.notice('Creating SecretsManager RabbitMQ Credentials secret...')
  try {
    await awsRequest(SecretsManagerService, 'createSecret', {
      Name: SHARED_INFRA_TESTS_RABBITMQ_CREDENTIALS_NAME,
      SecretString: JSON.stringify(ssmMqCredentials),
    })
  } catch (e) {
    if (!e.code === 'ResourceExistsException') {
      throw e
    }
  }
}

const activeMqBrokerName = 'integration-tests-activemq-broker'
const rabbitMqBrokerName = 'integration-tests-rabbitmq-broker'

async function handleInfrastructureCreation() {
  const [cfnTemplate, kafkaServerProperties] = await Promise.all([
    fsp.readFile(path.join(__dirname, 'cloudformation.yml'), 'utf8'),
    fsp.readFile(path.join(__dirname, 'kafka.server.properties')),
  ])

  await ensureActiveMQCredentialsSecret()
  await ensureRabbitMQCredentialsSecret()

  const clusterName = 'integration-tests-msk-cluster'
  const clusterConfName = 'integration-tests-msk-cluster-configuration'

  log.notice('Creating MSK Cluster configuration...')
  const clusterConfResponse = await awsRequest(
    KafkaService,
    'createConfiguration',
    {
      Name: clusterConfName,
      ServerProperties: kafkaServerProperties,
      KafkaVersions: ['2.2.1'],
    },
  )

  const clusterConfigurationArn = clusterConfResponse.Arn
  const clusterConfigurationRevision =
    clusterConfResponse.LatestRevision.Revision.toString()

  log.notice('Deploying integration tests CloudFormation stack...')
  await awsRequest(CloudFormationService, 'createStack', {
    StackName: SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
    TemplateBody: cfnTemplate,
    Parameters: [
      { ParameterKey: 'ClusterName', ParameterValue: clusterName },
      {
        ParameterKey: 'ActiveMQBrokerName',
        ParameterValue: activeMqBrokerName,
      },
      {
        ParameterKey: 'ActiveMQUser',
        ParameterValue: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_USER,
      },
      {
        ParameterKey: 'ActiveMQPassword',
        ParameterValue: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_PASSWORD,
      },
      {
        ParameterKey: 'RabbitMQBrokerName',
        ParameterValue: rabbitMqBrokerName,
      },
      {
        ParameterKey: 'RabbitMQUser',
        ParameterValue: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_USER,
      },
      {
        ParameterKey: 'RabbitMQPassword',
        ParameterValue: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_PASSWORD,
      },
      {
        ParameterKey: 'ClusterConfigurationArn',
        ParameterValue: clusterConfigurationArn,
      },
      {
        ParameterKey: 'ClusterConfigurationRevision',
        ParameterValue: clusterConfigurationRevision,
      },
    ],
  })

  await awsRequest(CloudFormationService, 'waitFor', 'stackCreateComplete', {
    StackName: SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
  })
  log.notice('Deployed integration tests CloudFormation stack!')
}

async function handleInfrastructureUpdate() {
  log.notice('Updating integration tests CloudFormation stack...')

  await ensureActiveMQCredentialsSecret()
  await ensureRabbitMQCredentialsSecret()

  const cfnTemplate = await fsp.readFile(
    path.join(__dirname, 'cloudformation.yml'),
    'utf8',
  )

  try {
    await awsRequest(CloudFormationService, 'updateStack', {
      StackName: SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
      TemplateBody: cfnTemplate,
      Parameters: [
        { ParameterKey: 'ClusterName', UsePreviousValue: true },
        {
          ParameterKey: 'ActiveMQBrokerName',
          ParameterValue: activeMqBrokerName,
        },
        {
          ParameterKey: 'ActiveMQUser',
          ParameterValue: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_USER,
        },
        {
          ParameterKey: 'ActiveMQPassword',
          ParameterValue: process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_PASSWORD,
        },
        {
          ParameterKey: 'RabbitMQBrokerName',
          ParameterValue: rabbitMqBrokerName,
        },
        {
          ParameterKey: 'RabbitMQUser',
          ParameterValue: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_USER,
        },
        {
          ParameterKey: 'RabbitMQPassword',
          ParameterValue: process.env.SLS_INTEGRATION_TESTS_RABBITMQ_PASSWORD,
        },
        { ParameterKey: 'ClusterConfigurationArn', UsePreviousValue: true },
        {
          ParameterKey: 'ClusterConfigurationRevision',
          UsePreviousValue: true,
        },
      ],
    })
  } catch (e) {
    if (e.message === 'No updates are to be performed.') {
      log.notice(
        'No changes detected. Integration tests CloudFormation stack is up to date.',
      )
      return
    }
    throw e
  }

  await awsRequest(CloudFormationService, 'waitFor', 'stackUpdateComplete', {
    StackName: SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
  })
  log.notice('Updated integration tests CloudFormation stack!')
}

;(async () => {
  log.notice('Starting setup of integration infrastructure...')

  if (!process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_USER) {
    log.error(
      '"SLS_INTEGRATION_TESTS_ACTIVE_MQ_USER" env variable has to be set when provisioning integration infrastructure',
    )
    process.exitCode = 1
    return
  }

  if (!process.env.SLS_INTEGRATION_TESTS_ACTIVE_MQ_PASSWORD) {
    log.error(
      '"SLS_INTEGRATION_TESTS_ACTIVE_MQ_PASSWORD" env variable has to be set when provisioning integration infrastructure',
    )
    process.exitCode = 1
    return
  }

  if (!process.env.SLS_INTEGRATION_TESTS_RABBITMQ_USER) {
    log.error(
      '"SLS_INTEGRATION_TESTS_RABBITMQ_USER" env variable has to be set when provisioning integration infrastructure',
    )
    process.exitCode = 1
    return
  }

  if (!process.env.SLS_INTEGRATION_TESTS_RABBITMQ_PASSWORD) {
    log.error(
      '"SLS_INTEGRATION_TESTS_RABBITMQ_PASSWORD" env variable has to be set when provisioning integration infrastructure',
    )
    process.exitCode = 1
    return
  }

  let describeResponse

  log.notice(
    'Checking if integration tests CloudFormation stack already exists...',
  )
  try {
    describeResponse = await awsRequest(
      CloudFormationService,
      'describeStacks',
      {
        StackName: SHARED_INFRA_TESTS_CLOUDFORMATION_STACK,
      },
    )
    log.notice('Integration tests CloudFormation stack already exists')
  } catch (e) {
    if (e.code !== 'ValidationError') {
      throw e
    }
    log.notice('Integration tests CloudFormation does not exist')
  }

  if (describeResponse) {
    const stackStatus = describeResponse.Stacks[0].StackStatus

    if (
      [
        'CREATE_COMPLETE',
        'UPDATE_COMPLETE',
        'UPDATE_ROLLBACK_COMPLETE',
      ].includes(stackStatus)
    ) {
      await handleInfrastructureUpdate()
    } else {
      log.error(
        `Existing stack has status: ${stackStatus} and it cannot be updated.`,
      )
      process.exitCode = 1
    }
  } else {
    await handleInfrastructureCreation()
  }

  log.notice('Setup of integration infrastructure finished')
})()
