import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ssm from "aws-cdk-lib/aws-ssm"
import * as acm from "aws-cdk-lib/aws-certificatemanager"
import * as route53 from "aws-cdk-lib/aws-route53"
import * as route53Targets from "aws-cdk-lib/aws-route53-targets"
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"
import * as path from "path";

interface StackProps extends cdk.StackProps {
  environment: string
}

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

    const table = new dynamodb.TableV2(this, "ghostv2-table", {
      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
      sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
      billing: dynamodb.Billing.onDemand(),
    });
    table.addGlobalSecondaryIndex({
      indexName: "gsi1",
      partitionKey: { name: "gsi1pk", type: dynamodb.AttributeType.STRING },
      sortKey: { name: "gsi1sk", type: dynamodb.AttributeType.STRING },
    });
    if (props.environment !== "prod") {
      return;
    }

    const vpc = new ec2.Vpc(this, "ghostv2 vpc", {
      maxAzs: 2,
      natGateways: 0,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: ec2.SubnetType.PUBLIC,
          cidrMask: 24,
        },
      ],
    });

    const ecsSecurityGroup = new ec2.SecurityGroup(this, "ghostv2 security group", {
      vpc,
      description: "Security group for ECS Fargate tasks",
      allowAllOutbound: true,
    });
    ecsSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      "Allow HTTP"
    );
    ecsSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(443),
      "Allow HTTPS"
    );
    ecsSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(8080),
      "Allow API"
    );

    const cluster = new ecs.Cluster(this, "ghostv2 cluster", {
      vpc,
    });
    const taskDefinition = new ecs.FargateTaskDefinition(
      this,
      "ghostv2 api",
      {
        memoryLimitMiB: 512,
        cpu: 256,
        runtimePlatform: {
          cpuArchitecture: ecs.CpuArchitecture.X86_64,
          operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
        },
      }
    );
    const secretImports = ["DATABASE_URL", "GH_CLIENT_ID", "GH_CLIENT_SECRET", "REPOS_TABLE_NAME", "SENTRY_DSN", "FRONTEND_BASE_URL"];
    const secrets = secretImports.reduce<Record<string, ecs.Secret>>((acc, name) => {
      acc[name] = ecs.Secret.fromSsmParameter(ssm.StringParameter.fromSecureStringParameterAttributes(this, `param${name}`, {
        parameterName: `/ghostv2/${props.environment}/${name}`
      }))
      return acc
    }, {})
    taskDefinition.addContainer("api", {
      image: ecs.ContainerImage.fromAsset(path.join(__dirname, "../../api")),
      environment: {
        RUST_LOG: "info",
      },
      portMappings: [
        {
          containerPort: 8080,
          protocol: ecs.Protocol.TCP,
        },
      ],
      secrets
    });
    table.grantReadWriteData(taskDefinition.taskRole);
    const service = new ecs.FargateService(this, "ghostv2 service", {
      cluster,
      taskDefinition,
      desiredCount: 1,
      assignPublicIp: true,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      securityGroups: [ecsSecurityGroup],
      capacityProviderStrategies: [
        {
          capacityProvider: "FARGATE_SPOT",
          weight: 1,
        },
      ],
    });
    const hostedZone = route53.HostedZone.fromLookup(this, "hosted zone", { domainName: "lucalise.ca" })
    const certificate = new acm.Certificate(this, "ghostv2 api cert", {
      domainName: "api.ghostv2.lucalise.ca",
      validation: acm.CertificateValidation.fromDns(hostedZone)
    })
    const alb = new elbv2.ApplicationLoadBalancer(this, "ghostv2 alb", {
      vpc,
      internetFacing: true,
      securityGroup: ecsSecurityGroup,
    });
    alb.setAttribute("idle_timeout.timeout_seconds", "120")
    alb.addRedirect({ sourcePort: 80, sourceProtocol: elbv2.ApplicationProtocol.HTTP, targetPort: 443, targetProtocol: elbv2.ApplicationProtocol.HTTPS })
    const listener = alb.addListener("https listener", { port: 443, open: true, certificates: [certificate] })
    listener.addTargets("ECSTargets", { port: 8080, protocol: elbv2.ApplicationProtocol.HTTP, targets: [service], healthCheck: { path: "/", port: "8080" } })
    new route53.ARecord(this, "alb record", { zone: hostedZone, recordName: "api.ghostv2.lucalise.ca", target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(alb)) })
  }
}
