/*
 * © 2021 Thoughtworks, Inc.
 */
import { mockClient } from 'aws-sdk-client-mock'
import path from 'path'
import moment from 'moment'
import * as fs from 'fs'

import {
  ComputeEstimator,
  MemoryEstimator,
  StorageEstimator,
} from '@cloud-carbon-footprint/core'
import {
  AWS_RECOMMENDATIONS_SERVICES,
  configLoader,
  RecommendationResult,
} from '@cloud-carbon-footprint/common'

import { AWS_CLOUD_CONSTANTS } from '../domain'
import { ServiceWrapper, ComputeOptimizerRecommendations } from '../lib'
import {
  mockEBSComputeOptimizerBucketList,
  mockEC2ComputeOptimizerBucketList,
  mockLambdaComputeOptimizerBucketList,
} from './fixtures/computeOptimizer.fixtures'
import {
  GetObjectCommand,
  ListObjectsV2Command,
  S3Client,
} from '@aws-sdk/client-s3'
import { CostExplorerClient } from '@aws-sdk/client-cost-explorer'
import { CloudWatchLogsClient } from '@aws-sdk/client-cloudwatch-logs'
import { CloudWatchClient } from '@aws-sdk/client-cloudwatch'

const s3Mock = mockClient(S3Client)

describe('AWS Compute Optimizer Recommendations Service', () => {
  const getServiceWrapper = () =>
    new ServiceWrapper(
      new CloudWatchClient(),
      new CloudWatchLogsClient(),
      new CostExplorerClient(),
      new S3Client(),
    )
  const getRecommendationsService = () =>
    new ComputeOptimizerRecommendations(
      new ComputeEstimator(),
      new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT),
      new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT),
      new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT),
      getServiceWrapper(),
    )

  const listBucketObjectsSpy = jest.fn()
  const mockBucketName = 'test-bucket'
  const defaultConfig = configLoader().AWS.RECOMMENDATIONS_SERVICE

  function mockListComputeOptimizerBucket(response: any) {
    listBucketObjectsSpy.mockResolvedValue(response)
    s3Mock.on(ListObjectsV2Command).callsFake(listBucketObjectsSpy)
  }

  function mockGetComputeOptimizerBucket(mockCSVFilePath: string) {
    const mockFilePath = path.join(process.cwd(), mockCSVFilePath)
    const mockFile = fs.readFileSync(mockFilePath)
    s3Mock.on(GetObjectCommand).resolves({
      $metadata: {},
      Body: Buffer.from(mockFile) as any,
    })
  }

  beforeAll(() => {
    configLoader().AWS.RECOMMENDATIONS_SERVICE =
      AWS_RECOMMENDATIONS_SERVICES.ComputeOptimizer
  })

  afterEach(() => {
    s3Mock.reset()
    jest.restoreAllMocks()
    configLoader().AWS.RECOMMENDATIONS_SERVICE = defaultConfig
  })

  it('gets recommendations for only "Over Provisioned" EC2 instances ignoring AutoScalingGroup', async () => {
    moment.now = function () {
      return +new Date('2022-01-21T00:00:00.000Z')
    }

    const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerEC2.csv'

    mockListComputeOptimizerBucket(mockEC2ComputeOptimizerBucketList)
    mockGetComputeOptimizerBucket(mockCSVFilePath)

    const awsRecommendationsServices = getRecommendationsService()

    const result =
      await awsRecommendationsServices.getRecommendations(mockBucketName)

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: '1234567890',
        accountName: '1234567890',
        region: 'eu-central-1',
        recommendationType: 'EC2-OVER_PROVISIONED',
        kilowattHourSavings: 11.060730563099998,
        resourceId: 'i-0c80d1b0f3a0c5c69',
        instanceName: 'PA-VM-100 | Networks',
        co2eSavings: 0.004070348847220799,
        recommendationDetail:
          'Save cost by changing instance type from m4.xlarge to t3.xlarge.',
        costSavings: 33.79,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('gets recommendations for only "Not Optimized" EBS volumes (with and without cost savings)', async () => {
    moment.now = function () {
      return +new Date('2022-01-21T00:00:00.000Z')
    }

    const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerEBS.csv'

    mockListComputeOptimizerBucket(mockEBSComputeOptimizerBucketList)
    mockGetComputeOptimizerBucket(mockCSVFilePath)

    const awsRecommendationsServices = getRecommendationsService()

    const result =
      await awsRecommendationsServices.getRecommendations(mockBucketName)

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: '1234567890',
        accountName: '1234567890',
        region: 'us-west-2',
        recommendationType: 'EBS-NotOptimized',
        kilowattHourSavings: 0.0019612799999999993,
        resourceId: 'vol-00e39f1234a7eadfb',
        co2eSavings: 5.857366758275517e-7,
        recommendationDetail:
          'Save cost by changing volume type from gp2(80GB) to gp3(50GB).',
        costSavings: 6.2,
      },
    ]

    expect(result).toEqual(expectedResult)
  })

  it('gets recommendations for only "Not Optimized" Lambda functions', async () => {
    moment.now = function () {
      return +new Date('2022-01-21T00:00:00.000Z')
    }

    const mockCSVFilePath = '/src/__tests__/fixtures/computeOptimizerLambda.csv'

    mockListComputeOptimizerBucket(mockLambdaComputeOptimizerBucketList)
    mockGetComputeOptimizerBucket(mockCSVFilePath)

    const awsRecommendationsServices = getRecommendationsService()

    const result =
      await awsRecommendationsServices.getRecommendations(mockBucketName)

    const expectedResult: RecommendationResult[] = [
      {
        cloudProvider: 'AWS',
        accountId: '1234567890',
        accountName: '1234567890',
        region: 'us-east-2',
        recommendationType: 'Lambda-NotOptimized',
        kilowattHourSavings: 0.1781104877331825,
        resourceId: 'api-user-prod-add_user:$LATEST',
        co2eSavings: 0.00006699239827610782,
        recommendationDetail:
          'Save cost by changing configuration memory size from 1024MB to 848MB.',
        costSavings: 2.988e-4,
      },
    ]

    expect(result).toEqual(expectedResult)
  })
})
