import * as cdk from "aws-cdk-lib";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as cognito from "aws-cdk-lib/aws-cognito";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as ssm from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
import { Context } from "../lib/context";
import {
  CONTRACT_USER_POOL_CUSTOM_DOMAIN_PREFIX,
  MANAGEMENT_CONSOLE_SUB_DOMAIN,
  MANAGEMENT_M2M_POOL_CUSTOM_DOMAIN_PREFIX,
  MANAGEMENT_USER_POOL_CUSTOM_DOMAIN_PREFIX,
} from "./const";
import { UserPoolCognitoDomain, UserPoolCustomDomain } from "./construct/user-pool-domain";
import { getLambdaArchitecture } from "./utils";

interface StackProps extends cdk.StackProps {
  stage: string;
  authProps: Context["authProps"];
  vpc: ec2.IVpc;
  hostedZone: route53.IHostedZone;
  managementM2MCertificate: acm.ICertificate;
  managementUserCertificate: acm.ICertificate;
  contractUserPoolCertificate: acm.ICertificate;
}

export class AuthStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    const { region } = new cdk.ScopedAws(this);
    const {
      stage,
      authProps,
      vpc,
      hostedZone,
      managementM2MCertificate,
      managementUserCertificate,
      contractUserPoolCertificate,
    } = props;

    const dbClusterSecretName = ssm.StringParameter.fromStringParameterAttributes(this, "DbClusterSecretName", {
      parameterName: `/${stage}/db-app-secret-name`,
      forceDynamicReference: true,
    }).stringValue;

    const dbClusterHost = ssm.StringParameter.fromStringParameterAttributes(this, "DbClusterHost", {
      parameterName: `/${stage}/db-host`,
      forceDynamicReference: true,
    }).stringValue;

    const dbClusterPort = ssm.StringParameter.fromStringParameterAttributes(this, "DbClusterPort", {
      parameterName: `/${stage}/db-port`,
      forceDynamicReference: true,
    }).stringValue;

    // Management API向けM2M認証基盤
    const managementPreTokenGenerationRole = new iam.Role(this, "PreTokenGenerationRole", {
      roleName: `${stage}-pre-token-generation-role`,
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("AWSSecretsManagerClientReadOnlyAccess"),
      ],
    });

    const managementPreTokenGenerationLambdaFunction = new lambda.Function(this, "PreTokenGenerationPythonFunction", {
      functionName: `${stage}-pre-token-generation-trigger-python`,
      code: lambda.Code.fromAsset("../../backend", {
        bundling: {
          image: lambda.Runtime.PYTHON_3_12.bundlingImage,
          command: ["bash", "-c", "pip install -r requirements.txt -t /asset-output && cp -au . /asset-output"],
          user: "root",
          bundlingFileAccess: cdk.BundlingFileAccess.VOLUME_COPY,
        },
      }),
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: "src.management_pre_token_generation_trigger.handler",
      architecture: getLambdaArchitecture(),
      memorySize: 1024,
      timeout: cdk.Duration.minutes(15),
      vpc,
      role: managementPreTokenGenerationRole,
      environment: {
        SERVICE: "management_pre_token_generation_trigger",
        POSTGRES_SECRET_NAME: dbClusterSecretName,
        POSTGRES_DB: "postgres",
        POSTGRES_HOST: dbClusterHost,
        POSTGRES_PORT: dbClusterPort,
        APPLICATION_LOG_LEVEL: authProps.applicationLogLevel,
      },
    });

    const managementM2MPool = new cognito.UserPool(this, "UserPool", {
      userPoolName: `${stage}-management-m2m-pool`,
      deletionProtection: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    managementM2MPool.addTrigger(
      cognito.UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,
      managementPreTokenGenerationLambdaFunction,
      cognito.LambdaVersion.V3_0,
    );

    // M2M認証を行うためには一つ以上のリソースサーバーが必要なため
    managementM2MPool.addResourceServer("ResourceServer", {
      identifier: "m2m",
      scopes: [
        {
          scopeName: "management",
          scopeDescription: "for management console",
        },
      ],
    });
    const managementM2MPoolDomain = authProps.enableUserPoolCustomDomain
      ? new UserPoolCustomDomain(this, "UserPoolCustomDomain", {
          userPool: managementM2MPool,
          hostedZone: hostedZone,
          domainPrefix: MANAGEMENT_M2M_POOL_CUSTOM_DOMAIN_PREFIX,
          certificate: managementM2MCertificate,
        })
      : new UserPoolCognitoDomain(this, "UserPoolCognitoDomain", {
          userPool: managementM2MPool,
          domainPrefix: `${stage}-ai-unit-works-hi`,
        });

    new ssm.StringParameter(this, "UserPoolIdParameter", {
      parameterName: `/${stage}/user-pool-id`,
      stringValue: managementM2MPool.userPoolId,
    });
    new ssm.StringParameter(this, "UserPoolOAuthTokenEndpointUrlParameter", {
      parameterName: `/${stage}/user-pool-oauth-token-endpoint-url`,
      stringValue: managementM2MPoolDomain.userPoolOAuthTokenEndpointUrl,
    });
    new ssm.StringParameter(this, "UserPoolJwksEndpointUrlParameter", {
      parameterName: `/${stage}/user-pool-jwks-endpoint-url`,
      stringValue: `https://cognito-idp.${region}.amazonaws.com/${managementM2MPool.userPoolId}/.well-known/jwks.json`,
    });

    // 管理画面 エンドユーザー認証用
    const managementUserCustomMessageRole = new iam.Role(this, "ManagementUserCustomMessageRole", {
      roleName: `${stage}-management-user-custom-message-role`,
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("AWSSecretsManagerClientReadOnlyAccess"),
      ],
    });

    const managementUserCustomMessageLambdaFunction = new lambda.Function(
      this,
      "ManagementUserCustomMessagePythonFunction",
      {
        functionName: `${stage}-management-user-custom-message-trigger-python`,
        code: lambda.Code.fromAsset("../../backend", {
          bundling: {
            image: lambda.Runtime.PYTHON_3_12.bundlingImage,
            command: ["bash", "-c", "pip install -r requirements.txt -t /asset-output && cp -au . /asset-output"],
            user: "root",
            bundlingFileAccess: cdk.BundlingFileAccess.VOLUME_COPY,
          },
        }),
        runtime: lambda.Runtime.PYTHON_3_12,
        handler: "src.management_user_custom_message_trigger.handler",
        architecture: getLambdaArchitecture(),
        memorySize: 1024,
        timeout: cdk.Duration.minutes(15),
        vpc,
        role: managementUserCustomMessageRole,
        environment: {
          SERVICE: "management_user_custom_message_trigger",
          POSTGRES_SECRET_NAME: dbClusterSecretName,
          POSTGRES_DB: "postgres",
          POSTGRES_HOST: dbClusterHost,
          POSTGRES_PORT: dbClusterPort,
          MANAGEMENT_CONSOLE_DOMAIN: `${MANAGEMENT_CONSOLE_SUB_DOMAIN}.${hostedZone.zoneName}`,
          APPLICATION_LOG_LEVEL: authProps.applicationLogLevel,
        },
      },
    );

    const managementUserPool = new cognito.UserPool(this, "ManagementUserPool", {
      userPoolName: `${stage}-management-user-pool`,
      signInAliases: {
        username: true,
        preferredUsername: true,
      },
      standardAttributes: {
        email: { required: false, mutable: true },
      },
      customAttributes: {
        tenant_id: new cognito.StringAttribute({ minLen: 36, maxLen: 256, mutable: true }),
        user_id: new cognito.StringAttribute({ minLen: 36, maxLen: 256, mutable: true }),
      },
      selfSignUpEnabled: false,
      signInCaseSensitive: true,
      accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
      userVerification: {
        emailStyle: cognito.VerificationEmailStyle.CODE, // パスワード再設定画面へのリンクはlambdaTrigger内で生成するためCODEにする
      },
      email: cognito.UserPoolEmail.withSES({
        fromEmail: authProps.sesFromEmail,
      }),
      lambdaTriggers: {
        customMessage: managementUserCustomMessageLambdaFunction,
      },
      deletionProtection: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      featurePlan: cognito.FeaturePlan.LITE,
    });

    const managementUserPoolDomain = authProps.enableUserPoolCustomDomain
      ? new UserPoolCustomDomain(this, "ManagementUserPoolCustomDomain", {
          userPool: managementUserPool,
          hostedZone: hostedZone,
          domainPrefix: MANAGEMENT_USER_POOL_CUSTOM_DOMAIN_PREFIX,
          certificate: managementUserCertificate,
        })
      : new UserPoolCognitoDomain(this, "ManagementUserPoolCognitoDomain", {
          userPool: managementUserPool,
          domainPrefix: `${stage}-user-ai-unit-works-hi`,
        });

    new ssm.StringParameter(this, "ManagementUserPoolIdParameter", {
      parameterName: `/${stage}/management-user-pool-id`,
      stringValue: managementUserPool.userPoolId,
    });
    new ssm.StringParameter(this, "ManagementUserPoolOAuthTokenEndpointUrlParameter", {
      parameterName: `/${stage}/management-user-pool-oauth-token-endpoint-url`,
      stringValue: managementUserPoolDomain.userPoolOAuthTokenEndpointUrl,
    });
    new ssm.StringParameter(this, "ManagementUserPoolJwksEndpointUrlParameter", {
      parameterName: `/${stage}/management-user-pool-jwks-endpoint-url`,
      stringValue: `https://cognito-idp.${region}.amazonaws.com/${managementUserPool.userPoolId}/.well-known/jwks.json`,
    });

    // Contract API向けM2M認証基盤
    const contractPreTokenGenerationRole = new iam.Role(this, "ContractPreTokenGenerationRole", {
      roleName: `${stage}-contract-pre-token-generation-role`,
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole"),
        iam.ManagedPolicy.fromAwsManagedPolicyName("AWSSecretsManagerClientReadOnlyAccess"),
      ],
    });

    const contractPreTokenGenerationLambdaFunction = new lambda.Function(
      this,
      "ContractPreTokenGenerationPythonFunction",
      {
        functionName: `${stage}-contract-pre-token-generation-trigger-python`,
        code: lambda.Code.fromAsset("../../backend", {
          bundling: {
            image: lambda.Runtime.PYTHON_3_12.bundlingImage,
            command: ["bash", "-c", "pip install -r requirements.txt -t /asset-output && cp -au . /asset-output"],
            user: "root",
            bundlingFileAccess: cdk.BundlingFileAccess.VOLUME_COPY,
          },
        }),
        runtime: lambda.Runtime.PYTHON_3_12,
        handler: "src.contract_pre_token_generation_trigger.handler",
        architecture: getLambdaArchitecture(),
        memorySize: 1024,
        timeout: cdk.Duration.minutes(15),
        vpc,
        role: contractPreTokenGenerationRole,
        environment: {
          SERVICE: "contract_pre_token_generation_trigger",
          POSTGRES_SECRET_NAME: dbClusterSecretName,
          POSTGRES_DB: "postgres",
          POSTGRES_HOST: dbClusterHost,
          POSTGRES_PORT: dbClusterPort,
          APPLICATION_LOG_LEVEL: authProps.applicationLogLevel,
        },
      },
    );

    const contractUserPool = new cognito.UserPool(this, "ContractUserPool", {
      userPoolName: `${stage}-contract-m2m-pool`,
      deletionProtection: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    contractUserPool.addTrigger(
      cognito.UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,
      contractPreTokenGenerationLambdaFunction,
      cognito.LambdaVersion.V3_0,
    );

    // M2M認証を行うためには一つ以上のリソースサーバーが必要なため
    contractUserPool.addResourceServer("ContractResourceServer", {
      identifier: "m2m",
      scopes: [
        {
          scopeName: "contract",
          scopeDescription: "for contract API",
        },
      ],
    });
    const contractUserPoolDomain = authProps.enableUserPoolCustomDomain
      ? new UserPoolCustomDomain(this, "ContractUserPoolCustomDomain", {
          userPool: contractUserPool,
          hostedZone: hostedZone,
          domainPrefix: CONTRACT_USER_POOL_CUSTOM_DOMAIN_PREFIX,
          certificate: contractUserPoolCertificate,
        })
      : new UserPoolCognitoDomain(this, "ContractUserPoolCognitoDomain", {
          userPool: contractUserPool,
          domainPrefix: `${stage}-contract-ai-unit-works-hi`,
        });

    new ssm.StringParameter(this, "ContractUserPoolIdParameter", {
      parameterName: `/${stage}/contract-user-pool-id`,
      stringValue: contractUserPool.userPoolId,
    });
    new ssm.StringParameter(this, "ContractUserPoolOAuthTokenEndpointUrlParameter", {
      parameterName: `/${stage}/contract-user-pool-oauth-token-endpoint-url`,
      stringValue: contractUserPoolDomain.userPoolOAuthTokenEndpointUrl,
    });
    new ssm.StringParameter(this, "ContractUserPoolJwksEndpointUrlParameter", {
      parameterName: `/${stage}/contract-user-pool-jwks-endpoint-url`,
      stringValue: `https://cognito-idp.${region}.amazonaws.com/${contractUserPool.userPoolId}/.well-known/jwks.json`,
    });
  }
}
