import * as cdk from "aws-cdk-lib";
import {
  ArnPrincipal,
  CompositePrincipal,
  Effect,
  ManagedPolicy,
  PolicyDocument,
  PolicyStatement,
  PrincipalWithConditions,
  Role,
} from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";
import { Context } from "vm";
import { netskopeIps, officeIps, operators, trustRoleArns, watchers } from "./const";

interface StackProps extends cdk.StackProps {
  readonly iamProps: Context["iamProps"];
  readonly workspacesWebIp: string;
}

export class IAMStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    const { iamProps, workspacesWebIp } = props;

    const SSO_ROLE_SUFFIX = "ForAwsSsoRole";

    const trustRolePrincipals = new CompositePrincipal(
      ...trustRoleArns.map((arn: string) => Role.fromRoleArn(this, arn, arn)),
    );

    const operatorEmails = operators.map((operator) => operator.mailAddress);

    new Role(this, `Administrator${SSO_ROLE_SUFFIX}`, {
      roleName: `Administrator${SSO_ROLE_SUFFIX}`,
      maxSessionDuration: cdk.Duration.hours(12),
      assumedBy: new PrincipalWithConditions(trustRolePrincipals, {
        IpAddress: {
          "aws:SourceIp": [workspacesWebIp],
        },
        StringEquals: {
          "saml:sub": operatorEmails,
        },
      }),
      managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")],
    });

    const operatorIps = operators.flatMap((operator) => operator.cidrs);
    const watcherIps = watchers.flatMap((watcher) => watcher.cidrs);
    const watcherEmails = watchers.map((watcher) => watcher.mailAddress);
    new Role(this, `Watcher${SSO_ROLE_SUFFIX}`, {
      roleName: `Watcher${SSO_ROLE_SUFFIX}`,
      maxSessionDuration: cdk.Duration.hours(12),
      assumedBy: new PrincipalWithConditions(trustRolePrincipals, {
        IpAddress: {
          "aws:SourceIp": [workspacesWebIp, ...officeIps, ...netskopeIps, ...operatorIps, ...watcherIps],
        },
        StringEquals: {
          "saml:sub": [...operatorEmails, ...watcherEmails],
        },
      }),
      managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")],
      inlinePolicies: {
        WatcherPolicy: new PolicyDocument({
          statements: [
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: [
                "support:*",
                "trustedadvisor:RefreshCheck",
                "servicequotas:RequestServiceQuotaIncrease",
                "servicequotas:ListTagsForResource",
                "athena:StartQueryExecution",
              ],
              resources: ["*"],
            }),
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: [
                "securityhub:UpdateFindings",
                "securityhub:BatchUpdateFindings",
                "securityhub:UpdateStandardsControl",
              ],
              resources: [`arn:aws:securityhub:*:${this.account}:hub/default`],
            }),
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: ["rds-data:ExecuteStatement"],
              resources: [`arn:aws:rds:${this.region}:${this.account}:cluster:*-db-cluster`],
            }),
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: ["secretsmanager:GetSecretValue"],
              resources: [`arn:aws:secretsmanager:${this.region}:${this.account}:secret:*-db-watcher-credentials*`],
            }),
            new PolicyStatement({
              effect: Effect.DENY,
              actions: [
                "secretsmanager:GetSecretValue",
                "secretsmanager:CreateSecret", // クエリエディタによるシークレットの裏側での自動作成を明示的に禁止
                "secretsmanager:PutSecretValue",
              ],
              resources: [
                `arn:aws:secretsmanager:${this.region}:${this.account}:secret:*-db-admin-credentials*`,
                `arn:aws:secretsmanager:${this.region}:${this.account}:secret:*-db-app-credentials*`,
                `arn:aws:secretsmanager:${this.region}:${this.account}:secret:*-db-client-developer-credentials*`,
                `arn:aws:secretsmanager:${this.region}:${this.account}:secret:rds-db-credentials/*`, // クエリエディタが自動生成するプレフィックスを禁止
              ],
            }),
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: ["dbqms:CreateQueryHistory"],
              resources: ["*"],
            }),
          ],
        }),
      },
    });
    if (iamProps.enableClientDeveloperRole) {
      // クライアント開発者用
      new Role(this, `ClientDeveloper${SSO_ROLE_SUFFIX}`, {
        roleName: `ClientDeveloper${SSO_ROLE_SUFFIX}`,
        maxSessionDuration: cdk.Duration.hours(12),
        assumedBy: new ArnPrincipal(
          "arn:aws:iam::686255989062:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_BasicAccessRights_ff606434c9b8b398", // CCoE踏み台アカウント
        ),
        inlinePolicies: {
          QueryEditorPolicy: new PolicyDocument({
            statements: [
              new PolicyStatement({
                effect: Effect.ALLOW,
                actions: [
                  "rds-data:ExecuteStatement", // クエリエディタの実行に必要
                  // NOTICE:
                  // 社内の人間に任意のSQLをDataAPIを実行できる権限が与えられるが、セッショントークンを発行する権限を渡していないためDBユーザーの権限を参照するクエリエディタを通してのみSQLが実行可能な状態に留めている。
                ],
                resources: [`arn:aws:rds:${this.region}:${this.account}:cluster:stg-db-cluster`],
              }),
              new PolicyStatement({
                effect: Effect.ALLOW,
                actions: ["secretsmanager:GetSecretValue"],
                resources: [
                  `arn:aws:secretsmanager:${this.region}:${this.account}:secret:stg-db-client-developer-credentials*`,
                ],
              }),
              new PolicyStatement({
                effect: Effect.ALLOW,
                actions: ["secretsmanager:ListSecrets"],
                resources: ["*"],
              }),
              new PolicyStatement({
                effect: Effect.ALLOW,
                actions: [
                  "rds:DescribeDBInstances",
                  "rds:DescribeDBClusters",
                  "rds:DescribeGlobalClusters",
                  "rds:DescribeDBParameterGroups",
                  "rds:DescribeDBClusterParameterGroups",
                  "rds:DescribeDBEngineVersions",
                  "rds:DescribeOptionGroups",
                  "rds:DescribeAccountAttributes",
                  "dbqms:CreateQueryHistory",
                ],
                resources: ["*"],
              }),
            ],
          }),
        },
      });
    }
  }
}
