import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import {
  AuroraPostgresEngineVersion,
  ClusterInstance,
  Credentials,
  DatabaseCluster,
  DatabaseClusterEngine,
  DatabaseInsightsMode,
  DatabaseSecret,
  ParameterGroup,
  PerformanceInsightRetention,
} from "aws-cdk-lib/aws-rds";
import * as ssm from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
import { Context } from "../lib/context";

interface StackProps extends cdk.StackProps {
  readonly stage: string;
  readonly vpc: ec2.IVpc;
  readonly dbProps: Context["dbProps"];
}

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

    const { stage, vpc, dbProps } = props;

    //${tenant}用のPGVector
    const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
      securityGroupName: `${stage}-db-sg`,
      vpc,
    });
    securityGroup.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(Number("5432")));

    const engine = DatabaseClusterEngine.auroraPostgres({
      version: AuroraPostgresEngineVersion.VER_16_8,
    });

    const customParameterGroup = new ParameterGroup(this, "CustomParameterGroup", {
      engine: engine,
      parameters: {
        // pg_stat_statementsはデフォルトパラメータグループで有効化されているもの
        shared_preload_libraries: "pg_stat_statements, pg_cron",
        log_min_duration_statement: "3000", // 3秒以上のクエリは statement テキスト付きでログに出力される
        log_statement: "none", // 通常の全件SQL文ログは出力しない（ただし上記スロークエリログは別）
        log_destination: "stderr", // ログの出力先をstderrに設定（CloudWatch Logsに転送される）
      },
    });

    const cluster = new DatabaseCluster(this, "DBCluster", {
      clusterIdentifier: `${stage}-db-cluster`,
      engine: engine,
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      securityGroups: [securityGroup],
      defaultDatabaseName: "postgres",
      enableDataApi: true,
      storageEncrypted: true,
      serverlessV2MinCapacity: dbProps.minAcuCapacity,
      serverlessV2MaxCapacity: dbProps.maxAcuCapacity,
      serverlessV2AutoPauseDuration: dbProps.minAcuCapacity === 0 ? cdk.Duration.minutes(10) : undefined,
      writer: ClusterInstance.serverlessV2("writer", {
        autoMinorVersionUpgrade: false,
      }),
      parameterGroup: customParameterGroup,
      credentials: Credentials.fromGeneratedSecret("postgres", {
        secretName: `${stage}-db-admin-credentials`,
      }),
      enablePerformanceInsights: dbProps.enableInsights,
      performanceInsightRetention: dbProps.enableInsights ? PerformanceInsightRetention.MONTHS_15 : undefined,
      databaseInsightsMode: dbProps.enableInsights ? DatabaseInsightsMode.ADVANCED : undefined,
      cloudwatchLogsExports: ["postgresql"],
      backup: undefined, // AWS Backupで期限が再設定されるため、デフォルト値を無効化する
    });

    const appSecret = new DatabaseSecret(this, "AppSecret", {
      username: "app",
      masterSecret: cluster.secret,
      secretName: `${stage}-db-app-credentials`,
    });
    // クライアント開発者向け
    const clientDeveloperSecret = new DatabaseSecret(this, "ClientDeveloperSecret", {
      username: "client_developer",
      masterSecret: cluster.secret,
      secretName: `${stage}-db-client-developer-credentials`,
    });
    const watcherSecret = new DatabaseSecret(this, "WatcherSecret", {
      username: "watcher",
      masterSecret: cluster.secret,
      secretName: `${stage}-db-watcher-credentials`,
    });

    let host = cluster.clusterEndpoint.hostname;
    // Proxy
    if (dbProps.enableRdsProxy) {
      const proxy = cluster.addProxy("DBProxy", {
        dbProxyName: `${stage}-db-proxy`,
        secrets: [appSecret],
        vpc,
        vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
        securityGroups: [securityGroup],
        requireTLS: false,
        maxConnectionsPercent: 100,
      });
      // 接続先をProxyのエンドポイントに上書き
      host = proxy.endpoint;
    }

    new ssm.StringParameter(this, "DBAdminSecretName", {
      parameterName: `/${stage}/db-admin-secret-name`,
      stringValue: cluster.secret!.secretName,
      tier: ssm.ParameterTier.STANDARD,
    });

    new ssm.StringParameter(this, "DBAppSecretName", {
      parameterName: `/${stage}/db-app-secret-name`,
      stringValue: appSecret.secretName,
      tier: ssm.ParameterTier.STANDARD,
    });

    new ssm.StringParameter(this, "DBClientDeveloperSecretName", {
      parameterName: `/${stage}/db-client-developer-secret-name`,
      stringValue: clientDeveloperSecret.secretName,
      tier: ssm.ParameterTier.STANDARD,
    });

    new ssm.StringParameter(this, "DBWatcherSecretName", {
      parameterName: `/${stage}/db-watcher-secret-name`,
      stringValue: watcherSecret.secretName,
      tier: ssm.ParameterTier.STANDARD,
    });

    new ssm.StringParameter(this, "DBEndpoint", {
      parameterName: `/${stage}/db-host`,
      stringValue: host,
      tier: ssm.ParameterTier.STANDARD,
    });
    new ssm.StringParameter(this, "DBPort", {
      parameterName: `/${stage}/db-port`,
      stringValue: cluster.clusterEndpoint.port.toString(),
      tier: ssm.ParameterTier.STANDARD,
    });
  }
}
