import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as codedeploy from 'aws-cdk-lib/aws-codedeploy';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import { ApplicationTargetGroup, ApplicationListener } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { studioSecrets } from '../secrets/studio-secrets';
import { fluteSecrets } from '../secrets/flute-secrets';
import { config } from '../../../config'; // Import the configuration

export interface FluteFargateServiceProps {
  accountStage: string;
  cluster: ecs.Cluster;
  vpc: ec2.IVpc;
  subnets: ec2.ISubnet[];
  blueTargetGroup: ApplicationTargetGroup;
  greenTargetGroup: ApplicationTargetGroup;
  httpsListener: ApplicationListener;
  loadBalancerSecurityGroup: ec2.SecurityGroup;
}

export class FluteFargateService extends Construct {
  public service: ecs.FargateService;

  constructor(scope: Construct, id: string, props: FluteFargateServiceProps) {
    super(scope, id);

    const taskRole = new iam.Role(this, 'GrpcTaskRole', {
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
      ],
    });

    // Define a Fargate task definition
    const taskDefinition = new ecs.FargateTaskDefinition(this, 'StudioApiFluteTaskDefinition', {
      memoryLimitMiB: 32768,
      cpu: 8192,
      taskRole: taskRole,
    });

    const repository = ecr.Repository.fromRepositoryAttributes(this, 'studioApiFluteRepository', {
      repositoryArn: `arn:aws:ecr:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:repository/studio-api-flute`,
      repositoryName: 'studio-api-flute',
    });
    const secrets = studioSecrets(this, props.accountStage, false);
    const fluteSecret = fluteSecrets(this, props.accountStage, false);

    // Add a container to the task definition
    const container = taskDefinition.addContainer('StudioApiFluteContainer1', {
      image: ecs.ContainerImage.fromEcrRepository(repository, 'latest'),
      logging: new ecs.AwsLogDriver({
        streamPrefix: 'StudioApiFluteContainer',
      }),
      environment: {
        AWS_DEFAULT_REGION: 'us-east-1',
        ENV: props.accountStage,
      },
      secrets: { ...secrets, ...fluteSecret },
      healthCheck: {
        command: ['CMD-SHELL', `curl -f http://localhost:9090/health || exit 1`],
        interval: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(5),
        retries: 3,
        startPeriod: cdk.Duration.seconds(60),
      },
      cpu: config[props.accountStage].studioApiServiceConfig.studioApiContainerConfig.cpu,
      memoryLimitMiB: config[props.accountStage].studioApiServiceConfig.studioApiContainerConfig.memory,
      essential: true,
    });

    // Add port mappings to the container
    container.addPortMappings({
      containerPort: 9090,
      name: 'studio-api-flute',
    });

    // Create a security group for the Fargate service
    const fargateServiceSecurityGroup = new ec2.SecurityGroup(this, 'FluteFargateServiceSG', {
      vpc: props.vpc,
      allowAllOutbound: true,
      description: 'BackendInfraStack/studioApiFluteService/FluteFargateServiceSG',
    });

    // Allow traffic from the load balancer to the Fargate service on port 9090
    fargateServiceSecurityGroup.addIngressRule(props.loadBalancerSecurityGroup, ec2.Port.tcp(9090), 'Allow traffic from Load Balancer');

    // Define the Fargate service
    this.service = new ecs.FargateService(this, 'StudioApiFluteFargateService', {
      cluster: props.cluster,
      taskDefinition: taskDefinition,
      serviceName: 'StudioApiFluteFargateService',
      deploymentController: {
        type: ecs.DeploymentControllerType.CODE_DEPLOY,
      },
      securityGroups: [fargateServiceSecurityGroup],
      healthCheckGracePeriod: cdk.Duration.seconds(60),
      vpcSubnets: {
        subnets: props.subnets,
      },
      enableExecuteCommand: true,
      desiredCount: 3,
    });

