import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { config } from '../../config';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as s3 from 'aws-cdk-lib/aws-s3';

export interface DBMonitorProps extends cdk.StackProps {
  accountStage: string;
}

export class DBMonitorStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: DBMonitorProps) {
    super(scope, id, props);

    const vpc = ec2.Vpc.fromLookup(this, 'SunoMainVpc', {
      vpcId: config[`${props.accountStage}`].vpc,
    });

    // Import the external layer by ARN
    const externalLayer = lambda.LayerVersion.fromLayerVersionArn(
      this,
      'ImportedLayer',
      'arn:aws:lambda:us-east-2:770693421928:layer:Klayers-p312-psycopg:7'
    );

    // Create Lambda layer with dependencies
    const layerFromRequirement = new lambda.LayerVersion(this, 'DatabaseMonitorReqDependences', {
      code: lambda.Code.fromAsset('lambda/glue-jobs/layers', {
        bundling: {
          image: lambda.Runtime.PYTHON_3_12.bundlingImage,
          command: ['bash', '-c', 'pip install -r requirements.txt -t /asset-output/python && ' + 'cp requirements.txt /asset-output/'],
        },
      }),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_12],
      description: 'Lambda layer with Python dependencies',
    });
    const secret = secretsmanager.Secret.fromSecretNameV2(this, 'dbsecret', 'app-user-main-db-secret');

    if (props.accountStage === 'prod') {
      const lambdaFunction = new lambda.Function(this, 'databaseMonitor', {
        runtime: lambda.Runtime.PYTHON_3_12,
        handler: 'database-monitor.handler',
        code: lambda.Code.fromAsset('lambda/database-monitor'),
        functionName: 'database-monitor',
        timeout: cdk.Duration.minutes(15),
        layers: [externalLayer, layerFromRequirement],
        memorySize: 1024,
        vpc: vpc,
      });

      const existingBucket = s3.Bucket.fromBucketName(this, 'ExistingBucket', 'suno-database-monior-log');
      existingBucket.grantPut(lambdaFunction);

      secret.grantRead(lambdaFunction);

      // Create a CloudWatch Events rule to trigger the Lambda function every 3 minutes
      const rule = new events.Rule(this, 'DatabaseMonitorRule', {
        schedule: events.Schedule.cron({ minute: '*/3' }),
        ruleName: 'DatabaseMonitorRule',
      });

      // Define the payload to send to the Lambda function
      const databaseMonitorRulePayload = {
        body: JSON.stringify({ sentence: 'Scheduled invocation' }),
      };

      // Add the Lambda function alias as the target of the rule with the specified payload
      rule.addTarget(
        new targets.LambdaFunction(lambdaFunction, {
          event: events.RuleTargetInput.fromObject(databaseMonitorRulePayload),
        })
      );

      // Grant CloudWatch Events permission to invoke the Lambda function
      lambdaFunction.addPermission('AllowCloudWatchInvoke', {
        principal: new iam.ServicePrincipal('events.amazonaws.com'),
        action: 'lambda:InvokeFunction',
        sourceArn: rule.ruleArn,
      });
    }

    const lambdaSubnets = config[`${props.accountStage}`].lambdaSubnets.map((subnetId) => ec2.Subnet.fromSubnetId(this, subnetId, subnetId));
    const databaseAccessor = new lambda.Function(this, 'databaseAccessor', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'database-accessor.handler',
      code: lambda.Code.fromAsset('lambda/database-accessor'),
      functionName: 'database-accessor',
      timeout: cdk.Duration.minutes(1),
      layers: [externalLayer, layerFromRequirement],
      memorySize: 1024,
      vpc: vpc,
      vpcSubnets: {
        subnets: lambdaSubnets,
      },
    });
    secret.grantRead(databaseAccessor);

    // Create Lambda layer with dependencies
    const redisLayerFromRequirement = new lambda.LayerVersion(this, 'RedisClipAccessorReqDependences', {
      code: lambda.Code.fromAsset('lambda/redis-clip-accessor/layers', {
        bundling: {
          image: lambda.Runtime.PYTHON_3_12.bundlingImage,
          command: ['bash', '-c', 'pip install -r requirements.txt -t /asset-output/python && ' + 'cp requirements.txt /asset-output/'],
        },
      }),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_12],
      description: 'Lambda layer with Python dependencies',
    });

    const ML_REDIS_URL =
      props.accountStage === 'prod'
        ? 'redis://valkey-cluster-rl.9rcjcr.ng.0001.use2.cache.amazonaws.com:6379'
        : 'redis://valkey-cluster-default.ic34gd.ng.0001.use2.cache.amazonaws.com:6379';

    const ORPHENUS_REDIS_URL =
      props.accountStage === 'prod'
        ? 'redis://valkey-cluster-orpheus.9rcjcr.ng.0001.use2.cache.amazonaws.com:6379'
        : 'redis://valkey-cluster-default.ic34gd.ng.0001.use2.cache.amazonaws.com:6379';

    const RECOMMENDATION_REDIS_URL =
      props.accountStage === 'prod'
        ? 'redis://valkey-cluster-recommendation.9rcjcr.ng.0001.use2.cache.amazonaws.com:6379'
        : 'redis://valkey-cluster-default.ic34gd.ng.0001.use2.cache.amazonaws.com:6379';

    const orpheusRedisAccessor = new lambda.Function(this, 'orpheusRedisAccessor', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'redis-clip-accessor.handler',
      code: lambda.Code.fromAsset('lambda/redis-clip-accessor'),
      functionName: 'redis-orpheus-accessor',
      timeout: cdk.Duration.minutes(1),
      layers: [externalLayer, redisLayerFromRequirement],
      memorySize: 1024,
      vpc: vpc,
      vpcSubnets: {
        subnets: lambdaSubnets,
      },
      environment: {
        ML_REDIS_URL: ORPHENUS_REDIS_URL,
      },
    });

    const recommendationRedisAccessor = new lambda.Function(this, 'recommendationRedisAccessor', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'redis-clip-accessor.handler',
      code: lambda.Code.fromAsset('lambda/redis-clip-accessor'),
      functionName: 'redis-recommendation-accessor',
      timeout: cdk.Duration.minutes(1),
      layers: [externalLayer, redisLayerFromRequirement],
      memorySize: 1024,
      vpc: vpc,
      vpcSubnets: {
        subnets: lambdaSubnets,
      },
      environment: {
        ML_REDIS_URL: RECOMMENDATION_REDIS_URL,
      },
    });
    const redisClipAccessor = new lambda.Function(this, 'redisClipAccessor', {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: 'redis-clip-accessor.handler',
      code: lambda.Code.fromAsset('lambda/redis-clip-accessor'),
      functionName: 'redis-clip-accessor',
      timeout: cdk.Duration.minutes(1),
      layers: [externalLayer, redisLayerFromRequirement],
      memorySize: 1024,
      vpc: vpc,
      vpcSubnets: {
        subnets: lambdaSubnets,
      },
      environment: {
        ML_REDIS_URL: ML_REDIS_URL,
      },
    });

    secret.grantRead(redisClipAccessor);
    secret.grantRead(orpheusRedisAccessor);
    secret.grantRead(recommendationRedisAccessor);
  }
}
