import {
    Stack,
    StackProps,
    aws_apigateway as apigateway,
    aws_lambda,
    aws_ec2,
    Duration,
} from 'aws-cdk-lib';
import * as aws_lambda_python_alpha from '@aws-cdk/aws-lambda-python-alpha';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { join } from 'path';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { BuildConfig } from '../utils/build-config';
import { Period } from 'aws-cdk-lib/aws-apigateway';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { VpcEndpoint, VpcEndpointService } from 'aws-cdk-lib/aws-ec2';
import { PolicyStatement, PolicyDocument, Effect, AnyPrincipal } from 'aws-cdk-lib/aws-iam';


export class MccAPIInfraStack extends Stack {
    public readonly api: apigateway.RestApi;

    constructor(
        scope: Construct,
        id: string,
        buildConfig: BuildConfig,
        props: StackProps,
    ) {
        super(scope, id, props);

        const stackName = `${buildConfig.Environment}${buildConfig.ApplicationShortName}`;

        const vpc = aws_ec2.Vpc.fromLookup(this, `${stackName}Vpc`, {
            vpcId: buildConfig.Parameters.VPC,
        });

        const executeApiVpcEndpointSecurityGroup = new aws_ec2.SecurityGroup(this, `${stackName}ExecuteApiVPCEsg`, {
            vpc,
            description: 'Security group for API Gateway VPC Endpoint',
            allowAllOutbound: true,
        });

        executeApiVpcEndpointSecurityGroup.addIngressRule(
            aws_ec2.Peer.ipv4('10.0.0.0/8'),
            aws_ec2.Port.tcp(443),
            'Allow inbound HTTPS traffic from 10.0.0.0/8'
        );

        const executeApiVpcEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `${stackName}ExecuteApiVPCE`, {
            vpc,
            service: aws_ec2.InterfaceVpcEndpointAwsService.APIGATEWAY,
            privateDnsEnabled: true,
            subnets: { subnetType: aws_ec2.SubnetType.PRIVATE_WITH_EGRESS },
            securityGroups: [executeApiVpcEndpointSecurityGroup]
        });

        const lambdaFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCApp`, {
            runtime: Runtime.PYTHON_3_11,
            handler: 'lambda_handler',
            timeout: Duration.seconds(30),
            entry: join(__dirname, '../../src'),
            index: 'run.py',
            memorySize: 1024,
            vpc,
            logRetention: RetentionDays.ONE_WEEK,
            environment: {
                POSTGRES_USER: process.env.POSTGRES_USER!,
                POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!,
                POSTGRES_DB: 'mccdb',
                FLASK_APP: process.env.FLASK_APP!,
                FLASK_ENV: process.env.FLASK_ENV!,
                DATABASE_URL: process.env.DATABASE_URL!,
                HOST_ENV: process.env.HOST_ENV!,
                SECRET_KEY: process.env.SECRET_KEY!,
            },
        });

        const lambdaDBUpgradeFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCAppDBUpgrade`, {
            runtime: Runtime.PYTHON_3_11,
            handler: 'lambda_handler_upgrade_db',
            timeout: Duration.minutes(5),
            entry: join(__dirname, '../../src'),
            index: 'run.py',
            memorySize: 1024,
            vpc,
            logRetention: RetentionDays.ONE_WEEK,
            functionName: `${stackName}MCCAppDBUpgrade`,
            environment: {
                POSTGRES_USER: process.env.POSTGRES_USER!,
                POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!,
                POSTGRES_DB: 'mccdb',
                FLASK_APP: process.env.FLASK_APP!,
                FLASK_ENV: process.env.FLASK_ENV!,
                DATABASE_URL: process.env.DATABASE_URL!,
                HOST_ENV: process.env.HOST_ENV!,
                SECRET_KEY: process.env.SECRET_KEY!,
            },
        });


        const api = new apigateway.LambdaRestApi(this, `${stackName}MCCAPI`, {
            handler: lambdaFunction,
            proxy: true,
            deployOptions: {
                stageName: buildConfig.Environment,
            },
            restApiName: `${buildConfig.ApplicationName} API`,
            description: `API for ${buildConfig.ApplicationName} in ${buildConfig.Environment}`,
            endpointConfiguration: {
                types: [apigateway.EndpointType.PRIVATE],
                vpcEndpoints: [executeApiVpcEndpoint],
            },
            policy: new PolicyDocument({
                statements: [
                    new PolicyStatement({
                        effect: Effect.ALLOW,
                        principals: [new AnyPrincipal()],
                        actions: ['execute-api:Invoke'],
                        resources: ['execute-api:/*/*/*'],
                        // Optionally, restrict by source VPC or VPC endpoint
                        conditions: {
                             StringEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId }
                         }
                    }),
                    new PolicyStatement({
                        effect: Effect.DENY,
                        principals: [new AnyPrincipal()],
                        actions: ['execute-api:Invoke'],
                        resources: ['execute-api:/*/*/*'],
                        // Optionally, restrict by source VPC or VPC endpoint
                        conditions: {
                             StringNotEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId }
                         }
                    }),
                ],
            }),
        });

        this.api = api;
    }
}