# ============================================================================
# Krow backend — developer tasks
#
# Migration commands are thin wrappers over the golang-migrate CLI. The
# migration FILES are the source of truth for the schema; nothing here or in
# go-api/ ever issues DDL of its own.
#
#   brew install golang-migrate     # or see github.com/golang-migrate/migrate
# ============================================================================

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

# Capture APP_ENV as the process environment supplied it, BEFORE .env is
# included. Make gives an included assignment precedence over the environment,
# so without this `APP_ENV=production make migrate-down` would silently read
# `development` out of .env and the guard below would not fire.
APP_ENV_FROM_ENVIRONMENT := $(APP_ENV)

ifneq (,$(wildcard .env))
include .env
export
endif

# The environment wins; .env is only the fallback. A command-line override
# (`make migrate-down APP_ENV=production`) outranks both, as Make intends.
ifneq (,$(APP_ENV_FROM_ENVIRONMENT))
APP_ENV := $(APP_ENV_FROM_ENVIRONMENT)
endif

MIGRATIONS_DIR ?= ./migrations
DATABASE_SCHEMA ?= public
DATABASE_SSLMODE ?= disable

# URL-encode the credentials and the database name. "Krow-force" is mixed-case
# and hyphenated, and a password can contain anything; neither is safe raw.
enc = $(shell printf '%s' '$(1)' | python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')

DB_URL = postgres://$(call enc,$(DATABASE_USER)):$(call enc,$(DATABASE_PASSWORD))@$(DATABASE_HOST):$(DATABASE_PORT)/$(call enc,$(DATABASE_NAME))?sslmode=$(DATABASE_SSLMODE)&search_path=$(DATABASE_SCHEMA)
MIGRATE = migrate -path $(MIGRATIONS_DIR) -database "$(DB_URL)"

PSQL = psql -h $(DATABASE_HOST) -p $(DATABASE_PORT) -U $(DATABASE_USER) -d "$(DATABASE_NAME)"

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

# ── Go ──────────────────────────────────────────────────────────────────────

.PHONY: run
run: ## Run the API against the local database
	cd go-api && go run ./cmd/api

.PHONY: build
build: ## Compile the API to go-api/bin/api
	cd go-api && go build -o bin/api ./cmd/api

.PHONY: tidy
tidy: ## go mod tidy
	cd go-api && go mod tidy

.PHONY: fmt
fmt: ## gofmt the module
	cd go-api && gofmt -w ./cmd ./internal

.PHONY: vet
vet: ## go vet the module
	cd go-api && go vet ./...

.PHONY: test
test: ## Run the Go tests
	cd go-api && go test ./...

.PHONY: check
check: fmt vet test ## Format, vet and test

# ── Database ────────────────────────────────────────────────────────────────

.PHONY: db-create
db-create: ## Create the database if it does not exist (never drops anything)
	@if psql -h $(DATABASE_HOST) -p $(DATABASE_PORT) -U $(DATABASE_USER) -d postgres -tAc \
	   "SELECT 1 FROM pg_database WHERE datname = '$(DATABASE_NAME)'" | grep -q 1; then \
	  echo "database \"$(DATABASE_NAME)\" already exists — nothing to do"; \
	else \
	  psql -h $(DATABASE_HOST) -p $(DATABASE_PORT) -U $(DATABASE_USER) -d postgres -c \
	    'CREATE DATABASE "$(DATABASE_NAME)"' && echo "created \"$(DATABASE_NAME)\""; \
	fi

.PHONY: seed
seed: ## Load the frontend demo dataset (idempotent upsert in one transaction)
	cd go-api && SEED_FIXTURE_PATH=$(CURDIR)/seed/fixtures/seed.json go run ./cmd/seed

.PHONY: gen-resources
gen-resources: ## Regenerate domain descriptors from the live schema
	python3 scripts/gen_resources.py > go-api/internal/domain/resources_gen.go
	cd go-api && gofmt -w ./internal/domain

.PHONY: db-health
db-health: ## Ask the running API for its database health
	@curl -fsS http://$(HTTP_HOST):$(HTTP_PORT)/health | python3 -m json.tool

# ── Migrations ──────────────────────────────────────────────────────────────

.PHONY: migrate-up
migrate-up: ## Apply all pending migrations
	$(MIGRATE) up

.PHONY: migrate-down
migrate-down: ## Roll back exactly one migration (guarded outside development)
	@if [ "$(APP_ENV)" != "development" ]; then \
	  echo "refusing: migrate-down is only permitted when APP_ENV=development (got '$(APP_ENV)')"; \
	  echo "roll back staging/production deliberately, with a reviewed plan and a fresh backup."; \
	  exit 1; \
	fi
	$(MIGRATE) down 1

.PHONY: migrate-status
migrate-status: ## Show the currently applied migration version
	@$(MIGRATE) version

.PHONY: migrate-new
migrate-new: ## Create a new migration pair: make migrate-new NAME=add_widgets
	@test -n "$(NAME)" || { echo "usage: make migrate-new NAME=add_widgets"; exit 1; }
	migrate create -ext sql -dir $(MIGRATIONS_DIR) -seq $(NAME)

.PHONY: migrate-force
migrate-force: ## Clear a dirty state: make migrate-force VERSION=1
	@test -n "$(VERSION)" || { echo "usage: make migrate-force VERSION=1"; exit 1; }
	$(MIGRATE) force $(VERSION)

# There is deliberately no `migrate-drop` target. golang-migrate's `drop`
# command deletes every table in the database; it must never be one keystroke
# away from a developer who meant `down`. Run it by hand if you truly need it.

.PHONY: verify-schema
verify-schema: ## Print the tables, enums, FKs and indexes that exist in the target schema
	@$(PSQL) -f scripts/verify_schema.sql

# ── Docker ──────────────────────────────────────────────────────────────────
#
# These read infrastructure/.env, NOT the repository-root .env that `make run`
# uses. Two files on purpose: the root one points at a developer's database
# from the host, the infrastructure one points at a deployment's database from
# inside a container, and DATABASE_HOST is different in each.

COMPOSE_DIR   := infrastructure
COMPOSE_FILES := -f docker-compose.yml
# Layer the local database in with: make docker-up LOCAL_DB=1
ifdef LOCAL_DB
COMPOSE_FILES += -f docker-compose.local-db.yml
endif
COMPOSE = cd $(COMPOSE_DIR) && docker compose $(COMPOSE_FILES)

.PHONY: docker-build
docker-build: ## Build the API image
	$(COMPOSE) build

.PHONY: docker-up
docker-up: ## Start the stack (migrations run first). LOCAL_DB=1 adds PostgreSQL
	$(COMPOSE) up -d --build
	@echo "api:    http://127.0.0.1:8080/health"
	@echo "logs:   make docker-logs"

.PHONY: docker-down
docker-down: ## Stop the stack, keeping volumes
	$(COMPOSE) down

.PHONY: docker-logs
docker-logs: ## Follow the API logs
	$(COMPOSE) logs -f api

.PHONY: docker-ps
docker-ps: ## Show stack status and health
	$(COMPOSE) ps

.PHONY: docker-migrate
docker-migrate: ## Apply migrations only, without starting the API
	$(COMPOSE) run --rm migrate

.PHONY: docker-seed
docker-seed: ## Load the demo fixture into the deployment's database
	$(COMPOSE) run --rm --entrypoint /usr/local/bin/seed api

.PHONY: docker-setpassword
docker-setpassword: ## Set a password: make docker-setpassword EMAIL=demo@krow.app
	@test -n "$(EMAIL)" || { echo "usage: make docker-setpassword EMAIL=demo@krow.app"; exit 1; }
	$(COMPOSE) run --rm -it --entrypoint /usr/local/bin/setpassword api -email $(EMAIL)

.PHONY: docker-config
docker-config: ## Render the fully resolved compose configuration
	$(COMPOSE) config
