/*
 * © 2021 Thoughtworks, Inc.
 */
import {
  GetRightsizingRecommendationCommand,
  GetRightsizingRecommendationCommandOutput,
} from '@aws-sdk/client-cost-explorer'
import { mockClient } from 'aws-sdk-client-mock'
import moment from 'moment'
import { CloudWatchClient } from '@aws-sdk/client-cloudwatch'
import { CostExplorerClient } from '@aws-sdk/client-cost-explorer'
import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'
import { S3Client } from '@aws-sdk/client-s3'

import { ComputeEstimator, MemoryEstimator } from '@cloud-carbon-footprint/core'
import {
  AWS_DEFAULT_RECOMMENDATION_TARGET,
  AWS_RECOMMENDATIONS_TARGETS,
  RecommendationResult,
} from '@cloud-carbon-footprint/common'

import {
  rightsizingCrossFamilyRecommendationModify,
  rightsizingCrossFamilyRecommendationTerminate,
  rightsizingRecommendationModify,
  rightsizingRecommendationModify1,
  rightsizingRecommendationTerminate,
} from './fixtures/costExplorer.fixtures'
import { AWS_CLOUD_CONSTANTS } from '../domain'
import { ServiceWrapper, RightsizingRecommendations } from '../lib'

describe('AWS Rightsizing Recommendations Service', () => {
  const getServiceWrapper = () =>
    new ServiceWrapper(
      new CloudWatchClient(),
      new CloudWatchLogsClient(),
      new CostExplorerClient(),
      new S3Client(),
    )

  const costExplorerMock = mockClient(CostExplorerClient)

  afterEach(() => {
    costExplorerMock.reset()
    jest.restoreAllMocks()
    getRightsizingRecommendationSpy.mockClear()
  })

  const getRightsizingRecommendationSpy = jest.fn()

  function mockGetRightsizingRecommendation(
    response: GetRightsizingRecommendationCommandOutput,
  ) {
    costExplorerMock.on(GetRightsizingRecommendationCommand).resolves(response)
  }

  it('Get recommendations from Rightsizing API type: Terminate with pagination', async () => {
    moment.now = function () {
      return +new Date('2020-04-01T00:00:00.000Z')
    }
    mockGetRightsizingRecommendation(rightsizingRecommendationTerminate)

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    const result = await awsRecommendationsServices.getRecommendations(
      AWS_DEFAULT_RECOMMENDATION_TARGET,
    )

    const calls = costExplorerMock.commandCalls(
      GetRightsizingRecommendationCommand,
    )

    expect(calls).toHaveLength(1)

    expect(calls[0].args[0].input).toEqual({
      Service: 'AmazonEC2',
      Configuration: {
        BenefitsConsidered: false,
        RecommendationTarget: 'SAME_INSTANCE_FAMILY',
      },
    })

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: 'test-account',
        accountName: 'test-account',
        region: 'us-east-2',
        recommendationType: 'TERMINATE',
        recommendationDetail: 'TERMINATE instance: test-instance-name.',
        kilowattHourSavings: 275.386397472,
        resourceId: 'test-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.10358062264645465,
        costSavings: 20,
      },
      {
        cloudProvider: 'AWS',
        accountId: 'test-account-1',
        accountName: 'test-account-1',
        region: 'us-east-2',
        recommendationType: 'TERMINATE',
        recommendationDetail: 'TERMINATE instance: test-instance-name.',
        kilowattHourSavings: 62.1072,
        resourceId: 'test-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.02336027670895392,
        costSavings: 80,
      },
      {
        cloudProvider: 'AWS',
        accountId: 'test-account-2',
        accountName: 'test-account-2',
        region: 'us-east-2',
        recommendationType: 'TERMINATE',
        recommendationDetail: 'TERMINATE instance: test-instance-name.',
        kilowattHourSavings: 0.38817,
        resourceId: 'test-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.00014600172943096202,
        costSavings: 20,
      },
      {
        accountId: 'test-account-3',
        accountName: 'test-account-3',
        cloudProvider: 'AWS',
        co2eSavings: 0.00014600172943096202,
        costSavings: 30,
        kilowattHourSavings: 0.38817,
        recommendationDetail: 'TERMINATE instance with Resource ID: test-id.',
        recommendationType: 'TERMINATE',
        region: 'us-east-2',
        resourceId: 'test-id',
        instanceName: '',
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('Get recommendations from Rightsizing API type: Modify with targetInstance Co2e', async () => {
    moment.now = function () {
      return +new Date('2020-04-01T00:00:00.000Z')
    }

    // Simulate AWS returning case type from runtime payload
    const modifyResponse: GetRightsizingRecommendationCommandOutput =
      JSON.parse(JSON.stringify(rightsizingRecommendationModify1))

    modifyResponse.RightsizingRecommendations![0].RightsizingType =
      'Modify' as unknown as any

    mockGetRightsizingRecommendation(modifyResponse)

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    const result = await awsRecommendationsServices.getRecommendations(
      AWS_DEFAULT_RECOMMENDATION_TARGET,
    )

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: 'test-account',
        accountName: 'test-account',
        region: 'us-east-2',
        recommendationType: 'Modify',
        recommendationDetail:
          'Modify instance: test-instance-name. Update instance type m5.xlarge to m5.large',
        kilowattHourSavings: 2.0615368199999997,
        resourceId: 'Test-resource-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.0007754023778385905,
        costSavings: 226,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('Get recommendations from Rightsizing API type: Modify', async () => {
    moment.now = function () {
      return +new Date('2020-04-01T00:00:00.000Z')
    }
    mockGetRightsizingRecommendation(rightsizingRecommendationModify)

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    const result = await awsRecommendationsServices.getRecommendations(
      AWS_DEFAULT_RECOMMENDATION_TARGET,
    )
    const calls = costExplorerMock.commandCalls(
      GetRightsizingRecommendationCommand,
    )

    expect(calls).toHaveLength(1)

    expect(calls[0].args[0].input).toEqual({
      Service: 'AmazonEC2',
      Configuration: {
        BenefitsConsidered: false,
        RecommendationTarget: 'SAME_INSTANCE_FAMILY',
      },
    })
    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: 'test-account',
        accountName: 'test-account',
        region: 'us-east-2',
        recommendationType: 'MODIFY',
        recommendationDetail:
          'MODIFY instance: test-instance-name. Update instance type t2.micro to t2.nano',
        kilowattHourSavings: 0.194085,
        resourceId: 'Test-resource-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.00007300086471548101,
        costSavings: 226,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('Get recommendations from Rightsizing API type: Terminate with Cross Family parameter', async () => {
    moment.now = function () {
      return +new Date('2020-04-01T00:00:00.000Z')
    }
    mockGetRightsizingRecommendation(
      rightsizingCrossFamilyRecommendationTerminate,
    )

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    const result = await awsRecommendationsServices.getRecommendations(
      AWS_RECOMMENDATIONS_TARGETS.CROSS_INSTANCE_FAMILY,
    )

    const calls = costExplorerMock.commandCalls(
      GetRightsizingRecommendationCommand,
    )

    expect(calls).toHaveLength(1)

    expect(calls[0].args[0].input).toEqual({
      Service: 'AmazonEC2',
      Configuration: {
        BenefitsConsidered: false,
        RecommendationTarget: AWS_RECOMMENDATIONS_TARGETS.CROSS_INSTANCE_FAMILY,
      },
    })
    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: 'test-account',
        accountName: 'test-account',
        region: 'us-east-2',
        recommendationType: 'TERMINATE',
        recommendationDetail: 'TERMINATE instance: test-instance-name.',
        kilowattHourSavings: 0.38817,
        resourceId: 'Test-resource-id',
        instanceName: 'test-instance-name',
        co2eSavings: 0.00014600172943096202,
        costSavings: 20,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('Get recommendations from Rightsizing API type: Modify with Cross Family parameter', async () => {
    moment.now = function () {
      return +new Date('2020-04-01T00:00:00.000Z')
    }
    mockGetRightsizingRecommendation(rightsizingCrossFamilyRecommendationModify)

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    const result = await awsRecommendationsServices.getRecommendations(
      AWS_RECOMMENDATIONS_TARGETS.CROSS_INSTANCE_FAMILY,
    )

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: 'test-account',
        accountName: 'test-account',
        region: 'us-east-2',
        recommendationType: 'MODIFY',
        recommendationDetail:
          'MODIFY instance: test-instance-name. Update instance type t2.micro to t3.micro',
        kilowattHourSavings: -0.38817,
        resourceId: 'Test-resource-id',
        instanceName: 'test-instance-name',
        co2eSavings: -0.00014600172943096202,
        costSavings: 20,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('Logs the error response if there is a problem getting recommendations', async () => {
    getRightsizingRecommendationSpy.mockRejectedValue({
      message: 'error-test',
    })
    costExplorerMock
      .on(GetRightsizingRecommendationCommand)
      .callsFake(getRightsizingRecommendationSpy)

    const awsRecommendationsServices = new RightsizingRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      getServiceWrapper(),
    )

    await expect(() =>
      awsRecommendationsServices.getRecommendations(
        AWS_DEFAULT_RECOMMENDATION_TARGET,
      ),
    ).rejects.toThrow(
      `Failed to grab AWS Rightsizing recommendations. Reason: error-test`,
    )
  })
})
