# Blog Application Deployment Makefile

.EXPORT_ALL_VARIABLES:

AWS_PROFILE ?= localstack
AWS_REGION ?= us-east-1

PROJECT_NAME ?= atproto-blog
BUCKET_NAME ?= $(PROJECT_NAME)
ALLOWED_ORIGIN ?= *
API_DID ?= $(if $(DOMAIN_NAME),did:web:$(DOMAIN_NAME),did:web:localhost)
VITE_API_URL ?= https://$(API_SUBDOMAIN).$(DOMAIN_NAME)
DYNAMODB_TABLE ?= blogs
IFRAMELY_SECRET_ARN ?=

INDEXER_STACK_NAME ?= $(PROJECT_NAME)-indexer-stack
INDEXER_S3_KEY ?= indexer/indexer.zip
# Docker bridge gateway — the address at which LocalStack is reachable from
# inside a LocalStack EC2 container (Docker-in-Docker).  Override if your
# setup uses a different network (e.g. host.docker.internal on macOS).
LOCALSTACK_ENDPOINT ?= http://172.17.0.1:4566

BACKFILL_STACK_NAME ?= $(PROJECT_NAME)-backfill-stack
BACKFILL_S3_KEY ?= backfill/backfill.zip
BACKFILL_TRIGGER_BUCKET ?= $(PROJECT_NAME)-backfill
BACKFILL_DIDS_S3_KEY ?= dids.txt
# Required for run-local-backfill / run-aws-backfill — no default.
DIDS_FILE ?=

ARTIFACTS_BUCKET_NAME ?= $(PROJECT_NAME)-artifacts

CLOUDFRONT_STACK_NAME ?= $(PROJECT_NAME)-cloudfront-stack
# REST API ID from the blog-api stack (visible in the ApiUrl output or via describe-stacks)
API_GATEWAY_ID ?=

WAF_STACK_NAME ?= $(PROJECT_NAME)-waf-stack
# WAF and ACM must always be deployed to us-east-1 (CloudFront scope requirement)
WAF_REGION := us-east-1
ACM_REGION  := us-east-1

ACM_STACK_NAME ?= $(PROJECT_NAME)-acm-stack

ROUTE53_STACK_NAME ?= $(PROJECT_NAME)-route53-stack
# Apex domain and hosted zone — shared by create-aws-acm, create-aws-cloudfront, create-aws-route53.
HOSTED_ZONE_ID ?=
DOMAIN_NAME ?=
# Optional subdomain prefix for the site (e.g. www). Leave empty for apex-only.
SITE_SUBDOMAIN ?=
# Optional subdomain prefix for the API (e.g. api). Leave empty to skip.
API_SUBDOMAIN ?= api

.PHONY: help start-localstack stop-localstack install-cfn-lint install-lambda \
        validate-cf lint-cf \
        create-local-tables create-local-bucket create-local-artifacts create-local-secret create-local-api \
        create-aws-tables create-aws-bucket create-aws-artifacts create-aws-secret create-aws-api \
        create-aws-cloudfront \
        create-aws-waf \
        create-aws-acm \
        create-aws-route53 \
        setup-aws-domain \
        update-local-api update-aws-api \
        install-indexer build-indexer \
        upload-run-local-indexer upload-indexer-aws \
        create-local-indexer create-aws-indexer \
        deploy-local-indexer deploy-aws-indexer \
        update-aws-indexer \
        restart-aws-indexer \
        run-local-indexer \
        install-backfill build-backfill \
        create-local-backfill create-aws-backfill \
        deploy-local-backfill-lambda deploy-aws-backfill-lambda \
        deploy-local-backfill deploy-aws-backfill \
        update-local-backfill update-aws-backfill \
        run-local-backfill run-aws-backfill \
        logs-aws-backfill \
        sync-site build-site deploy-site invalidate-site list-invalidations serve-local local aws \
        update-local-site update-local \
        update-iframely-sri

