/*
 * © 2021 Thoughtworks, Inc.
 */

import { mockClient } from 'aws-sdk-client-mock'

import S3 from '../lib/S3'
import { ServiceWrapper } from '../lib'
import mockAWSCloudWatchGetMetricDataCall from '../lib/mockAWSCloudWatchGetMetricDataCall'
import { buildCostExplorerGetCostResponse } from './fixtures/builders'
import { CloudWatchClient } from '@aws-sdk/client-cloudwatch'
import {
  CostExplorerClient,
  GetCostAndUsageCommand,
} from '@aws-sdk/client-cost-explorer'
import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'
import { S3Client } from '@aws-sdk/client-s3'

const cloudWatchMock = mockClient(CloudWatchClient)
const costExplorerMock = mockClient(CostExplorerClient)

describe('S3', () => {
  afterEach(() => {
    costExplorerMock.reset()
    cloudWatchMock.reset()
  })

  const region = 'us-east-1'
  const start = '2020-08-01T00:00:00.000Z'
  const dayTwo = '2020-08-02T00:00:00.000Z'
  const dayThree = '2020-08-03T00:00:00.000Z'
  const end = '2020-08-04T00:00:00.000Z'
  const metricDataQueries = [
    {
      Id: 's3Size',
      Expression:
        'SUM(SEARCH(\'{AWS/S3,BucketName,StorageType} MetricName="BucketSizeBytes" StorageType="StandardStorage"\', \'Average\', 86400))',
    },
  ]
  const getServiceWrapper = () =>
    new ServiceWrapper(
      new CloudWatchClient(),
      new CloudWatchLogsClient(),
      new CostExplorerClient(),
      new S3Client(),
    )

  it('gets S3 usage', async () => {
    const response: any = {
      MetricDataResults: [
        {
          Id: 's3Size',
          Label: 's3Size',
          Timestamps: [start, dayTwo, dayThree, end],
          Values: [2586032500, 3286032500, 7286032500, 4286032500],
          StatusCode: 'Complete',
          Messages: [],
        },
      ],
    }

    mockAWSCloudWatchGetMetricDataCall(
      new Date(start),
      new Date(end),
      response,
      metricDataQueries,
    )

    const s3Service = new S3(getServiceWrapper())
    const result = await s3Service.getUsage(new Date(start), new Date(end))
    expect(result).toEqual([
      {
        terabyteHours: 0.0564475885767024,
        timestamp: new Date(start),
      },
      {
        terabyteHours: 0.0717270995664876,
        timestamp: new Date(dayTwo),
      },
      {
        terabyteHours: 0.1590385909366887,
        timestamp: new Date(dayThree),
      },
      {
        terabyteHours: 0.09355497240903787,
        timestamp: new Date(end),
      },
    ])
  })

  it('gets S3 cost for two days', async () => {
    costExplorerMock.on(GetCostAndUsageCommand).resolves(
      buildCostExplorerGetCostResponse([
        { start, amount: 2.3, keys: ['Amazon Simple Storage Service'] },
        { start: dayTwo, amount: 4.6, keys: ['test'] },
      ]),
    )

    const s3Service = new S3(getServiceWrapper())
    const s3Costs = await s3Service.getCosts(
      new Date(start),
      new Date(end),
      region,
    )

    expect(s3Costs).toEqual([
      { amount: 2.3, currency: 'USD', timestamp: new Date(start) },
      { amount: 4.6, currency: 'USD', timestamp: new Date(dayTwo) },
    ])
  })

  it('throw Partial Data Error if partial data returned', async () => {
    const response: any = {
      MetricDataResults: [
        {
          Id: 's3Size',
          Label: 's3Size',
          Timestamps: [start, dayTwo, dayThree, end],
          Values: [2586032500, 3286032500, 7286032500, 4286032500],
          StatusCode: 'Complete',
          Messages: [],
        },
        {
          Id: 's3Size',
          Label: 's3Size',
          Timestamps: [start, dayTwo, dayThree, end],
          Values: [2586032500, 3286032500, 7286032500, 4286032500],
          StatusCode: 'PartialData',
          Messages: [],
        },
      ],
    }
    mockAWSCloudWatchGetMetricDataCall(
      new Date(start),
      new Date(end),
      response,
      metricDataQueries,
    )

    const s3Service = new S3(getServiceWrapper())
    const getS3Usage = async () =>
      await s3Service.getUsage(new Date(start), new Date(end))

    await expect(getS3Usage).rejects.toThrow('Partial Data Returned from AWS')
  })

  it('do not throw Partial Data Error if complete data is returned', async () => {
    const response: any = {
      MetricDataResults: [
        {
          Id: 's3Size',
          Label: 's3Size',
          Timestamps: [start, dayTwo, dayThree, end],
          Values: [2586032500, 3286032500, 7286032500, 4286032500],
          StatusCode: 'Complete',
          Messages: [],
        },
        {
          Id: 's3Size',
          Label: 's3Size',
          Timestamps: [start, dayTwo, dayThree, end],
          Values: [2586032500, 3286032500, 7286032500, 4286032500],
          StatusCode: 'Complete',
          Messages: [],
        },
      ],
    }
    mockAWSCloudWatchGetMetricDataCall(
      new Date(start),
      new Date(end),
      response,
      metricDataQueries,
    )

    const s3Service = new S3(getServiceWrapper())
    const getS3Usage = async () =>
      await s3Service.getUsage(new Date(start), new Date(end))

    await expect(getS3Usage).not.toThrow()
  })
})
