#!/usr/bin/env bash
set -euo pipefail

TARGET_ROOT="${1:-.}"
BLACKSHIELD_SAAS_IMAGE="${BLACKSHIELD_SAAS_IMAGE:-public.ecr.aws/blackshield-security/saas-scanner:1.0.0}"

replace_literal() {
  local file_path="$1"
  local placeholder="$2"
  local replacement="$3"
  local tmp_file="${file_path}.tmp.$$"

  sed "s|${placeholder}|${replacement}|g" "$file_path" > "$tmp_file"
  mv "$tmp_file" "$file_path"
}

mkdir -p "$TARGET_ROOT/deploy/aws-saas-scanner"
cat > "$TARGET_ROOT/deploy/aws-saas-scanner/app.py" <<'EOF'
#!/usr/bin/env python3
"""CDK app entry point for BlackShield SaaS Scanner (AWS Lambda)."""
import os

import aws_cdk as cdk

from saas_scanner_stack import SaasScannerStack

app = cdk.App()

env = cdk.Environment(
    account=os.environ.get("CDK_DEFAULT_ACCOUNT"),
    region=os.environ.get("CDK_DEFAULT_REGION", "us-east-1"),
)

provider = (
    app.node.try_get_context("saas_scan_provider")
    or os.environ.get("SAAS_SCAN_PROVIDER", "google_workspace")
)

# Stack name includes provider to allow independent deployments per provider
stack_name = f"BlackShieldSaasScanner-{provider.replace('_', '-').title()}"

SaasScannerStack(
    app,
    app.node.try_get_context("stack_name") or stack_name,
    env=env,
    schedule_cron=(
        app.node.try_get_context("schedule_cron") or "0 */6 * * ? *"
    ),
    blackshield_api_url=(
        app.node.try_get_context("api_url")
        or os.environ["BLACKSHIELD_API_URL"]
    ),
    secret_name=(
        app.node.try_get_context("secret_name")
        or os.environ.get("SECRET_NAME", f"blackshield/saas-scanner/{provider}")
    ),
    scanner_image_uri=(
        app.node.try_get_context("image_uri")
        or os.environ["SCANNER_IMAGE_URI"]
    ),
    saas_scan_provider=provider,
    collector_strategy=(
        app.node.try_get_context("collector_strategy") or "api"
    ),
)

cdk.Tags.of(app).add("Project", "SecurityPlatform")
cdk.Tags.of(app).add("ManagedBy", "CDK")
cdk.Tags.of(app).add("Component", "saas-scanner")
cdk.Tags.of(app).add("Provider", provider)

app.synth()
EOF

mkdir -p "$TARGET_ROOT/deploy/aws-saas-scanner"
cat > "$TARGET_ROOT/deploy/aws-saas-scanner/cdk.json" <<'EOF'
{
  "app": "python app.py",
  "context": {
    "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
    "@aws-cdk/core:checkSecretUsage": true,
    "@aws-cdk/aws-iam:minimizePolicies": true
  }
}
EOF

mkdir -p "$TARGET_ROOT/deploy/aws-saas-scanner"
cat > "$TARGET_ROOT/deploy/aws-saas-scanner/requirements.txt" <<'EOF'
aws-cdk-lib>=2.130.0
constructs>=10.0.0
EOF

mkdir -p "$TARGET_ROOT/deploy/aws-saas-scanner"
cat > "$TARGET_ROOT/deploy/aws-saas-scanner/saas_scanner_stack.py" <<'EOF'
"""
CDK stack: BlackShield SaaS Scanner (AWS Lambda + EventBridge Scheduler).

Deploys a Lambda container that runs the SaaS scanner (OAuth inventory +
AI asset discovery) on a schedule and sends findings to the platform.

Supports: google_workspace, microsoft_graph, github_code, generic

Usage examples:
    # Google Workspace — OAuth inventory
    BLACKSHIELD_API_URL=https://api.blackshield.chaplau.com \\
    SCANNER_IMAGE_URI=public.ecr.aws/blackshield-security/saas-scanner:latest \\
    SAAS_SCAN_PROVIDER=google_workspace \\
    SECRET_NAME=blackshield/saas-scanner/google \\
    cdk deploy BlackShieldSaasScanner-Google --require-approval never

    # Microsoft 365 — OAuth inventory
    BLACKSHIELD_API_URL=https://api.blackshield.chaplau.com \\
    SCANNER_IMAGE_URI=public.ecr.aws/blackshield-security/saas-scanner:latest \\
    SAAS_SCAN_PROVIDER=microsoft_graph \\
    SECRET_NAME=blackshield/saas-scanner/microsoft \\
    cdk deploy BlackShieldSaasScanner-MSFT --require-approval never

Secret JSON schema (store in Secrets Manager before deploying):
    {
        "BLACKSHIELD_API_KEY": "sp_xxxx",
        "SAAS_ACCESS_TOKEN": "<provider-token>",
        "GOOGLE_CUSTOMER_ID": "C0xxxxxxx"   # Google only
    }
"""
from aws_cdk import (
    Duration,
    RemovalPolicy,
    Stack,
    aws_events as events,
    aws_events_targets as targets,
    aws_iam as iam,
    aws_lambda as lambda_,
    aws_logs as logs,
    aws_secretsmanager as secretsmanager,
)
from constructs import Construct

