import * as cdk from "aws-cdk-lib";
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as route53_targets from "aws-cdk-lib/aws-route53-targets";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3_deployment from "aws-cdk-lib/aws-s3-deployment";
import * as ssm from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
import { Context } from "../lib/context";
import { MANAGEMENT_CONSOLE_WILDCARD_DOMAIN_PREFIX } from "./const";
import { ApiDomain } from "./construct/api-domain";

interface StackProps extends cdk.StackProps {
  readonly stage: string;
  readonly frontendProps: Context["frontendProps"];
  readonly certificate: ICertificate;
}

export class FrontendStack extends cdk.Stack {
  public readonly managementConsoleLogBucket?: s3.IBucket;
  public readonly managementConsoleDistribution: cloudfront.IDistribution;

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

    const { stage, certificate, frontendProps } = props;

    // S3
    const hostingBucket = new s3.Bucket(this, "ManagementConsoleHostingBucket", {
      bucketName: `${stage}-management-console-hosting`,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true,
    });
    if (frontendProps.enableAccessLog) {
      // CloudFront ログ配信の設定は us-east-1 でやる必要があるため、ここではバケットの作成のみを行う
      // 配信設定は log-delivery-us-east-1-stack で定義する
      const logBucket = new s3.Bucket(this, "ManagementConsoleAccessLogsBucket", {
        bucketName: `${stage}-management-console-access-logs`,
        removalPolicy: cdk.RemovalPolicy.DESTROY,
        autoDeleteObjects: true,
        enforceSSL: true,
        lifecycleRules: [
          {
            expiration: cdk.Duration.days(frontendProps.logRetentionDays),
          },
        ],
      });
      this.managementConsoleLogBucket = logBucket;
    }

    // CloudFront
    const apiDomainName = ssm.StringParameter.valueForStringParameter(
      this,
      ApiDomain.parameterName(stage, `${stage}-management-api`),
    );

    const baseCSP =
      "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none';";
    const connectSrc = `connect-src 'self' https://${apiDomainName} https://cognito-idp.${this.region}.amazonaws.com;`;

    const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, "ManagementConsoleResponseHeadersPolicy", {
      responseHeadersPolicyName: `${stage}-management-console-response-headers-policy`,
      comment: "Security headers for management console distribution",
      securityHeadersBehavior: {
        contentSecurityPolicy: {
          contentSecurityPolicy: `${baseCSP} ${connectSrc}`,
          override: true,
        },
        contentTypeOptions: {
          override: true,
        },
        frameOptions: {
          frameOption: cloudfront.HeadersFrameOption.DENY,
          override: true,
        },
        referrerPolicy: {
          referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
          override: true,
        },
        strictTransportSecurity: {
          accessControlMaxAge: cdk.Duration.seconds(31536000),
          includeSubdomains: true,
          preload: true,
          override: true,
        },
        // xssProtection は ヘッダーの利用が非推奨とされているため、設定しない
      },
      removeHeaders: ["Server"],
    });

    const hostedZoneId = ssm.StringParameter.valueForStringParameter(this, `/${stage}/hosted-zone-id`);
    const hostedZoneName = ssm.StringParameter.valueForStringParameter(this, `/${stage}/hosted-zone-name`);
    const hostedZone = route53.HostedZone.fromHostedZoneAttributes(this, "Zone", {
      hostedZoneId,
      zoneName: hostedZoneName,
    });

    const indexFileName = "index.html";

    const distribution = new cloudfront.Distribution(this, "ManagementConsoleDistribution", {
      domainNames: [`${MANAGEMENT_CONSOLE_WILDCARD_DOMAIN_PREFIX}.${hostedZone.zoneName}`],
      certificate: certificate,
      defaultRootObject: indexFileName,
      defaultBehavior: {
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: origins.S3BucketOrigin.withOriginAccessControl(hostingBucket),
        responseHeadersPolicy: responseHeadersPolicy,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
      },
      minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
      errorResponses: [
        { httpStatus: 403, responseHttpStatus: 200, responsePagePath: `/${indexFileName}` },
        { httpStatus: 404, responseHttpStatus: 200, responsePagePath: `/${indexFileName}` },
      ],
    });
    this.managementConsoleDistribution = distribution;

    // Deployment
    const configFilePath = "runtime/config.json";
    const appConfig = {
      userPoolId: ssm.StringParameter.valueForStringParameter(this, `/${stage}/management-user-pool-id`),
      apiEndpoint: `https://${apiDomainName}`,
    };
    new s3_deployment.BucketDeployment(this, "DeployManagementConsole", {
      sources: [
        s3_deployment.Source.asset("../../frontend", {
          bundling: {
            image: cdk.DockerImage.fromRegistry("public.ecr.aws/docker/library/node:22"),
            command: ["bash", "-c", "npm ci && npm run build && cp -r dist/* /asset-output/"],
            bundlingFileAccess: cdk.BundlingFileAccess.VOLUME_COPY,
          },
          exclude: [configFilePath],
        }),
        s3_deployment.Source.jsonData(configFilePath, appConfig),
      ],
      destinationBucket: hostingBucket,
      distribution,
      distributionPaths: [`/${indexFileName}`, `/${configFilePath}`],
    });

    new route53.ARecord(this, "ManagementConsoleARecord", {
      zone: hostedZone,
      recordName: MANAGEMENT_CONSOLE_WILDCARD_DOMAIN_PREFIX,
      target: route53.RecordTarget.fromAlias(new route53_targets.CloudFrontTarget(distribution)),
    });
  }
}
