import { Construct } from 'constructs';
import * as certManager from 'aws-cdk-lib/aws-certificatemanager';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
import { ISubnet, SecurityGroup } from 'aws-cdk-lib/aws-ec2';
import * as aws_apigateway from 'aws-cdk-lib/aws-apigateway';
import { Duration, Tags } from 'aws-cdk-lib';

export const createEventLoggerApiGateway = async (scope: Construct, certificate: certManager.ICertificate, accountStage: string, region: string) => {

    const LAMBDA_TIMEOUT_SECONDS = 60;
  
    const lambdaID = 'EventLoggerKinesisFilterLambda';
    const lambdaNamePrefix = 'event-logger-kinesis-filter';
  
    const iamID = 'ApiGatewayLambdaRole';
    const iamNamePrefix = 'EventLoggerApiGatewayLambdaRole';

    const apiName = 'event-logger';
    const cdkTagKey = 'CreatedBy';
    const cdkTagValue = 'CDK';

    const regionMapping = {
        'us-east-2': 'stratovibe',
        'eu-central-1': 'chromadrone',
        'ap-southeast-1': 'zephyrtone',
    };

    // Create IAM Role for API Gateway to assume
    const apiGatewayRole = new iam.Role(scope, iamID, {
        roleName: `${iamNamePrefix}-${region}`,
        assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
        managedPolicies: [
            iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonKinesisFullAccess'),
            iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonKinesisFirehoseFullAccess'),
            iam.ManagedPolicy.fromManagedPolicyArn(
                scope,
                'EventLoggerApiGatewayKinesisRolePolicy',
                'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs'
            ),
            iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonKinesisAnalyticsFullAccess'),
            iam.ManagedPolicy.fromAwsManagedPolicyName('AWSLambda_FullAccess'),
        ],
    });

    // Create the Lambda function for the API Gateway to send data to
    const eventLoggerFilterLambda = new lambda.Function(scope, lambdaID, {
        runtime: lambda.Runtime.PYTHON_3_10,
        handler: 'main.lambda_handler',
        code: lambda.Code.fromAsset(`lambda/${lambdaNamePrefix}`),
        timeout: Duration.seconds(LAMBDA_TIMEOUT_SECONDS),
        functionName: `${lambdaNamePrefix}-lambda`,
        logRetention: logs.RetentionDays.TWO_WEEKS,
    });

    eventLoggerFilterLambda.role?.addManagedPolicy(
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonKinesisFullAccess'),
    )

    eventLoggerFilterLambda.addToRolePolicy(
        new iam.PolicyStatement({
            actions: ['cloudwatch:PutMetricData'],
            resources: ['*'],
        }),
    );

    const domainName = new aws_apigateway.DomainName(scope, `EventLogger${accountStage.charAt(0).toUpperCase() + accountStage.slice(1)}Domain`, {
        domainName: `m-${regionMapping[region as keyof typeof regionMapping]}.${accountStage}.suno.com`,
        certificate,
    });

    // Create a CloudWatch Log Group
    const logGroup = new logs.LogGroup(scope, 'MetricsApiGatewayAccessLogs', {
        retention: logs.RetentionDays.ONE_WEEK,
    });

    let baseURL = 'https://suno.com';
    if (accountStage === 'staging') {
        baseURL = 'https://b.suno.fm';
    }

    const api = new aws_apigateway.RestApi(scope, 'EventLoggerApiGateway', {
        restApiName: apiName,
        description: 'This api gateway serves the event logger stream.',
        deployOptions: {
            stageName: 'prod',
            loggingLevel: aws_apigateway.MethodLoggingLevel.INFO,
            dataTraceEnabled: true,
            accessLogDestination: new aws_apigateway.LogGroupLogDestination(logGroup),
            accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields(),
        },
    });

    // Set the CloudWatch Logs role ARN in account settings
    const cloudWatchLogsRoleArn = apiGatewayRole.roleArn;

    new aws_apigateway.CfnAccount(scope, 'ApiGatewayAccount', {
        cloudWatchRoleArn: cloudWatchLogsRoleArn,
    });

    // Define the mock integration
    const metricsMockIntegration = new aws_apigateway.MockIntegration({
        integrationResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': "'*'",
                    'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                    'method.response.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
                },
            },
        ],
        requestTemplates: {
            'application/json': JSON.stringify({
                statusCode: 200,
            }),
        },
    });

    // Define the mock integration
    const metricsMockGetIntegration = new aws_apigateway.MockIntegration({
        integrationResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
                    'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                    'method.response.header.Access-Control-Allow-Methods': "'GET,OPTIONS'",
                },
            },
        ],
        requestTemplates: {
            'application/json': JSON.stringify({
                statusCode: 200,
            }),
        },
    });

    // Define the mock integration
    const metricsMockTIntegration = new aws_apigateway.MockIntegration({
        integrationResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
                    'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                    'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,PUT'",
                    'method.response.header.Access-Control-Max-Age': "'3600'",
                },
            },
        ],
        requestTemplates: {
            'application/json': JSON.stringify({
                statusCode: 200,
            }),
        },
    });

    // Define an empty model
    const emptyModel = new aws_apigateway.Model(scope, 'EmptyModel', {
        modelName: 'EmptyModel',
        restApi: api,
        contentType: 'application/json',
        schema: { schema: aws_apigateway.JsonSchemaVersion.DRAFT4, title: 'Empty Schema', type: aws_apigateway.JsonSchemaType.OBJECT },
    });

    // Define an error model
    const errorModel = new aws_apigateway.Model(scope, 'ErrorModel', {
        modelName: 'ErrorModel',
        restApi: api,
        contentType: 'application/json',
        schema: {
            schema: aws_apigateway.JsonSchemaVersion.DRAFT4,
            title: 'Error Schema',
            type: aws_apigateway.JsonSchemaType.OBJECT,
            properties: {
                message: { type: aws_apigateway.JsonSchemaType.STRING },
                // code: { type: aws_apigateway.JsonSchemaType.STRING },
            },
            // required: ['message', 'code'],
            required: ['message'],
        },
    });

    // Define the AWS integration
    const metricsBAWSIntegration = new aws_apigateway.AwsIntegration({
        service: 'kinesis',
        action: 'PutRecord',
        integrationHttpMethod: 'POST',
        options: {
            credentialsRole: apiGatewayRole,
            integrationResponses: [
                {
                    statusCode: '200',
                },
            ],
            requestTemplates: {
                'application/json': `
#set($inputRoot = $input.path('$'))
{
    "StreamName": "event-logger-temp-batched",
    "StreamARN": "arn:aws:kinesis:us-east-2:734185074900:stream/event-logger-temp-batched",
    "Records": [
    #foreach($elem in $inputRoot.batch)
        #set($pathBegin = "$.batch[")
        #set($pathEnd = "]")
        #set($currentIndex = $foreach.index)
        #set($thePath = "$pathBegin$currentIndex$pathEnd")
        #set($elemJson = $input.json($thePath)) 
        #set($request_data = "
            {
            ""request_body"": ""$util.base64Encode($elemJson)"",
            ""x-client-ip"": ""$context.identity.sourceIp""
            }
        ")
        {
            "Data": "$util.base64Encode($request_data)",
            "PartitionKey": "$elem.anonymousId"
        }#if($foreach.hasNext),#end
    #end
    ]
}
                    `,
            },
            requestParameters: {
                'integration.request.header.Content-Type': "'x-amz-json-1.1'",
            },
        },
    });

    // Define the AWS integration
    const metricsTAWSIntegration = new aws_apigateway.AwsIntegration({
        service: 'kinesis',
        action: 'PutRecord',
        integrationHttpMethod: 'POST',
        options: {
            credentialsRole: apiGatewayRole,
            integrationResponses: [
                {
                    statusCode: '200',
                    responseTemplates: {
                        'application/json': JSON.stringify({
                            status: 200,
                        }),
                    },
                    responseParameters: {
                        'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
                    },
                },
            ],

            requestTemplates: {
                'application/json': `
#set($inputRoot = $input.path('$'))
#set($request_data = "{
    ""request_body"": ""$util.base64Encode($input.json('$'))"",
    ""x-client-ip"": ""$context.identity.sourceIp"",
    ""request-time"": ""$context.requestTime"",
    ""domain-name"": ""$context.domainName""
}")
{
    "StreamName": "event-logger-${region}",
    "Data": "$util.base64Encode($request_data)",
    "PartitionKey": "$.properties.userId"
}
                        `,
            },
            requestParameters: {
                'integration.request.header.Content-Type': "'x-amz-json-1.1'",
            },
        },
    });

    const metricsPostTAWSIntegration = new aws_apigateway.AwsIntegration({
        service: 'kinesis',
        action: 'PutRecord',
        integrationHttpMethod: 'POST',
        options: {
            credentialsRole: apiGatewayRole,
            integrationResponses: [
                {
                    statusCode: '200',
                    responseTemplates: {
                        'application/json': JSON.stringify({
                            status: 200,
                        }),
                    },
                    responseParameters: {
                        'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
                    },
                },
            ],

            requestTemplates: {
                'text/plain': `
#set($inputRoot = $input.path('$'))
#set($request_data = "{
    ""request_body"": ""$util.base64Encode($input.json('$'))"",
    ""x-client-ip"": ""$context.identity.sourceIp"",
    ""request-time"": ""$context.requestTime"",
    ""domain-name"": ""$context.domainName""
}")
{
    "StreamName": "event-logger-${region}",
    "Data": "$util.base64Encode($request_data)",
    "PartitionKey": "$.properties.userId"
}
                        `,
            },
            requestParameters: {
                'integration.request.header.Content-Type': "'x-amz-json-1.1'",
            },
        },
    });

    const mockMethodResponses = [
        {
            statusCode: '200',
            responseParameters: {
                'method.response.header.Access-Control-Allow-Headers': true,
                'method.response.header.Access-Control-Allow-Methods': true,
                'method.response.header.Access-Control-Allow-Origin': true,
            },
            responseModels: {
                'application/json': emptyModel,
            },
        },
    ];

    const mockMethodTResponses = [
        {
            statusCode: '200',
            responseParameters: {
                'method.response.header.Access-Control-Allow-Headers': true,
                'method.response.header.Access-Control-Allow-Methods': true,
                'method.response.header.Access-Control-Allow-Origin': true,
                'method.response.header.Access-Control-Max-Age': true,
            },
            responseModels: {
                'application/json': emptyModel,
            },
        },
    ];

    const mockIntegrationResponseTemplate = `
    {
        "integrations": {
            "Segment.io": {
                "apiKey": "suno",
                "host":"https://d34bueduid6uip.cloudfront.net/dev-testing/metrics/v1",
                "unbundledIntegrations": [],
                "addBundledMetadata": true,
                "maybeBundledConfigIds": {},
                "versionSettings": {
                    "version": "4.4.7",
                    "componentTypes": [
                        "browser"
                    ]
                },
                "retryQueue": false
            }
        },
        "plan": {
            "track": {
                "__default": {
                    "enabled": true,
                    "integrations": {}
                }
            },
            "identify": {
                "__default": {
                    "enabled": true
                }
            },
            "group": {
                "__default": {
                    "enabled": true
                }
            }
        },
        "edgeFunction": {},
        "analyticsNextEnabled": true,
        "middlewareSettings": {},
        "enabledMiddleware": {},
        "metrics": {
            "sampleRate": 0.1
        },
        "legacyVideoPluginsEnabled": false,
        "remotePlugins": []
    }
    `;

    const mockGetIntegration = new aws_apigateway.MockIntegration({
        integrationResponses: [
            {
                statusCode: '200',
                responseTemplates: {
                    'application/json': mockIntegrationResponseTemplate,
                },
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
                },
            },
        ],
        passthroughBehavior: aws_apigateway.PassthroughBehavior.WHEN_NO_MATCH,
        requestTemplates: {
            'application/json': '{"statusCode": 200}',
        },
    });

    const awsMethodResponses = [
        {
            statusCode: '200',
            responseModels: {
                'application/json': emptyModel,
            },
        },
    ];

    // Check if the 'metrics' resource already exists
    // This is the old resource name, and we should remove it after the migration
    let metricsResource = api.root.getResource('metrics');
    if (!metricsResource) {
        metricsResource = api.root.addResource('metrics');
    }

    // Add a resource and method to the API
    const metricsBResource = metricsResource.addResource('b');
    metricsBResource.addMethod('OPTIONS', metricsMockIntegration, {
        methodResponses: mockMethodResponses,
    });

    metricsBResource.addMethod('POST', metricsBAWSIntegration, {
        methodResponses: awsMethodResponses,
    });

    const metricsListenHistoryResource = metricsResource.addResource('listen-history');
    metricsListenHistoryResource.addMethod('OPTIONS', metricsMockIntegration, {
        methodResponses: mockMethodResponses,
    });

    metricsListenHistoryResource.addMethod('PUT', metricsBAWSIntegration, {
        authorizationType: aws_apigateway.AuthorizationType.IAM,
        requestModels: {
            'application/json': emptyModel,
        },
        methodResponses: awsMethodResponses,
    });

    const metricsProjectsResource = metricsResource.addResource('projects');
    const metricsProjectsSunoResource = metricsProjectsResource.addResource('suno');
    const metricsProjectsSunoSettingsResource = metricsProjectsSunoResource.addResource('settings');
    metricsProjectsSunoSettingsResource.addMethod('OPTIONS', metricsMockGetIntegration, {
        methodResponses: mockMethodResponses,
    });

    metricsProjectsSunoSettingsResource.addMethod('GET', mockGetIntegration, {
        methodResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': true,
                },
                responseModels: {
                    'application/json': emptyModel,
                },
            },
        ],
    });

    const metricsTResource = metricsResource.addResource('t');

    metricsTResource.addMethod('OPTIONS', metricsMockTIntegration, {
        methodResponses: mockMethodTResponses,
    });

    metricsTResource.addMethod('PUT', metricsTAWSIntegration, {
        methodResponses: mockMethodTResponses,
    });

    metricsTResource.addMethod('POST', metricsPostTAWSIntegration, {
        methodResponses: mockMethodTResponses,
    });

    // This is the new resource name, and we should use it after the migration
    const metricsNewResource = api.root.addResource('agg-receiver-service').addResource('v1').addResource('events');
    if (!metricsNewResource) {
        throw new Error('metrics resource not found');
    }

    // Add a resource and method to the API
    const metricsBNewResource = metricsNewResource.addResource('b');
    metricsBNewResource.addMethod('OPTIONS', metricsMockIntegration, {
        methodResponses: mockMethodResponses,
    });

    // the response template that will be returned as the response to the http request
    const integrationResponseTemplateValue = JSON.stringify({'message': 'success'});
    // the format in which the request will be sent to the lambda function
    const requestTemplateValue = `
    #set($inputRoot = $input.path('$'))
    {
        "StreamName": "event-logger-${region}",
        "ClientIP": "$context.identity.sourceIp",
        "RequestTime": "$context.requestTime",
        "DomainName": "$context.domainName",
        "Data": $inputRoot.batch
    }
        `;

    // setting up the lambda integration in API Gateway
    const metricsBNewAWSLambdaIntegration = new aws_apigateway.LambdaIntegration(eventLoggerFilterLambda, {
        proxy: false,
        integrationResponses: [{
            statusCode: '200',
            responseTemplates: {
              'application/json': integrationResponseTemplateValue,
              'text/plain': integrationResponseTemplateValue
            },
            responseParameters: {
                'method.response.header.Access-Control-Allow-Origin': `'${baseURL}'`,
            },
        }],
        passthroughBehavior: aws_apigateway.PassthroughBehavior.NEVER,
        requestTemplates: {
            'text/plain': requestTemplateValue,
            'application/json': requestTemplateValue
        },
    });

    metricsBNewResource.addMethod('POST', metricsBNewAWSLambdaIntegration, {
        methodResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': true,
                },
                responseModels: {
                    'application/json': emptyModel,
                },
            },
        ],
    });

    const metricsNewListenHistoryResource = metricsNewResource.addResource('listen-history');
    metricsNewListenHistoryResource.addMethod('OPTIONS', metricsMockIntegration, {
        methodResponses: mockMethodResponses,
    });

    metricsNewListenHistoryResource.addMethod('PUT', metricsBAWSIntegration, {
        authorizationType: aws_apigateway.AuthorizationType.IAM,
        requestModels: {
            'application/json': emptyModel,
        },
        methodResponses: awsMethodResponses,
    });

    const metricsProjectsNewResource = metricsNewResource.addResource('projects');
    const metricsProjectsSunoNewResource = metricsProjectsNewResource.addResource('suno');
    const metricsProjectsSunoSettingsNewResource = metricsProjectsSunoNewResource.addResource('settings');
    metricsProjectsSunoSettingsNewResource.addMethod('OPTIONS', metricsMockGetIntegration, {
        methodResponses: mockMethodResponses,
    });

    metricsProjectsSunoSettingsNewResource.addMethod('GET', mockGetIntegration, {
        methodResponses: [
            {
                statusCode: '200',
                responseParameters: {
                    'method.response.header.Access-Control-Allow-Origin': true,
                },
                responseModels: {
                    'application/json': emptyModel,
                },
            },
        ],
    });

    const metricsTNewResource = metricsNewResource.addResource('t');

    metricsTNewResource.addMethod('OPTIONS', metricsMockTIntegration, {
        methodResponses: mockMethodTResponses,
    });

    metricsTNewResource.addMethod('PUT', metricsTAWSIntegration, {
        methodResponses: mockMethodTResponses,
    });

    metricsTNewResource.addMethod('POST', metricsPostTAWSIntegration, {
        methodResponses: mockMethodTResponses,
    });



    // Create an API mapping
    new aws_apigateway.BasePathMapping(scope, 'MetricsLoggerPathMapping', {
        domainName: domainName,
        restApi: api,
        stage: api.deploymentStage,
    });

    // Tag the API Gateway to indicate it was created by CDK
    Tags.of(api).add(cdkTagKey, cdkTagValue);

    return api;
};