help:
	@echo "Blog Application Deployment"
	@echo ""
	@echo "Available targets:"
	@echo "  start-localstack      - Start LocalStack via Docker (requires LOCALSTACK_AUTH_TOKEN)"
	@echo "  stop-localstack       - Stop the LocalStack Docker container"
	@echo "  local                 - Full local deploy (LocalStack must be running)"
	@echo "  aws                   - Deploy to AWS"
	@echo "  create-aws-waf            - Deploy WAFv2 WebACL to us-east-1 (required before cloudfront)"
	@echo "  update-local              - Rebuild and redeploy API lambda + frontend to LocalStack"
	@echo "  update-local-api          - Rebuild and hot-deploy API lambda to LocalStack"
	@echo "  update-local-site         - Rebuild frontend and sync to LocalStack S3"
	@echo "  run-local-indexer         - Build and run indexer locally against LocalStack DynamoDB"
	@echo "  deploy-aws-indexer        - Build, upload, and deploy indexer EC2 stack to AWS"
	@echo "  update-aws-indexer        - Build, upload, and hot-reload running indexer via SSM (preserves cursor)"
	@echo "  restart-aws-indexer       - Restart the running indexer service via SSM (no code change)"
	@echo "  deploy-local-backfill - Build, upload, and deploy backfill Lambda stack to LocalStack"
	@echo "  deploy-aws-backfill   - Build, upload, and deploy backfill Lambda stack to AWS"
	@echo "  run-local-backfill    - Upload dids.txt to LocalStack trigger bucket (DIDS_FILE=...)"
	@echo "  run-aws-backfill      - Upload dids.txt to AWS trigger bucket (DIDS_FILE=...)"
	@echo "  logs-aws-backfill     - Tail the most recent backfill Lambda CloudWatch log stream"
	@echo "  build-site            - Build the SPA with VITE_API_DID/VITE_API_URL derived from DOMAIN_NAME"
	@echo "  deploy-site           - Build, sync, and invalidate CloudFront cache (requires DOMAIN_NAME, AWS_PROFILE)"
	@echo "  invalidate-site       - Invalidate CloudFront cache for the site distribution"
	@echo "  list-invalidations    - Show recent CloudFront invalidation statuses"
	@echo "  update-iframely-sri   - Fetch cdn.iframe.ly/embed.js and update the pinned SRI hash"

# Requires LOCALSTACK_AUTH_TOKEN to be set in the environment.
start-localstack:
	@echo "🚀 Starting LocalStack Pro via Docker..."
	docker rm -f localstack 2>/dev/null || true
	docker run --rm -d \
		--name localstack \
		-p 4566:4566 \
		-p 4510-4559:4510-4559 \
		-e LOCALSTACK_AUTH_TOKEN \
		-v /var/run/docker.sock:/var/run/docker.sock \
		localstack/localstack-pro
	@echo "⏳ Waiting for LocalStack to be ready..."
	@until curl -sf http://localhost:4566/_localstack/health > /dev/null; do sleep 1; done
	@echo "✅ LocalStack ready at http://localhost:4566"

stop-localstack:
	@echo "🛑 Stopping LocalStack..."
	docker stop localstack 2>/dev/null || true
	@echo "✅ LocalStack stopped"

install-cfn-lint:
	@echo "📦 Installing cfn-lint..."
	conda create -n blog python=3.12 cfn-lint
	@echo "✅ Python environment created with cfn-lint installed."

install-lambda:
	@echo "📦 Installing lambda dependencies..."
	cd api && npm install
	@echo "✅ Lambda dependencies installed"

validate-cf:
	@echo "🔍 Validating CloudFormation templates..."
	aws cloudformation validate-template --template-body file://./cf/acm.yaml
	aws cloudformation validate-template --template-body file://./cf/artifacts.yaml
	aws cloudformation validate-template --template-body file://./cf/backfill.yaml
	aws cloudformation validate-template --template-body file://./cf/blog-api.yaml
	aws cloudformation validate-template --template-body file://./cf/cloudfront.yaml
	aws cloudformation validate-template --template-body file://./cf/dynamodb.yaml
	aws cloudformation validate-template --template-body file://./cf/indexer.yaml
	aws cloudformation validate-template --template-body file://./cf/route53.yaml
	aws cloudformation validate-template --template-body file://./cf/s3.yaml
	aws cloudformation validate-template --template-body file://./cf/secrets.yaml
	aws cloudformation validate-template --template-body file://./cf/waf.yaml
	@echo "✅ CloudFormation template validation completed"

lint-cf:
	@echo "🔍 Linting CloudFormation templates..."
	conda run -n blog cfn-lint ./cf/acm.yaml
	conda run -n blog cfn-lint ./cf/artifacts.yaml
	conda run -n blog cfn-lint ./cf/backfill.yaml
	conda run -n blog cfn-lint ./cf/blog-api.yaml
	conda run -n blog cfn-lint ./cf/cloudfront.yaml
	conda run -n blog cfn-lint ./cf/dynamodb.yaml
	conda run -n blog cfn-lint ./cf/indexer.yaml
	conda run -n blog cfn-lint ./cf/route53.yaml
	conda run -n blog cfn-lint ./cf/s3.yaml
	conda run -n blog cfn-lint ./cf/secrets.yaml
	conda run -n blog cfn-lint ./cf/waf.yaml
	@echo "✅ CloudFormation linting completed"

create-local-tables:
	@echo "🗄️  Creating LocalStack DynamoDB tables..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/dynamodb.yaml \
		--stack-name $(PROJECT_NAME)-dynamodb-stack \
		--no-fail-on-empty-changeset
	@echo "✅ LocalStack tables created"