# Map of provider → scan mode
_PROVIDER_SCAN_MODE: dict[str, str] = {
    "google_workspace": "oauth",
    "microsoft_graph": "oauth",
    "github_code": "ai_assets",
    "generic": "oauth",
}


class SaasScannerStack(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        schedule_cron: str,
        blackshield_api_url: str,
        secret_name: str,
        scanner_image_uri: str,
        saas_scan_provider: str,
        collector_strategy: str = "api",
        **kwargs: object,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        scan_mode = _PROVIDER_SCAN_MODE.get(saas_scan_provider, "oauth")

        # ── IAM role ──────────────────────────────────────────────────────────
        # SaaS scanner only needs to call external APIs and Secrets Manager.
        # No AWS resource read permissions required.
        role = iam.Role(
            self,
            "ScannerRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            description=f"BlackShield SaaS scanner role ({saas_scan_provider})",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "service-role/AWSLambdaBasicExecutionRole"
                ),
            ],
        )

        # Allow reading credentials from Secrets Manager
        secret = secretsmanager.Secret.from_secret_name_v2(
            self, "SaasSecret", secret_name
        )
        secret.grant_read(role)

        # ── CloudWatch log group ───────────────────────────────────────────────
        log_group = logs.LogGroup(
            self,
            "ScannerLogs",
            log_group_name=f"/aws/lambda/{construct_id}",
            retention=logs.RetentionDays.ONE_MONTH,
            removal_policy=RemovalPolicy.DESTROY,
        )

        # ── Lambda (container image) ───────────────────────────────────────────
        fn = lambda_.DockerImageFunction(
            self,
            "SaasScannerFn",
            function_name=construct_id,
            code=lambda_.DockerImageCode.from_image_uri(scanner_image_uri),
            role=role,
            timeout=Duration.minutes(14),
            memory_size=1024,
            log_group=log_group,
            environment={
                "BLACKSHIELD_API_URL": blackshield_api_url,
                "SAAS_SECRET_NAME": secret_name,       # read at runtime by entrypoint
                "SAAS_SCAN_MODE": scan_mode,
                "SAAS_SCAN_PROVIDER": saas_scan_provider,
                "SAAS_COLLECTOR_STRATEGY": collector_strategy,
                "SCAN_INTERVAL_SECONDS": "0",           # one-shot per Lambda invocation
                "LOG_LEVEL": "INFO",
                "BATCH_SIZE": "500",
            },
        )

        # ── EventBridge schedule ───────────────────────────────────────────────
        parts = schedule_cron.split()
        rule = events.Rule(
            self,
            "ScanSchedule",
            description=f"Trigger BlackShield SaaS scanner ({saas_scan_provider})",
            schedule=events.Schedule.cron(
                minute=parts[0],
                hour=parts[1],
                day=parts[2] if len(parts) > 2 else "*",
                month=parts[3] if len(parts) > 3 else "*",
                week_day=parts[4] if len(parts) > 4 else "?",
            ),
        )
        rule.add_target(targets.LambdaFunction(fn))
EOF

replace_literal \
  "$TARGET_ROOT/deploy/aws-saas-scanner/saas_scanner_stack.py" \
  "public.ecr.aws/blackshield-security/saas-scanner:latest" \
  "$BLACKSHIELD_SAAS_IMAGE"

printf "Wrote source bundle files:
"
printf "  - %s\n" "$TARGET_ROOT/deploy/aws-saas-scanner/app.py"
printf "  - %s\n" "$TARGET_ROOT/deploy/aws-saas-scanner/cdk.json"
printf "  - %s\n" "$TARGET_ROOT/deploy/aws-saas-scanner/requirements.txt"
printf "  - %s\n" "$TARGET_ROOT/deploy/aws-saas-scanner/saas_scanner_stack.py"