    // Attach the Fargate service to the blue target group initially
    this.service.attachToApplicationTargetGroup(props.blueTargetGroup);

    // Enable auto-scaling for the Fargate service
    const scalableTarget = this.service.autoScaleTaskCount({
      minCapacity: 3,
      maxCapacity: 10,
    });

    // Define a scaling policy based on CPU utilization
    scalableTarget.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 60,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    // Define a scaling policy based on memory utilization
    scalableTarget.scaleOnMemoryUtilization('MemoryScaling', {
      targetUtilizationPercent: 60,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    // Create a CodeDeploy application
    const codedeployApp = new codedeploy.EcsApplication(this, 'StudioApiFluteCodeDeployApp', {
      applicationName: 'StudioApiFluteCodeDeployApp',
    });

    // Create a CodeDeploy deployment group with canary deployment configuration
    const codedeployDeploymentGroup = new codedeploy.EcsDeploymentGroup(this, 'StudioApiFluteCodeDeployDeploymentGroup', {
      application: codedeployApp,
      service: this.service,
      deploymentConfig: codedeploy.EcsDeploymentConfig.ALL_AT_ONCE,
      blueGreenDeploymentConfig: {
        blueTargetGroup: props.blueTargetGroup,
        greenTargetGroup: props.greenTargetGroup,
        listener: props.httpsListener,
        terminationWaitTime: cdk.Duration.minutes(config[props.accountStage].studioApiServiceConfig.terminationWaitTime),
      },
    });

    // Create a Lambda function to handle the CodeDeploy deployment
    const deploymentFunction = new lambda.Function(this, 'FluteCodeDeployFunction', {
      runtime: lambda.Runtime.NODEJS_16_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/flute-deploy'),
      functionName: 'FluteCodeDeployFunction',
      environment: {
        APPLICATION_NAME: codedeployApp.applicationName,
        DEPLOYMENT_GROUP_NAME: codedeployDeploymentGroup.deploymentGroupName,
        TASK_DEFINITION_FAMILY: taskDefinition.family,
        CONTAINER_NAME: container.containerName,
      },
      timeout: cdk.Duration.seconds(30),
    });

    // Grant necessary permissions to the Lambda function
    deploymentFunction.addToRolePolicy(
      new iam.PolicyStatement({
        actions: [
          'sts:GetCallerIdentity',
          'ecs:ListTaskDefinitions',
          'ecs:describeTaskDefinition',
          'ecs:registerTaskDefinition',
          'ecs:UpdateService',
          'ecs:RunTask',
          'ecs:DescribeTasks',
          'ecr:DescribeImages',
          'iam:PassRole',
          'ssm:PutParameter',
          'codedeploy:CreateDeployment',
          'codedeploy:GetDeployment',
          'codedeploy:GetDeploymentConfig',
          'codedeploy:GetApplication',
          'codedeploy:GetApplicationRevision',
          'codedeploy:RegisterApplicationRevision',
          'codedeploy:GetDeploymentConfig',
          'codedeploy:GetDeploymentGroup',
          'codedeploy:GetDeploymentTarget',
          'codedeploy:ListApplications',
          'codedeploy:ListDeployments',
          'codedeploy:ListDeploymentConfigs',
          'codedeploy:ListDeploymentGroups',
          'codedeploy:ListDeploymentTargets',
          'codedeploy:StopDeployment',
        ],
        resources: ['*'],
      })
    );

    // Create an EventBridge rule to monitor ECR image push events
    const rule = new events.Rule(this, 'FluteEcrImagePushRule', {
      eventPattern: {
        source: ['aws.ecr'],
        detailType: ['ECR Image Action'],
        detail: {
          'repository-name': ['studio-api-flute'],
          'action-type': ['PUSH'],
          'image-tag': ['latest'],
        },
      },
    });
    rule.addTarget(new targets.LambdaFunction(deploymentFunction));
  }
}