create-local-bucket:
	@echo "🪣  Creating LocalStack S3 bucket..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/s3.yaml \
		--stack-name $(PROJECT_NAME)-s3-stack \
		--parameter-overrides BucketName=$(BUCKET_NAME) \
		--no-fail-on-empty-changeset
	@echo "✅ LocalStack bucket created"

create-local-artifacts:
	@echo "📦 Creating LocalStack artifacts bucket..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/artifacts.yaml \
		--stack-name $(PROJECT_NAME)-artifacts-stack \
		--parameter-overrides ArtifactsBucketName=$(ARTIFACTS_BUCKET_NAME) \
		--no-fail-on-empty-changeset
	@echo "✅ LocalStack artifacts bucket created"

create-local-secret:
	@echo "🔑 Creating LocalStack secrets stack..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/secrets.yaml \
		--stack-name $(PROJECT_NAME)-secrets-stack \
		--no-fail-on-empty-changeset
	@echo "✅ LocalStack secrets stack created"

# Deploys Lambda + API Gateway V1 (REST API). Upload real code with deploy-local-api-lambda after.
create-local-api:
	@echo "🔐 Creating LocalStack blog API stack (V1)..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/blog-api.yaml \
		--stack-name $(PROJECT_NAME)-api-stack \
		--capabilities CAPABILITY_NAMED_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides ApiDid=$(API_DID) DynamoDBTable=$(DYNAMODB_TABLE) AllowedOrigin=$(ALLOWED_ORIGIN) IframelySecretArn=$(IFRAMELY_SECRET_ARN) SiteDomain=$(DOMAIN_NAME)
	@echo "🔄 Forcing API Gateway redeployment to prod stage..."
	@REST_API_ID=$$(aws cloudformation describe-stacks --profile localstack \
		--stack-name $(PROJECT_NAME)-api-stack \
		--query 'Stacks[0].Outputs[?OutputKey==`ApiId`].OutputValue' \
		--output text) && \
	aws apigateway create-deployment --profile localstack \
		--rest-api-id "$$REST_API_ID" \
		--stage-name prod
	@echo "✅ LocalStack blog API stack created"

create-aws-tables:
	@echo "🗄️  Creating AWS DynamoDB tables..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/dynamodb.yaml \
		--stack-name $(PROJECT_NAME)-dynamodb-stack \
		--no-fail-on-empty-changeset
	@echo "✅ AWS DynamoDB tables created"

create-aws-bucket:
	@echo "🪣  Creating AWS S3 bucket..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/s3.yaml \
		--stack-name $(PROJECT_NAME)-s3-stack \
		--parameter-overrides BucketName=$(BUCKET_NAME) \
		--no-fail-on-empty-changeset
	@echo "✅ AWS bucket created"

create-aws-artifacts:
	@echo "📦 Creating AWS artifacts bucket..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/artifacts.yaml \
		--stack-name $(PROJECT_NAME)-artifacts-stack \
		--parameter-overrides ArtifactsBucketName=$(ARTIFACTS_BUCKET_NAME) \
		--no-fail-on-empty-changeset
	@echo "✅ AWS artifacts bucket created"

create-aws-secret:
	@echo "🔑 Creating AWS secrets stack..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/secrets.yaml \
		--stack-name $(PROJECT_NAME)-secrets-stack \
		--no-fail-on-empty-changeset
	@echo "✅ AWS secrets stack created"

create-aws-api:
	@echo "🔐 Creating AWS blog API stack (V1)..."
	@IFRAMELY_ARN="$(IFRAMELY_SECRET_ARN)"; \
	if [ -z "$$IFRAMELY_ARN" ]; then \
		IFRAMELY_ARN=$$(aws cloudformation describe-stacks \
			--profile $${AWS_PROFILE} --region $${AWS_REGION} \
			--stack-name $(PROJECT_NAME)-secrets-stack \
			--query 'Stacks[0].Outputs[?OutputKey==`IframelySecretArn`].OutputValue' \
			--output text 2>/dev/null || echo ''); \
	fi; \
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/blog-api.yaml \
		--stack-name $(PROJECT_NAME)-api-stack \
		--capabilities CAPABILITY_NAMED_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides ApiDid=$(API_DID) DynamoDBTable=$(DYNAMODB_TABLE) AllowedOrigin=$(ALLOWED_ORIGIN) IframelySecretArn=$$IFRAMELY_ARN SiteDomain=$(DOMAIN_NAME)
	@echo "🔄 Forcing API Gateway redeployment to prod stage..."
	@REST_API_ID=$$(aws cloudformation describe-stacks --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(PROJECT_NAME)-api-stack \
		--query 'Stacks[0].Outputs[?OutputKey==`ApiId`].OutputValue' \
		--output text) && \
	aws apigateway create-deployment --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--rest-api-id "$$REST_API_ID" \
		--stage-name prod
	@echo "✅ AWS blog API stack created"

