import * as cdk from "aws-cdk-lib";
import { Alarm, ComparisonOperator, Metric, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
import { CfnEIP, NatProvider, Vpc } from "aws-cdk-lib/aws-ec2";
import { Effect, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { Stream } from "aws-cdk-lib/aws-kinesis";
import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose";
import { Code, Function, Runtime, StartingPosition } from "aws-cdk-lib/aws-lambda";
import { KinesisEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
import { FilterPattern, ILogGroup, LogGroup, MetricFilter } from "aws-cdk-lib/aws-logs";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { ITopic, Topic } from "aws-cdk-lib/aws-sns";
import * as workspacesweb from "aws-cdk-lib/aws-workspacesweb";
import { Construct } from "constructs";
import * as path from "path";
import { operators } from "./const";
import { getLambdaArchitecture } from "./util";

interface WorkspaceUser {
  readonly userName: string;
  readonly mailAddress: string;
}

interface StackProps extends cdk.StackProps {
  readonly accountAlias: string;
}

export class WorkspacesWebStack extends cdk.Stack {
  readonly natIp: string;
  readonly topic: ITopic;
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    const { accountAlias } = props;
    const { region } = new cdk.ScopedAws(this);
    //Vpc
    const eip = new CfnEIP(this, "Eip", {
      domain: "vpc",
    });
    this.natIp = eip.attrPublicIp;

    const vpc = new Vpc(this, "Vpc", {
      vpcName: `${accountAlias}-workspaces-web-vpc`,
      natGateways: 1,
      natGatewayProvider: NatProvider.gateway({ eipAllocationIds: [eip.attrAllocationId] }),
      restrictDefaultSecurityGroup: false,
    });

    //Topic
    const topic = new Topic(this, "Topic", {
      topicName: `${accountAlias}-workspaces-web-user-access-topic`,
    });
    this.topic = topic;

    //Bucket
    const bucket = new Bucket(this, `Bucket`, {
      bucketName: `${accountAlias}-workspaces-web-user-access-logs-${region}`,
    });

    //Kinesis Data Stream
    const dataStream = new Stream(this, "DataStream", {
      streamName: "amazon-workspaces-web-user-access-logs-stream",
    });

    //IAM
    const role = new Role(this, "Role", {
      assumedBy: new ServicePrincipal("firehose.amazonaws.com"),
    });
    role.addToPolicy(
      new PolicyStatement({
        actions: ["kinesis:DescribeStream", "kinesis:GetShardIterator", "kinesis:GetRecords"],
        effect: Effect.ALLOW,
        resources: [dataStream.streamArn, `${dataStream.streamArn}/*`],
      })
    );
    role.addToPolicy(
      new PolicyStatement({
        actions: [
          "s3:AbortMultipartUpload",
          "s3:GetBucketLocation",
          "s3:GetObject",
          "s3:ListBucket",
          "s3:ListBucketMultipartUploads",
          "s3:PutObject",
        ],
        effect: Effect.ALLOW,
        resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
      })
    );

    //Kinesis Data FireHose
    const deliveryStream = new CfnDeliveryStream(this, "DeliveryStream", {
      deliveryStreamName: `amazon-workspaces-web-user-access-logs`,
      deliveryStreamType: "KinesisStreamAsSource",
      kinesisStreamSourceConfiguration: {
        kinesisStreamArn: dataStream.streamArn,
        roleArn: role.roleArn,
      },
      s3DestinationConfiguration: {
        bucketArn: bucket.bucketArn,
        roleArn: role.roleArn,
        bufferingHints: {
          intervalInSeconds: 60,
          sizeInMBs: 5,
        },
        compressionFormat: "GZIP",
        prefix: "logs/",
        errorOutputPrefix: "errors/",
      },
    });
    deliveryStream.node.addDependency(role);

    //Logs
    const logGroup = new LogGroup(this, "LogGroup", {
      logGroupName: "workspaces-web-user-access-logs",
    });

    //Lambda
    const lambdaFunction = new Function(this, `${getLambdaArchitecture().name}Function`, {
      runtime: Runtime.NODEJS_22_X,
      handler: "index.handler",
      code: Code.fromAsset(path.join(__dirname, "../lambda")),
      architecture: getLambdaArchitecture(),
      environment: {
        LOG_GROUP_NAME: logGroup.logGroupName,
      },
      timeout: cdk.Duration.seconds(30),
    });

    lambdaFunction.addEventSource(
      new KinesisEventSource(dataStream, {
        batchSize: 100, // default
        startingPosition: StartingPosition.TRIM_HORIZON,
      })
    );

    const functionAlarm = new Alarm(this, "FunctionAlarm", {
      alarmName: `${accountAlias}-workspace-web-lambda-function-alarm`,
      threshold: 1,
      evaluationPeriods: 1,
      metric: lambdaFunction.metricErrors(),
      datapointsToAlarm: 1,
      treatMissingData: TreatMissingData.NOT_BREACHING,
    });

    functionAlarm.addAlarmAction(new SnsAction(topic));

    //Alarms
    operators.forEach((user) => {
      this.createAlarm("StartSession", accountAlias, user, logGroup, topic);
      this.createAlarm("EndSession", accountAlias, user, logGroup, topic);
    });

    // Workspaces Web Portal
    const networkSettings = new workspacesweb.CfnNetworkSettings(this, "WorkspacesWebNetworkSettings", {
      securityGroupIds: [vpc.vpcDefaultSecurityGroup],
      subnetIds: vpc.privateSubnets.map((subnet) => subnet.subnetId),
      vpcId: vpc.vpcId,
    });

    const userAccessLoggingSettings = new workspacesweb.CfnUserAccessLoggingSettings(
      this,
      "userAccessLoggingSettings",
      {
        kinesisStreamArn: dataStream.streamArn,
      }
    );
    const userSettings = new workspacesweb.CfnUserSettings(this, "userSettings", {
      copyAllowed: "Disabled",
      downloadAllowed: "Disabled",
      pasteAllowed: "Enabled",
      printAllowed: "Disabled",
      uploadAllowed: "Enabled",
      disconnectTimeoutInMinutes: 120,
      idleDisconnectTimeoutInMinutes: 60,
    });
    const browserPolicy = `{
      "chromePolicies": {
        "ManagedBookmarks": {
          "value": [
            {
              "name": "BasicAccessRights",
              "url": "https://d-95671d33b8.awsapps.com/start/#/console?account_id=343953862039&role_name=BasicAccessRights"
            },
            {
              "name": "aiunit-dev",
              "url": "https://signin.aws.amazon.com/switchrole?roleName=AdministratorForAwsSsoRole&account=aiunit-dev&displayName=Administrator@aiunit-dev"
            },
            {
              "name": "aiunit-prod",
              "url": "https://signin.aws.amazon.com/switchrole?roleName=AdministratorForAwsSsoRole&account=aiunit-prod&displayName=Administrator@aiunit-prod"
            }
          ]
        },
        "BookmarkBarEnabled": {
          "value": true
        },
        "RestoreOnStartup": {
          "value": 5
        },
        "RestoreOnStartupURLs": {
          "value": []
        },
        "URLBlocklist": {
          "value": []
        },
        "URLAllowlist": {
          "value": []
        },
        "AllowDeletingBrowserHistory": {
          "value": true
        },
        "IncognitoModeAvailability": {
          "value": 0
        }
      }
    }`;

    const browserSettings = new workspacesweb.CfnBrowserSettings(this, "browserSettings", {
      browserPolicy,
    });

    new workspacesweb.CfnPortal(this, "WorkspacesWebPortal", {
      displayName: "リモート作業用ウェブポータル",
      networkSettingsArn: networkSettings.attrNetworkSettingsArn,
      userAccessLoggingSettingsArn: userAccessLoggingSettings.attrUserAccessLoggingSettingsArn,
      userSettingsArn: userSettings.attrUserSettingsArn,
      browserSettingsArn: browserSettings.attrBrowserSettingsArn,
      instanceType: "standard.regular",
      maxConcurrentSessions: 5,
    });
  }

  createAlarm(
    eventType: "StartSession" | "EndSession",
    accountAlias: string,
    user: WorkspaceUser,
    logGroup: ILogGroup,
    topic: ITopic
  ) {
    // FilterPattern
    const metricFilter = new MetricFilter(this, `MetricFilter-${eventType}-${user.userName}`, {
      metricNamespace: "LogMetrics",
      metricName: `${eventType}-${user.userName}`,
      logGroup,
      filterPattern: FilterPattern.all(
        FilterPattern.stringValue("$.eventType", "=", eventType),
        FilterPattern.stringValue("$.userName", "=", `${user.mailAddress}`)
      ),
      metricValue: "1",
    });

    // Metric
    const metric = new Metric({
      namespace: "LogMetrics",
      metricName: metricFilter.metric().metricName,
      statistic: "Sum",
      period: cdk.Duration.minutes(1),
    });

    // Alarm
    const alarm = new Alarm(this, `Alarm-${eventType}-${user.userName}`, {
      alarmName: `${accountAlias}-workspace-web-${user.userName}-${eventType}-alarm`,
      metric,
      threshold: 0,
      evaluationPeriods: 1,
      comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
      actionsEnabled: true,
    });

    alarm.addAlarmAction(new SnsAction(topic));
  }
}
