/*
 * Copyright (C) 2023 Amazon.com, Inc. or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * CDK Nag for ECS Cluster
 *
 * @group nag/ecs-cluster
 */

import path from 'path';
import fs from 'fs';

import { App, Aspects, Stack } from 'aws-cdk-lib';
import { Annotations, Match } from 'aws-cdk-lib/assertions';
import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag';
import { EcsCluster } from '../../src'
import {
  InstanceClass,
  InstanceSize,
  InstanceType,
  MachineImage,
  SubnetType,
  UserData,
  Vpc
} from 'aws-cdk-lib/aws-ec2';
import { ContainerImage } from 'aws-cdk-lib/aws-ecs';
import { Queue, QueueEncryption } from 'aws-cdk-lib/aws-sqs';
import { Topic } from 'aws-cdk-lib/aws-sns';
import * as logs from 'aws-cdk-lib/aws-logs';

const mockApp = new App();
const mockStack = new Stack(mockApp, 'NagStack');

const oldResolve = path.resolve;

/**
 * Mock the `path.resolve` function to point to the `dist`
 * directory instead of the `src` directory when running
 * in the context of the test suite.
 * @param args the path segments.
 * @returns a resolved path.
 */
path.resolve = (...args: string[]) => {
  const endsWithJs = args[args.length - 1].endsWith('.js');
  const pathExists = fs.existsSync(oldResolve(...args));

  if (endsWithJs && !pathExists) {
    // Replace the `src` directory by `dist` in the entire path
    const newPath = oldResolve(...args).replace(/src/g, 'dist');
    return (oldResolve(newPath));
  }
  return (oldResolve(...args));
};

const logGroup = new logs.LogGroup(mockStack, 'LogRetention');

// Instantiate Construct
new EcsCluster(mockStack, 'EcsCluster', {
  vpc: new Vpc(mockStack, 'vpc', {subnetConfiguration: [{
      cidrMask: 24,
      name: 'ingress',
      subnetType: SubnetType.PUBLIC,
    },
    {
      cidrMask: 24,
      name: 'application',
      subnetType: SubnetType.PRIVATE_WITH_EGRESS,
    },
    {
      cidrMask: 28,
      name: 'rds',
      subnetType: SubnetType.PRIVATE_ISOLATED,
    }]}),
  autoScaling: {
    minInstanceCapacity: 0,
    maxInstanceCapacity: 10,
    maxMessagesPerTask: 10,
    maxTaskCapacity: 5,
  },
  containerProps: {
    containerName: 'myContainer',
    image: ContainerImage.fromRegistry(''),
    cpuLimit: 4,
    memoryLimitMiB: 4096,
    gpuCount: 1,
  },
  launchTemplateProps: {
    instanceType: InstanceType.of(InstanceClass.C5, InstanceSize.XLARGE),
    machineImage: MachineImage.latestAmazonLinux2023(),
    userData: UserData.forLinux()
  },
  eventBus: new Topic(mockStack, 'topic'),
  eventQueue: new Queue(mockStack, 'queue', {
    encryption: QueueEncryption.SQS_MANAGED
  }),
  logGroup,
  containerInsights: true
});

Aspects.of(mockStack).add(new AwsSolutionsChecks({ verbose: true }));

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/vpc/Resource',
    [{ id: 'AwsSolutions-VPC7', reason: 'VPC is provided by the customer, not part of the construct' }],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/EcsCluster/Asg/ASG',
    [
        { id: 'AwsSolutions-EC26', reason: 'ASG generated by ECS AsgCapacityProvider Construct. cannot customize this' },
        { id: 'AwsSolutions-AS3', reason: 'ASG generated by ECS AsgCapacityProvider Construct. cannot customize this' }
    ],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/topic/Resource',
    [
        { id: 'AwsSolutions-SNS2', reason: 'Topic is provided by the customer, not part of the construct' },
        { id: 'AwsSolutions-SNS3', reason: 'Topic is provided by the customer, not part of the construct' }
    ],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/queue/Resource',
    [
      { id: 'AwsSolutions-SQS3', reason: 'Queue is provided by the customer, not part of the construct' },
      { id: 'AwsSolutions-SQS4', reason: 'Queue is provided by the customer, not part of the construct' }
    ],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/EcsCluster/InstanceRole/DefaultPolicy/Resource',
    [
      { id: 'AwsSolutions-IAM5', reason: 'Generated by CDK ECS construct' },
    ],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/EcsCluster/AutoScaler/Resource',
    [
      { id: 'AwsSolutions-L1', reason: 'Using NodeJS 18 which was the latest until very recently' },
    ],
);

NagSuppressions.addStackSuppressions(mockStack, [
  { id: 'AwsSolutions-IAM4', reason: 'Using standard managed policies' }
]);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/EcsCluster/TaskDefinition/Resource',
    [
      { id: 'AwsSolutions-ECS2', reason: 'No secret is injected, only technical references (ARN, REGION, ...)' },
    ],
);

NagSuppressions.addResourceSuppressionsByPath(
    mockStack,
    '/NagStack/EcsCluster/AutoScaler/ServiceRole/DefaultPolicy/Resource',
    [
      { id: 'AwsSolutions-IAM5', reason: 'Using a condition to restrict permission only to the cluster' },
    ],
);

describe('CDK Nag', () => {

  test('No unsuppressed Errors', () => {
    const errors = Annotations
      .fromStack(mockStack)
      .findError('*', Match.stringLikeRegexp('AwsSolutions-.*'));
    if (errors && errors.length > 0) {
      console.log(errors);
    }
    expect(errors).toHaveLength(0);
  });

});