# AWS-only: LocalStack Hobby does not support WAF.
# Deployed to us-east-1 regardless of AWS_REGION (CloudFront WAF requirement).
create-aws-waf:
	@echo "🛡️  Deploying WAFv2 WebACL to us-east-1..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $(WAF_REGION) \
		--template-file ./cf/waf.yaml \
		--stack-name $(WAF_STACK_NAME) \
		--no-fail-on-empty-changeset
	@echo "✅ WAF WebACL deployed"

# AWS-only: LocalStack Hobby does not support CloudFront.
# API_GATEWAY_ID is auto-fetched from the api stack output if not provided.
# After deploying, use the SiteUrl output as AllowedOrigin when redeploying create-aws-api.
# WAF and ACM outputs are pulled automatically if those stacks exist.
create-aws-cloudfront:
	@echo "🌐 Creating AWS CloudFront distributions..."
	@API_GW_ID="$(API_GATEWAY_ID)"; \
	if [ -z "$$API_GW_ID" ]; then \
		API_GW_ID=$$(aws cloudformation describe-stacks \
			--profile $${AWS_PROFILE} --region $${AWS_REGION} \
			--stack-name $(PROJECT_NAME)-api-stack \
			--query 'Stacks[0].Outputs[?OutputKey==`ApiId`].OutputValue' \
			--output text 2>/dev/null || echo ''); \
	fi; \
	[ -n "$$API_GW_ID" ] || (echo "❌ Could not determine API_GATEWAY_ID — pass it explicitly or run create-aws-api first."; exit 1); \
	WEB_ACL_ARN=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $(WAF_REGION) \
		--stack-name $(WAF_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`WebACLArn`].OutputValue' \
		--output text 2>/dev/null || echo ''); \
	CERT_ARN=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $(ACM_REGION) \
		--stack-name $(ACM_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`CertificateArn`].OutputValue' \
		--output text 2>/dev/null || echo ''); \
	PARAMS="SiteBucketName=$(BUCKET_NAME) ApiGatewayId=$$API_GW_ID"; \
	if [ -n "$$WEB_ACL_ARN" ]; then PARAMS="$$PARAMS WebACLArn=$$WEB_ACL_ARN"; fi; \
	if [ -n "$$CERT_ARN" ]; then PARAMS="$$PARAMS CertificateArn=$$CERT_ARN SiteApexDomain=$(DOMAIN_NAME) SiteSubdomain=$(SITE_SUBDOMAIN) ApiSubdomain=$(API_SUBDOMAIN)"; fi; \
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/cloudfront.yaml \
		--stack-name $(CLOUDFRONT_STACK_NAME) \
		--no-fail-on-empty-changeset \
		--parameter-overrides $$PARAMS
	@echo "✅ CloudFront distributions created"

# AWS-only: LocalStack Hobby does not support ACM.
# Deployed to us-east-1 regardless of AWS_REGION (CloudFront certificate requirement).
# Required: HOSTED_ZONE_ID, DOMAIN_NAME
# Optional: SITE_SUBDOMAIN, API_SUBDOMAIN (default: api)
create-aws-acm:
	@[ -n "$(HOSTED_ZONE_ID)" ] || (echo "❌ HOSTED_ZONE_ID is required."; exit 1)
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required (e.g. example.com)."; exit 1)
	@echo "🔒 Creating ACM certificate in us-east-1 (DNS validation via Route 53)..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $(ACM_REGION) \
		--template-file ./cf/acm.yaml \
		--stack-name $(ACM_STACK_NAME) \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			DomainName=$(DOMAIN_NAME) \
			HostedZoneId=$(HOSTED_ZONE_ID)
	@echo "✅ ACM certificate issued"

