# Single entry point for both environments (design D14).
#
# Every target names its compose files explicitly. Compose's auto-loaded
# docker-compose.override.yml is deliberately unused, so `make prod` can never silently
# pick up development settings.

SHELL := /bin/bash
.DEFAULT_GOAL := help

DC          := docker compose
BASE        := -f docker-compose.yml
DEV         := $(BASE) -f docker-compose.dev.yml --env-file .env.dev
PROD        := $(BASE) -f docker-compose.prod.yml --env-file .env.prod

# Most targets act on dev. Pass ENV=prod to point them at production instead:
#   make migrate ENV=prod
ENV ?= dev
ifeq ($(ENV),prod)
COMPOSE := $(DC) $(PROD)
ENVFILE := .env.prod
else
COMPOSE := $(DC) $(DEV)
ENVFILE := .env.dev
endif

.PHONY: help dev prod down logs ps migrate migration seed walkthrough test lint format client psql backup restore clean check-env

help: ## Show available targets
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-12s\033[0m %s\n", $$1, $$2}'

check-env: ## Fail early with a useful message if the env file is missing
	@test -f $(ENVFILE) || { \
		echo "Missing $(ENVFILE). Copy .env.example and fill it in:"; \
		echo "  cp .env.example $(ENVFILE)"; \
		exit 1; \
	}

dev: check-env ## Start the development stack (hot reload, DRY_RUN=true, http://localhost)
	$(DC) $(DEV) up --build -d
	@echo "Dev stack up: http://localhost  (API docs at http://localhost/api/docs)"

# One command, whole deployment. The order is the point: images are built before anything
# is torn down, the database comes up alone and migrations run against it while nothing is
# serving, and only then does the rest of the stack start. Starting everything at once and
# migrating afterwards means the API serves requests against a schema it does not match for
# however long `alembic upgrade` takes.
#
# Safe to re-run. Every step is idempotent, so this is also the deploy-an-update command.
prod: ## Build, migrate and start the production stack (TLS) — the whole deployment
	@test -f .env.prod || { echo "Missing .env.prod — refusing to start production."; exit 1; }
	@echo "==> Preparing the media directory"
	@mkdir -p media
	@# Card artwork is a bind mount and the API no longer runs as root (api/Dockerfile),
	@# so the host directory must belong to the image's uid or every upload fails with
	@# EACCES — at the moment an admin publishes a card, not at boot. Fails harmlessly on
	@# Windows and macOS, where the Docker bind mount ignores ownership anyway.
	@chown -R 1001:1001 media 2>/dev/null || echo "    (could not chown media/ — fine on Windows/macOS; on Linux run: sudo chown -R 1001:1001 media)"
	@echo "==> Building images"
	$(DC) $(PROD) build
	@echo "==> Starting the database"
	$(DC) $(PROD) up -d --wait postgres
	@echo "==> Applying migrations"
	$(DC) $(PROD) run --rm --no-deps api alembic upgrade head
	@echo "==> Starting the stack"
	$(DC) $(PROD) up -d --wait
	@echo ""
	@echo "Production stack up and healthy."
	@$(DC) $(PROD) ps

down: ## Stop the stack for the selected ENV (default dev)
	$(COMPOSE) down

logs: ## Tail logs (make logs S=api to follow one service)
	$(COMPOSE) logs -f $(S)

ps: ## Show running containers
	$(COMPOSE) ps

migrate: check-env ## Apply database migrations
	$(COMPOSE) run --rm api alembic upgrade head

migration: check-env ## Autogenerate a migration: make migration name="add guests"
	@test -n "$(name)" || { echo 'Usage: make migration name="what changed"'; exit 1; }
	$(COMPOSE) run --rm api alembic revision --autogenerate -m "$(name)"

seed: check-env ## Load the demo wedding, events, templates and first admin (dev only)
	$(COMPOSE) run --rm api python -m scripts.seed

walkthrough: check-env ## Drive the whole onboarding path end to end (dev only)
	$(COMPOSE) exec api python -m scripts.walkthrough

test: check-env ## Run the API test suite
	$(COMPOSE) run --rm api pytest -q

lint: check-env ## Lint and type-check both applications
	$(COMPOSE) run --rm api ruff check .
	$(COMPOSE) run --rm api mypy app
	$(COMPOSE) run --rm web npm run typecheck

format: check-env ## Auto-fix formatting and import order
	$(COMPOSE) run --rm api ruff check --fix .
	$(COMPOSE) run --rm api ruff format .

client: check-env ## Regenerate the TypeScript API client from OpenAPI
	./scripts/generate-client.sh

psql: check-env ## Open a psql shell
	$(COMPOSE) exec postgres psql -U $${POSTGRES_USER:-rsvp} -d $${POSTGRES_DB:-rsvp}

# Two things are stateful now, not one. Card artwork lives on disk (design D4), so a
# database-only backup restores rows pointing at files that no longer exist — the card
# record survives and the card does not. Both are written with the same timestamp so a
# restore can pair them without guessing.
backup: ## Dump the database AND card media to backups/ (timestamped)
	@mkdir -p backups
	@stamp=$$(date +%Y-%m-%d_%H%M); \
	$(COMPOSE) exec -T postgres pg_dump -U $${POSTGRES_USER:-rsvp} $${POSTGRES_DB:-rsvp} \
		> backups/rsvp_$$stamp.sql; \
	if [ -d media ] && [ -n "$$(ls -A media 2>/dev/null)" ]; then \
		tar -czf backups/media_$$stamp.tar.gz media; \
		echo "Wrote backups/rsvp_$$stamp.sql and backups/media_$$stamp.tar.gz"; \
	else \
		echo "Wrote backups/rsvp_$$stamp.sql (no media to archive)"; \
	fi

restore: ## Restore a dump: make restore FILE=backups/rsvp_....sql (media archive picked up automatically)
	@test -n "$(FILE)" || { echo 'Usage: make restore FILE=backups/rsvp_....sql'; exit 1; }
	$(COMPOSE) exec -T postgres psql -U $${POSTGRES_USER:-rsvp} -d $${POSTGRES_DB:-rsvp} < $(FILE)
	@stamp=$$(basename "$(FILE)" .sql | sed 's/^rsvp_//'); \
	archive="backups/media_$$stamp.tar.gz"; \
	if [ -f "$$archive" ]; then \
		tar -xzf "$$archive" -C .; \
		echo "Restored media from $$archive"; \
	else \
		echo "WARNING: no $$archive alongside this dump — card designs will point at missing artwork."; \
	fi

clean: ## Stop the dev stack and delete its volumes (destroys local data)
	$(DC) $(DEV) down -v
