import * as cdk from "aws-cdk-lib";
import { CfnSlackChannelConfiguration } from "aws-cdk-lib/aws-chatbot";
import { Rule } from "aws-cdk-lib/aws-events";
import { SnsTopic } from "aws-cdk-lib/aws-events-targets";
import { AnyPrincipal, Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { Key } from "aws-cdk-lib/aws-kms";
import { ITopic, Topic } from "aws-cdk-lib/aws-sns";
import { StringParameter } from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
import { SLACK_WORKSPACE_ID } from "./const";

export type AlertProps = {
  readonly slackChannelId: string;
  readonly slackChannelName: string;
};

interface StackProps extends cdk.StackProps {
  readonly accountAlias: string;
  readonly additionalTopic: ITopic;
  readonly alertProps: AlertProps;
}

export class AlertStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    const { accountAlias, additionalTopic } = props;
    const { slackChannelId, slackChannelName } = props.alertProps;

    // Key
    const topicName = `${accountAlias}-alert-topic`;
    const key = new Key(this, "AlertTopicKey", {
      alias: `alias/${topicName}-key`,
      description: "Key for alert topic",
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    key.addToResourcePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        principals: [new AnyPrincipal()],
        actions: ["kms:Decrypt", "kms:GenerateDataKey*"],
        resources: ["*"],
      }),
    );

    // Topic
    const alertTopic = new Topic(this, "AlertTopic", {
      topicName,
      masterKey: key,
    });
    alertTopic.addToResourcePolicy(
      new PolicyStatement({
        sid: "SNSPublishingPermissions",
        effect: Effect.ALLOW,
        principals: [new ServicePrincipal("events.amazonaws.com"), new ServicePrincipal("budgets.amazonaws.com")],
        actions: ["sns:Publish"],
        resources: [alertTopic.topicArn],
      }),
    );
    new StringParameter(this, "ParamStageAlertTopicArn", {
      parameterName: `/account/alert-topic-arn`,
      stringValue: alertTopic.topicArn,
    });

    // AWS Chatbot
    const chatbotRole = new Role(this, "AWSChatbotRole", {
      roleName: `AlertAWSChatbotRole`,
      assumedBy: new ServicePrincipal("chatbot.amazonaws.com"),
      inlinePolicies: {
        ChatbotPolicy: new PolicyDocument({
          statements: [
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: ["cloudwatch:Describe*", "cloudwatch:Get*", "cloudwatch:List*", "support:*"],
              resources: ["*"],
            }),
          ],
        }),
      },
    });
    new CfnSlackChannelConfiguration(this, "SlackChannelConfiguration", {
      configurationName: slackChannelName,
      iamRoleArn: chatbotRole.roleArn,
      slackChannelId,
      slackWorkspaceId: SLACK_WORKSPACE_ID,
      snsTopicArns: [alertTopic.topicArn, additionalTopic.topicArn],
    });

    // SecurityHub Alert
    const securityHubRule = new Rule(this, "SecurityHubRule", {
      eventPattern: {
        source: ["aws.securityhub"],
        detailType: ["Security Hub Findings - Imported"],
        detail: {
          findings: {
            Compliance: {
              Status: ["FAILED", "WARNING", "NOT_AVAILABLE"],
            },
            RecordState: ["ACTIVE"],
            Severity: {
              Label: ["CRITICAL"],
            },
            Workflow: {
              Status: ["NEW"],
            },
          },
        },
      },
    });
    securityHubRule.addTarget(new SnsTopic(alertTopic));

    // GuardDuty Alert
    const guardDutyRule = new Rule(this, "GuardDutyRule", {
      eventPattern: {
        source: ["aws.securityhub"], //SecurityHubに統合されたGuardDutyの結果を扱う
        detailType: ["Security Hub Findings - Imported"],
        detail: {
          findings: {
            ProductName: ["GuardDuty"],
            RecordState: ["ACTIVE"],
            Severity: {
              Label: ["CRITICAL"],
            },
            Workflow: {
              Status: ["NEW"],
            },
          },
        },
      },
    });
    guardDutyRule.addTarget(new SnsTopic(alertTopic));
  }
}