# AWS-only: LocalStack Hobby does not support Route 53.
# Run create-aws-cloudfront first — this target reads SiteDistributionDomain and
# ApiDistributionDomain from the cloudfront stack outputs automatically.
# Required: HOSTED_ZONE_ID (e.g. Z1234567890ABCDEF), DOMAIN_NAME (e.g. example.com)
# Optional: SITE_SUBDOMAIN (e.g. www, default: empty = apex only), API_SUBDOMAIN (default: api)
create-aws-route53:
	@[ -n "$(HOSTED_ZONE_ID)" ] || (echo "❌ HOSTED_ZONE_ID is required. Get it from the Route 53 console."; exit 1)
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required (e.g. example.com)."; exit 1)
	@echo "🌐 Creating Route 53 DNS records..."
	@SITE_DIST_DOMAIN=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(CLOUDFRONT_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`SiteDistributionDomain`].OutputValue' \
		--output text) && \
	API_DIST_DOMAIN=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(CLOUDFRONT_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`ApiDistributionDomain`].OutputValue' \
		--output text) && \
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/route53.yaml \
		--stack-name $(ROUTE53_STACK_NAME) \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			HostedZoneId=$(HOSTED_ZONE_ID) \
			DomainName=$(DOMAIN_NAME) \
			SiteDistributionDomain=$$SITE_DIST_DOMAIN \
			ApiDistributionDomain=$$API_DIST_DOMAIN \
			SiteSubdomain=$(SITE_SUBDOMAIN) \
			ApiSubdomain=$(API_SUBDOMAIN)
	@echo "✅ Route 53 DNS records created"

# AWS-only: Full custom domain setup — runs the sequence:
#   create-aws-acm → create-aws-waf → create-aws-cloudfront →
#   create-aws-route53 → create-aws-api (AllowedOrigin) →
#   update did.json → pnpm build → sync-site
# Required: DOMAIN_NAME (e.g. example.com)
# Optional: SITE_SUBDOMAIN (e.g. www), API_SUBDOMAIN (default: api)
setup-aws-domain:
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required (e.g. make setup-aws-domain DOMAIN_NAME=example.com)."; exit 1)
	@echo "🌐 Setting up custom domain $(DOMAIN_NAME)..."
	@HOSTED_ZONE_ID=$$(aws route53 list-hosted-zones-by-name \
		--dns-name "$(DOMAIN_NAME)" \
		--query "HostedZones[?Name=='$(DOMAIN_NAME).'].Id | [0]" \
		--output text | sed 's|/hostedzone/||') && \
	API_GATEWAY_ID=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(PROJECT_NAME)-api-stack \
		--query 'Stacks[0].Outputs[?OutputKey==`ApiId`].OutputValue' \
		--output text) && \
	$(MAKE) create-aws-acm \
		DOMAIN_NAME=$(DOMAIN_NAME) HOSTED_ZONE_ID=$$HOSTED_ZONE_ID && \
	$(MAKE) create-aws-waf && \
	$(MAKE) create-aws-cloudfront \
		API_GATEWAY_ID=$$API_GATEWAY_ID \
		DOMAIN_NAME=$(DOMAIN_NAME) \
		SITE_SUBDOMAIN=$(SITE_SUBDOMAIN) \
		API_SUBDOMAIN=$(API_SUBDOMAIN) && \
	$(MAKE) create-aws-route53 \
		DOMAIN_NAME=$(DOMAIN_NAME) \
		HOSTED_ZONE_ID=$$HOSTED_ZONE_ID \
		SITE_SUBDOMAIN=$(SITE_SUBDOMAIN) \
		API_SUBDOMAIN=$(API_SUBDOMAIN) && \
	$(MAKE) create-aws-api ALLOWED_ORIGIN=https://$(DOMAIN_NAME) && \
	jq --arg id "did:web:$(DOMAIN_NAME)" \
		--arg url "https://$(API_SUBDOMAIN).$(DOMAIN_NAME)" \
		'.id = $$id | .service[0].serviceEndpoint = $$url' \
		../public/.well-known/did.json > /tmp/did.json && \
	mv /tmp/did.json ../public/.well-known/did.json && \
	jq --arg domain "$(DOMAIN_NAME)" \
		--arg scope "atproto repo:site.standard.publication repo:site.standard.document repo:site.standard.graph.subscription repo:app.bsky.feed.post?action=create&action=update&action=delete repo:app.bsky.feed.like rpc:com.atproto.server.getServiceAuth?aud=* blob:image/jpeg blob:image/png blob:image/gif blob:image/webp" \
		'.client_id = ("https://" + $$domain + "/oauth-client-metadata.json") | .client_uri = ("https://" + $$domain) | .redirect_uris = [("https://" + $$domain + "/")] | .scope = $$scope' \
		../public/oauth-client-metadata.json > /tmp/oauth-client-metadata.json && \
	mv /tmp/oauth-client-metadata.json ../public/oauth-client-metadata.json && \
	$(MAKE) deploy-site
	@echo "✅ Custom domain $(DOMAIN_NAME) setup complete"

update-local-api:
	@echo "🔧 Deploying blog API lambda to LocalStack..."
	cd api && npm run build && npm run zip
	aws lambda update-function-code --profile localstack \
		--function-name blog-api \
		--zip-file fileb://api/function.zip
	@echo "✅ Lambda deployed to LocalStack"

