import AWS = require('aws-sdk');
import { CloudwatchMetricStructure, Datapoint } from '../interfaces/cloudwatch';
import { Metric } from '../models/Metric';
import { AWSTags } from '../cache';

export class CloudwatchHelpers {
  static cloudwatch: AWS.CloudWatch = new AWS.CloudWatch();

  static namespacePeriods: { [name: string]: number, 'default': number } = {
    "AWS/EBS": 300,
    "default": 60
  };

  public static async pullCloudwatchMetrics(): Promise<Metric[]> {
    let endTime = new Date();
    let startTime = new Date(endTime.getTime() - 660000);

    return new Promise((resolve, reject) => {
      let metricPromises = [] as Promise<Metric[]>[];

      this.cloudwatch.listMetrics().eachPage((err: any, data: AWS.CloudWatch.Types.ListMetricsOutput) => {
        if (err) {
          console.log(err);
          reject();
          return false;
        }

        if (data === null || !data.Metrics) {
          Promise.all(metricPromises).then((metrics) => {
            let flattenedMetrics: Metric[] = [].concat.apply([], metrics)
            resolve(flattenedMetrics);
          });
          return true;
        }

        data.Metrics.map((metricDefinition) => {
          metricPromises.push(this.getMetric(metricDefinition, startTime, endTime));
        });
        return true;
      });
    }) as Promise<Metric[]>;
  }

  private static async getMetric(metricDefinition: AWS.CloudWatch.Types.Metric, startTime: Date, endTime: Date): Promise<Metric[]> {
    let period = this.namespacePeriods[metricDefinition.Namespace] || this.namespacePeriods.default;

    let params: AWS.CloudWatch.Types.GetMetricStatisticsInput = {
      StartTime: startTime,
      EndTime: endTime,
      MetricName: metricDefinition.MetricName,
      Namespace: metricDefinition.Namespace,
      Period: period,
      Dimensions: metricDefinition.Dimensions,
      Statistics: [
        'Average',
        'Maximum',
        'Minimum',
        'Sum',
        'SampleCount'
      ]
    };

    let extendedParams: AWS.CloudWatch.Types.GetMetricStatisticsInput = {
      StartTime: startTime,
      EndTime: endTime,
      MetricName: metricDefinition.MetricName,
      Namespace: metricDefinition.Namespace,
      Period: period,
      Dimensions: metricDefinition.Dimensions,
      ExtendedStatistics: [
        'p50',
        'p90',
        'p95',
        'p99',
      ]
    };

    let metricPromise = this.cloudwatch.getMetricStatistics(params).promise();
    let extendedMetricPromise = this.cloudwatch.getMetricStatistics(extendedParams).promise();

    return Promise.all([metricPromise, extendedMetricPromise]).then(metrics => {
      let metric = metrics[0];
      let extendedMetric = metrics[1];

      let datapoints = [];

      for (let d of metric.Datapoints as Datapoint[]) {

        // Check for extended metrics
        let percentiles = extendedMetric.Datapoints.filter((e) => {
          return (d.Timestamp.getTime() === e.Timestamp.getTime() && d.Unit === e.Unit && e.ExtendedStatistics)
        }).map((e) => {
          return e.ExtendedStatistics
        })
        
        // Add extended metrics unless all values are the same (usually all 0 or 1)
        if (percentiles.length > 0) {
          let pSet: number[] = [];
          for (let i of Object.keys(percentiles[0])) {
            if (!(pSet.indexOf(percentiles[0][i]) > -1)) {
              pSet.push(percentiles[0][i]);
            }
          }
          if (pSet.length > 1) {
            for (let i of Object.keys(percentiles[0])) {
              if (i === 'p50' || i === 'p90' || i === 'p95' || i === 'p99') {
                d[i] = percentiles[0][i];
              }
            }
          }
        }

        let metricInput: CloudwatchMetricStructure = {
          Definition: {
            Namespace: metricDefinition.Namespace as string,
            MetricName: metricDefinition.MetricName as string,
            Dimensions: metricDefinition.Dimensions as AWS.CloudWatch.Types.Dimension[],
          },
          Metric: {
            Label: (metric.Label as string),
            Datapoints: [d]
          }
        }
        datapoints.push(new Metric(metricInput));
      }
      return datapoints;
    });

  };

}
