import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3n from "aws-cdk-lib/aws-s3-notifications";
import * as sqs from "aws-cdk-lib/aws-sqs";
import { Construct } from "constructs";
import * as path from "path";

interface ImageProcessorLambdaStackProps extends cdk.StackProps {
  /**
   * Environment name (staging, prod)
   */
  environment: string;

  /**
   * Database URL for the Lambda
   */
  databaseUrl: string;

  /**
   * Frontend URL for CORS configuration
   */
  frontendUrl: string;
}

export class ImageProcessorLambdaStack extends cdk.Stack {
  /**
   * The Lambda function for image processing
   */
  public readonly lambdaFunction: lambda.Function;

  /**
   * The S3 bucket for image uploads
   */
  public readonly uploadsBucket: s3.Bucket;

  /**
   * Dead letter queue for failed Lambda invocations
   */
  public readonly deadLetterQueue: sqs.Queue;

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

    const { environment, databaseUrl, frontendUrl } = props;

    // Dead Letter Queue for failed Lambda invocations
    this.deadLetterQueue = new sqs.Queue(this, "ImageProcessorDLQ", {
      queueName: `image-processor-dlq-${environment}`,
      retentionPeriod: cdk.Duration.days(14),
    });

    // S3 bucket for uploads
    this.uploadsBucket = new s3.Bucket(this, "UploadsBucket", {
      bucketName: `village-share-${environment}`,
      versioned: true,
      encryption: s3.BucketEncryption.S3_MANAGED,
      blockPublicAccess: new s3.BlockPublicAccess({
        blockPublicAcls: true,
        blockPublicPolicy: false, // Allow bucket policy for public reads
        ignorePublicAcls: true,
        restrictPublicBuckets: false,
      }),
      cors: [
        {
          allowedMethods: [
            s3.HttpMethods.GET,
            s3.HttpMethods.PUT,
            s3.HttpMethods.POST,
          ],
          allowedOrigins: [frontendUrl, "http://localhost:3000"],
          allowedHeaders: ["*"],
          exposedHeaders: ["ETag"],
          maxAge: 3600,
        },
      ],
      lifecycleRules: [
        {
          // Clean up incomplete multipart uploads
          abortIncompleteMultipartUploadAfter: cdk.Duration.days(1),
        },
        {
          // Delete staging files that weren't processed after 7 days
          prefix: "staging/",
          expiration: cdk.Duration.days(7),
        },
      ],
    });

    // Bucket policy: allow public read for non-staging files
    this.uploadsBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        principals: [new iam.AnyPrincipal()],
        actions: ["s3:GetObject"],
        resources: [
          `${this.uploadsBucket.bucketArn}/profiles/*`,
          `${this.uploadsBucket.bucketArn}/items/*`,
          `${this.uploadsBucket.bucketArn}/forum/*`,
        ],
      })
    );

    // Lambda execution role
    const lambdaRole = new iam.Role(this, "ImageProcessorLambdaRole", {
      roleName: `image-processor-lambda-role-${environment}`,
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
      description: "Execution role for Image Processor Lambda",
    });

    // CloudWatch Logs permissions
    lambdaRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
        ],
        resources: ["*"],
      })
    );

    // S3 permissions
    lambdaRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          "s3:GetObject",
          "s3:PutObject",
          "s3:DeleteObject",
          "s3:HeadObject",
        ],
        resources: [`${this.uploadsBucket.bucketArn}/*`],
      })
    );

    // Lambda function
    this.lambdaFunction = new lambda.Function(this, "ImageProcessorLambda", {
      functionName: `image-processor-${environment}`,
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset(
        path.join(__dirname, "../../../lambdas/imageProcessor"),
        {
          bundling: {
            image: lambda.Runtime.NODEJS_20_X.bundlingImage,
            command: [
              "bash",
              "-c",
              [
                "cp -r /asset-input/* /asset-output/",
                "cd /asset-output",
                "npm install --omit=dev",
                // Copy shared modules
                "mkdir -p shared",
                "cp -r /asset-input/../shared/* shared/",
                "cd shared && npm install --omit=dev",
              ].join(" && "),
            ],
          },
        }
      ),
      role: lambdaRole,
      timeout: cdk.Duration.seconds(60),
      memorySize: 1024, // Higher memory for image processing
      environment: {
        NODE_ENV: environment,
        DATABASE_URL: databaseUrl,
        S3_BUCKET: this.uploadsBucket.bucketName,
        AWS_REGION: this.region,
        LOG_LEVEL: environment === "prod" ? "info" : "debug",
      },
      deadLetterQueue: this.deadLetterQueue,
      retryAttempts: 2,
      description:
        "Processes uploaded images: extracts metadata and strips EXIF",
    });

    // S3 event notification for staging uploads
    this.uploadsBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED,
      new s3n.LambdaDestination(this.lambdaFunction),
      {
        prefix: "staging/",
      }
    );

    // Outputs
    new cdk.CfnOutput(this, "LambdaFunctionArn", {
      value: this.lambdaFunction.functionArn,
      description: "ARN of the Image Processor Lambda",
      exportName: `ImageProcessorLambdaArn-${environment}`,
    });

    new cdk.CfnOutput(this, "UploadsBucketName", {
      value: this.uploadsBucket.bucketName,
      description: "Name of the uploads S3 bucket",
      exportName: `UploadsBucketName-${environment}`,
    });

    new cdk.CfnOutput(this, "UploadsBucketArn", {
      value: this.uploadsBucket.bucketArn,
      description: "ARN of the uploads S3 bucket",
      exportName: `UploadsBucketArn-${environment}`,
    });

    new cdk.CfnOutput(this, "DLQUrl", {
      value: this.deadLetterQueue.queueUrl,
      description: "URL of the Dead Letter Queue",
      exportName: `ImageProcessorDLQUrl-${environment}`,
    });
  }
}