update-aws-api:
	@echo "🔧 Deploying blog API lambda to AWS..."
	cd api && npm run build && npm run zip
	aws lambda update-function-code --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--function-name blog-api \
		--zip-file fileb://api/function.zip
	@echo "✅ Lambda deployed to AWS"

install-indexer:
	@echo "📦 Installing indexer dependencies..."
	cd indexer && npm install
	@echo "✅ Indexer dependencies installed"

build-indexer: install-indexer
	@echo "🔧 Building and packaging indexer..."
	cd indexer && npm run build && npm run zip
	@echo "✅ Indexer built: indexer/indexer.zip"

upload-run-local-indexer: build-indexer
	@echo "📤 Uploading indexer bundle to LocalStack artifacts bucket..."
	aws s3 cp --profile localstack \
		indexer/indexer.zip s3://$(ARTIFACTS_BUCKET_NAME)/$(INDEXER_S3_KEY)
	@echo "✅ Indexer bundle uploaded"

upload-indexer-aws: build-indexer
	@echo "📤 Uploading indexer bundle to AWS artifacts bucket..."
	aws s3 cp --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		indexer/indexer.zip s3://$(ARTIFACTS_BUCKET_NAME)/$(INDEXER_S3_KEY)
	@echo "✅ Indexer bundle uploaded"

create-local-indexer:
	@echo "🖥️  Deploying indexer EC2 stack to LocalStack..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/indexer.yaml \
		--stack-name $(INDEXER_STACK_NAME) \
		--capabilities CAPABILITY_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			S3BucketName=$(ARTIFACTS_BUCKET_NAME) \
			S3Key=$(INDEXER_S3_KEY) \
			DynamoDBTable=$(DYNAMODB_TABLE) \
			AwsEndpointUrl=$(LOCALSTACK_ENDPOINT)
	@echo "✅ LocalStack indexer stack deployed"

create-aws-indexer:
	@echo "🖥️  Deploying indexer EC2 stack to AWS..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/indexer.yaml \
		--stack-name $(INDEXER_STACK_NAME) \
		--capabilities CAPABILITY_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			S3BucketName=$(ARTIFACTS_BUCKET_NAME) \
			S3Key=$(INDEXER_S3_KEY) \
			DynamoDBTable=$(DYNAMODB_TABLE)
	@echo "✅ AWS indexer stack deployed"

deploy-local-indexer: upload-run-local-indexer create-local-indexer

deploy-aws-indexer: upload-indexer-aws create-aws-indexer

