'use strict';

const AWS = require('aws-sdk');
const https = require('https');
const url = require('url');

const logger = console;

async function response(event, context, status, data = {}, err) {
  const reason = err ? err.message : '';
  const { StackId, RequestId, LogicalResourceId, PhysicalResourceId, ResponseURL } = event;

  const body = JSON.stringify({
    StackId,
    RequestId,
    LogicalResourceId,
    PhysicalResourceId,
    Status: status,
    Reason: reason && `${reason} See details in CloudWatch Log: ${context.logStreamName}`,
    Data: data,
  });

  logger.log(body);

  const parsedUrl = url.parse(ResponseURL);
  const options = {
    hostname: parsedUrl.hostname,
    port: 443,
    path: parsedUrl.path,
    method: 'PUT',
    headers: {
      'Content-Type': '',
      'Content-Length': body.length,
    },
  };

  return new Promise((resolve, reject) => {
    const request = https.request(options, (resp) => {
      logger.log(`STATUS: ${resp.statusCode}`);
      logger.log(`HEADERS: ${JSON.stringify(resp.headers)}`);
      return resolve(data);
    });

    request.on('error', (error) => {
      logger.log(`sendResponse Error:\n${error}`);
      return reject(error);
    });

    request.on('end', () => {
      logger.log('end');
      return resolve();
    });

    request.write(body);
    request.end();
  });
}

function getLambdaArn(partition, region, accountId, functionName) {
  return `arn:${partition}:lambda:${region}:${accountId}:function:${functionName}`;
}

function getEnvironment(context) {
  const arn = context.invokedFunctionArn.match(
    /^arn:(aws[\w-]*).*:lambda:([\w+-]{2,}\d+):(\d+):function:(.*)$/
  );

  return {
    LambdaArn: arn[0],
    Partition: arn[1],
    Region: arn[2],
    AccountId: arn[3],
    LambdaName: arn[4],
  };
}

function handlerWrapper(handler, PhysicalResourceId) {
  return async (event, context, callback) => {
    // extend the `event` object to include the PhysicalResourceId
    event = Object.assign({}, event, { PhysicalResourceId });
    return Promise.resolve(handler(event, context, callback))
      .then(
        (result) => response(event, context, 'SUCCESS', result),
        (error) => response(event, context, 'FAILED', {}, error)
      )
      .then((result) => callback(null, result), callback);
  };
}

async function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

const MAX_AWS_REQUEST_TRY = (() => {
  const DEFAULT_MAX_AWS_REQUEST_TRY = 8;
  const userValue = Number(process.env.SLS_AWS_REQUEST_MAX_RETRIES);
  return userValue >= 0 ? userValue : DEFAULT_MAX_AWS_REQUEST_TRY;
})();

const getServiceInstance = (nameInput) => {
  let name;
  let params = {};
  if (typeof nameInput === 'string') {
    name = nameInput;
  } else {
    name = nameInput.name;
    params = nameInput.params;
  }
  return new AWS[name](params);
};

async function awsRequest(service, method, ...args) {
  const serviceInstance = getServiceInstance(service);
  const callAws = async (requestTry) => {
    try {
      return await serviceInstance[method](...args).promise();
    } catch (error) {
      if (
        requestTry < MAX_AWS_REQUEST_TRY &&
        error &&
        (error.statusCode === 429 || error.retryable)
      ) {
        await wait(2000 + Math.random() * 1000);
        return callAws(++requestTry);
      }
      throw error;
    }
  };
  return callAws(1);
}

module.exports = {
  logger,
  response,
  getEnvironment,
  getLambdaArn,
  handlerWrapper,
  wait,
  awsRequest,
};