update-aws-indexer: upload-indexer-aws
	@echo "🔄 Updating running indexer instance via SSM..."
	@INSTANCE_ID=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(INDEXER_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' \
		--output text); \
	[ -n "$$INSTANCE_ID" ] || { echo "❌ Indexer instance not found — run deploy-aws-indexer first"; exit 1; }; \
	echo "📡 Sending update command to $$INSTANCE_ID..."; \
	COMMAND_ID=$$(aws ssm send-command \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--instance-ids "$$INSTANCE_ID" \
		--document-name "AWS-RunShellScript" \
		--parameters 'commands=["set -e","systemctl stop indexer","aws s3 cp s3://$(ARTIFACTS_BUCKET_NAME)/$(INDEXER_S3_KEY) /opt/indexer/indexer.zip","unzip -o /opt/indexer/indexer.zip -d /opt/indexer","rm /opt/indexer/indexer.zip","systemctl start indexer"]' \
		--query 'Command.CommandId' \
		--output text); \
	echo "⏳ Waiting for update to complete (command $$COMMAND_ID)..."; \
	aws ssm wait command-executed \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--instance-id "$$INSTANCE_ID" \
		--command-id "$$COMMAND_ID"; \
	echo "✅ Indexer updated and restarted"

restart-aws-indexer:
	@INSTANCE_ID=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(INDEXER_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' \
		--output text); \
	[ -n "$$INSTANCE_ID" ] || { echo "❌ Indexer instance not found — run deploy-aws-indexer first"; exit 1; }; \
	echo "🔄 Restarting indexer on $$INSTANCE_ID..."; \
	COMMAND_ID=$$(aws ssm send-command \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--instance-ids "$$INSTANCE_ID" \
		--document-name "AWS-RunShellScript" \
		--parameters 'commands=["systemctl restart indexer"]' \
		--query 'Command.CommandId' \
		--output text); \
	aws ssm wait command-executed \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--instance-id "$$INSTANCE_ID" \
		--command-id "$$COMMAND_ID"; \
	echo "✅ Indexer restarted"

shell-aws-indexer:
	@INSTANCE_ID=$$(aws cloudformation describe-stacks \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--stack-name $(INDEXER_STACK_NAME) \
		--query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' \
		--output text); \
	[ -n "$$INSTANCE_ID" ] || { echo "❌ Indexer instance not found — run deploy-aws-indexer first"; exit 1; }; \
	echo "🔌 Opening SSM shell on $$INSTANCE_ID..."; \
	aws ssm start-session \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--target "$$INSTANCE_ID"

# LocalStack does not run real EC2 containers, so for local dev the indexer
# runs as a plain process against LocalStack DynamoDB on localhost:4566.
run-local-indexer: build-indexer
	@echo "🏃 Running indexer against LocalStack..."
	AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test \
	AWS_REGION=$(AWS_REGION) \
	AWS_ENDPOINT_URL=http://localhost:4566 \
	DYNAMODB_TABLE=$(DYNAMODB_TABLE) \
	CURSOR_FILE=/tmp/indexer-cursor \
	NODE_ENV=production \
	node indexer/dist/index.js


install-backfill:
	@echo "📦 Installing backfill dependencies..."
	cd backfill && npm install
	@echo "✅ Backfill dependencies installed"

build-backfill: install-backfill
	@echo "🔧 Building and packaging backfill..."
	cd backfill && npm run build && npm run zip
	@echo "✅ Backfill built: backfill/backfill.zip"

create-local-backfill:
	@echo "🖥️  Deploying backfill Lambda stack to LocalStack..."
	aws cloudformation deploy --profile localstack \
		--template-file ./cf/backfill.yaml \
		--stack-name $(BACKFILL_STACK_NAME) \
		--capabilities CAPABILITY_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			TriggerBucketName=$(BACKFILL_TRIGGER_BUCKET) \
			DynamoDBTable=$(DYNAMODB_TABLE) \
			AwsEndpointUrl=$(LOCALSTACK_ENDPOINT)
	@echo "✅ LocalStack backfill stack deployed"

create-aws-backfill:
	@echo "🖥️  Deploying backfill Lambda stack to AWS..."
	aws cloudformation deploy --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--template-file ./cf/backfill.yaml \
		--stack-name $(BACKFILL_STACK_NAME) \
		--capabilities CAPABILITY_IAM \
		--no-fail-on-empty-changeset \
		--parameter-overrides \
			TriggerBucketName=$(BACKFILL_TRIGGER_BUCKET) \
			DynamoDBTable=$(DYNAMODB_TABLE)
	@echo "✅ AWS backfill stack deployed"

deploy-local-backfill-lambda: build-backfill
	@echo "🔧 Deploying backfill Lambda code to LocalStack..."
	aws lambda update-function-code --profile localstack \
		--function-name backfill \
		--zip-file fileb://backfill/backfill.zip
	@echo "✅ Backfill Lambda deployed to LocalStack"

deploy-aws-backfill-lambda: build-backfill
	@echo "🔧 Deploying backfill Lambda code to AWS..."
	aws lambda update-function-code --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--function-name backfill \
		--zip-file fileb://backfill/backfill.zip
	@echo "✅ Backfill Lambda deployed to AWS"

deploy-local-backfill: create-local-backfill deploy-local-backfill-lambda

deploy-aws-backfill: create-aws-backfill deploy-aws-backfill-lambda

update-local-backfill: deploy-local-backfill-lambda

update-aws-backfill: deploy-aws-backfill-lambda

run-local-backfill:
	@[ -n "$(DIDS_FILE)" ] || (echo "❌ DIDS_FILE is required — e.g. make run-local-backfill DIDS_FILE=dids.txt"; exit 1)
	@echo "📤 Uploading $(DIDS_FILE) to LocalStack backfill trigger bucket..."
	aws s3 cp --profile localstack \
		$(DIDS_FILE) s3://$(BACKFILL_TRIGGER_BUCKET)/$(BACKFILL_DIDS_S3_KEY)
	@echo "✅ Upload complete — backfill Lambda triggered"

run-aws-backfill:
	@[ -n "$(DIDS_FILE)" ] || (echo "❌ DIDS_FILE is required — e.g. make run-aws-backfill DIDS_FILE=dids.txt"; exit 1)
	@echo "📤 Uploading $(DIDS_FILE) to AWS backfill trigger bucket..."
	aws s3 cp --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		$(DIDS_FILE) s3://$(BACKFILL_TRIGGER_BUCKET)/$(BACKFILL_DIDS_S3_KEY)
	@echo "✅ Upload complete — backfill Lambda triggered"

logs-aws-backfill:
	@echo "📋 Fetching latest backfill Lambda logs..."
	@# head -1: --output text with --max-items appends a pagination NextToken line ("None") after the result
	@STREAM=$$(aws logs describe-log-streams --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--log-group-name /aws/lambda/backfill \
		--order-by LastEventTime --descending --max-items 1 \
		--query 'logStreams[0].logStreamName' --output text | head -1) && \
	aws logs get-log-events --profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--log-group-name /aws/lambda/backfill \
		--log-stream-name "$$STREAM" \
		--query 'events[].message' --output text

build-site:
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required (e.g. make build-site DOMAIN_NAME=example.com)."; exit 1)
	VITE_API_URL=$(VITE_API_URL) VITE_API_DID=$(API_DID) pnpm --dir .. build

deploy-site: build-site sync-site invalidate-site

invalidate-site:
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required."; exit 1)
	@echo "🔄 Invalidating CloudFront cache for $(DOMAIN_NAME)..."
	@DIST_ID=$$(aws cloudfront list-distributions \
		--profile $${AWS_PROFILE} \
		--query "DistributionList.Items[?Aliases.Items && contains(Aliases.Items, '$(DOMAIN_NAME)')].Id | [0]" \
		--output text) && \
	[ -n "$$DIST_ID" ] && [ "$$DIST_ID" != "None" ] || { echo "⚠️  No distribution found for $(DOMAIN_NAME) — skipping"; exit 0; }; \
	aws cloudfront create-invalidation \
		--profile $${AWS_PROFILE} \
		--distribution-id "$$DIST_ID" \
		--paths "/*" \
		--query 'Invalidation.Id' --output text && \
	echo "✅ Invalidation queued"

list-invalidations:
	@[ -n "$(DOMAIN_NAME)" ] || (echo "❌ DOMAIN_NAME is required."; exit 1)
	@DIST_ID=$$(aws cloudfront list-distributions \
		--profile $${AWS_PROFILE} \
		--query "DistributionList.Items[?Aliases.Items && contains(Aliases.Items, '$(DOMAIN_NAME)')].Id | [0]" \
		--output text) && \
	aws cloudfront list-invalidations \
		--profile $${AWS_PROFILE} \
		--distribution-id "$$DIST_ID" \
		--query 'InvalidationList.Items[*].[Id,Status,CreateTime]' \
		--output table

sync-site:
	@echo "🚀 Syncing build/client/ to s3://$(BUCKET_NAME)..."
	aws s3 sync ../build/client/ s3://$(BUCKET_NAME)/ \
		--profile $${AWS_PROFILE} --region $${AWS_REGION} \
		--delete
	@echo "✅ Site synced"

# Proxy LocalStack S3 through Caddy on :8080. API calls go directly to LocalStack
# API Gateway (CORS handled by the API Gateway CORS config in blog-api.yaml).
serve-local:
	@tmp=$$(mktemp); \
	printf '{\n  admin off\n}\nhttp://:8080 {\n  reverse_proxy localhost:4566 {\n    header_up Host $(BUCKET_NAME).s3-website.$(AWS_REGION).localhost.localstack.cloud\n    @notfound status 404\n    handle_response @notfound {\n      rewrite * /index.html\n      reverse_proxy localhost:4566 {\n        header_up Host $(BUCKET_NAME).s3-website.$(AWS_REGION).localhost.localstack.cloud\n      }\n    }\n  }\n}\n' > $$tmp; \
	caddy run --adapter caddyfile --config $$tmp

local:
	@echo "🚀 Deploying to local environment..."
	./deploy-local.sh

aws:
	@echo "🚀 Deploying to AWS..."
	./deploy-aws.sh

# .EXPORT_ALL_VARIABLES would bake VITE_API_URL=https://api. (empty DOMAIN_NAME) into
# the bundle, overriding .env.local. Strip those vars so Vite reads .env.local instead.
update-local-site:
	@echo "🏗️  Building frontend..."
	env -u VITE_API_URL -u VITE_API_DID pnpm --dir .. build
	@echo "🚀 Syncing build/client/ to LocalStack s3://$(BUCKET_NAME)..."
	aws s3 sync ../build/client/ s3://$(BUCKET_NAME)/ \
		--profile localstack --region $(AWS_REGION) \
		--delete
	@echo "✅ Site updated"

update-local: update-local-api update-local-site

update-iframely-sri:
	@echo "🔒 Fetching cdn.iframe.ly/embed.js and computing SRI hash..."
	@hash=$$(curl -fsSL https://cdn.iframe.ly/embed.js | openssl dgst -sha256 -binary | openssl base64 | tr -d '\n'); \
	sri="sha256-$$hash"; \
	echo "  $$sri"; \
	sed -i "s|const EMBED_JS_INTEGRITY = '.*'|const EMBED_JS_INTEGRITY = '$$sri'|" ../app/components/IframelyEmbed.tsx; \
	sed -i "s|expect(script.integrity).toBe('.*')|expect(script.integrity).toBe('$$sri')|" ../app/components/IframelyEmbed.test.tsx; \
	echo "✅ Updated IframelyEmbed.tsx and IframelyEmbed.test.tsx"
