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

# Load .env if present so targets can use the same variables as compose.
ifneq (,$(wildcard ./.env))
include .env
export
endif

# === Container engine detection (docker OR podman) ============================
#
# Auto-detects on PATH, with docker preferred when both are installed.
# Override either variable from the environment or the command line:
#   make up ENGINE=podman
#   make up COMPOSE="podman compose"
#
ENGINE ?= $(shell \
  if command -v docker >/dev/null 2>&1; then echo docker; \
  elif command -v podman >/dev/null 2>&1; then echo podman; \
  fi)

ifeq ($(strip $(ENGINE)),)
$(error Neither docker nor podman found in PATH. Install one, or set ENGINE=...)
endif

# Pick the right compose driver based on the chosen engine.
# - docker         → "docker compose"   (compose v2 plugin)
# - podman 4.0+    → "podman compose"
# - older podman   → "podman-compose"   (legacy Python wrapper)
ifndef COMPOSE
ifeq ($(ENGINE),docker)
COMPOSE := docker compose
else
COMPOSE := $(shell \
  if podman compose version >/dev/null 2>&1; then echo "podman compose"; \
  elif command -v podman-compose >/dev/null 2>&1; then echo "podman-compose"; \
  else echo "podman compose"; fi)
endif
endif

# Project name is mostly used to look up volumes / containers via raw engine calls.
PROJECT ?= metamorph

# Suppress the noisy `>>>> Executing external compose provider …` banner that
# `podman compose` emits on every invocation (harmless, but spammy in logs).
export PODMAN_COMPOSE_WARNING_LOGS = false

.PHONY: help env engine up down build rebuild logs logs-api ps health shell-api shell-db psql \
        dev dev-api dev-front lint lint-api lint-front fmt test test-api test-front \
        e2e e2e-install e2e-report e2e-up wait-healthy \
        migrate migrate-down migrate-revision migrate-status \
        seed-mitre print-install-token print-install-token-force \
        volumes inspect-health clean

help: ## Show this help
	@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_0-9-]+:.*##/ {printf "  \033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
	@printf "\n  Container engine in use: \033[33m%s\033[0m  |  compose: \033[33m%s\033[0m\n" "$(ENGINE)" "$(COMPOSE)"

engine: ## Print the detected container engine and compose driver
	@echo "ENGINE=$(ENGINE)"
	@echo "COMPOSE=$(COMPOSE)"

env: ## Bootstrap a local .env from .env.example if missing
	@test -f .env || (cp .env.example .env && echo "Created .env — edit it before 'make up'")

# === Compose lifecycle ========================================================

up: env ## Build (if needed) and start all services
	$(COMPOSE) up -d --build

down: ## Stop and remove containers (keep volumes)
	$(COMPOSE) down

build: ## Build images without starting
	$(COMPOSE) build

rebuild: ## Force rebuild without cache
	$(COMPOSE) build --no-cache

logs: ## Tail logs from all services
	$(COMPOSE) logs -f --tail=200

logs-api: ## Tail only the api container logs (useful to inspect JSON log lines)
	$(COMPOSE) logs -f --tail=200 api

ps: ## List running services
	$(COMPOSE) ps

shell-api: ## Shell into the api container
	$(COMPOSE) exec api bash

shell-db: ## Shell into the db container
	$(COMPOSE) exec db sh

psql: ## Open psql in the db container
	$(COMPOSE) exec db psql -U $(POSTGRES_USER) -d $(POSTGRES_DB)

# === Container introspection (engine-agnostic) ================================

volumes: ## List the named volumes created by this project
	@$(ENGINE) volume ls --filter "name=$(PROJECT)_"

inspect-health: ## Print the health status of every container in the project
	@for c in $(PROJECT)-db $(PROJECT)-api $(PROJECT)-front; do \
		printf "%-30s " "$$c"; \
		$(ENGINE) inspect --format '{{.State.Health.Status}}' "$$c" 2>/dev/null || echo "(no-healthcheck or stopped)"; \
	done

# === Local dev (no container) =================================================

dev: ## Run api + front locally in parallel (Ctrl-C stops both)
	@$(MAKE) -j2 --no-print-directory dev-api dev-front

dev-api: ## Run Flask in dev mode
	cd backend && APP_ENV=dev uv run flask --app app.main run --debug --host 0.0.0.0 --port 8000

dev-front: ## Run Vite dev server
	cd frontend && npm run dev

# === Quality ==================================================================

lint: lint-api lint-front ## Lint everything

lint-api:
	cd backend && uv run ruff check . && uv run ruff format --check .

lint-front:
	cd frontend && npm run lint && npm run typecheck

fmt: ## Auto-format
	cd backend && uv run ruff format .
	cd frontend && npm run format

test: test-api test-front ## Run all tests

test-api: ## Run backend pytest in an ephemeral container against the live DB
	@echo "Building backend test image (target: test)…"
	@$(ENGINE) build -q --target test -t metamorph-api-test ./backend > /dev/null
	@echo "Running pytest…"
	$(ENGINE) run --rm \
		--network $(PROJECT)_metamorph \
		-e APP_ENV=test \
		-e POSTGRES_DB=$(POSTGRES_DB) \
		-e POSTGRES_USER=$(POSTGRES_USER) \
		-e POSTGRES_PASSWORD=$(POSTGRES_PASSWORD) \
		-e POSTGRES_HOST=db \
		-e POSTGRES_PORT=5432 \
		-e JWT_SECRET=test-only-secret-not-checked-in-this-mode \
		-e LOG_LEVEL=WARNING \
		-e FRONT_ORIGIN=http://localhost:8080 \
		metamorph-api-test

test-front:
	cd frontend && npm test --if-present

# === End-to-end tests (Playwright) ============================================

e2e-install: ## Install Playwright deps + chromium browser (use sudo on Debian/Ubuntu)
	cd e2e && npm install && npx playwright install --with-deps chromium

e2e: wait-healthy ## Run the e2e suite against the running stack
	cd e2e && BASE_URL=http://localhost:$(or $(HOST_FRONT_PORT),8080) npm test

e2e-report: ## Open the latest Playwright HTML report
	cd e2e && npx playwright show-report

e2e-up: ## Bring the stack up, wait healthy, then run e2e
	$(MAKE) up
	$(MAKE) e2e

health: ## Curl the health endpoint via the front nginx (one shot)
	@curl -sSf "http://localhost:$(or $(HOST_FRONT_PORT),8080)/api/v1/health" \
		&& echo "" \
		|| (echo "  unreachable — is 'make up' done?"; exit 1)

wait-healthy: ## Wait until the front+api are reachable (60s timeout)
	@port=$(or $(HOST_FRONT_PORT),8080); \
	echo "Waiting for http://localhost:$$port/api/v1/health …"; \
	for i in $$(seq 1 30); do \
		if curl -sf "http://localhost:$$port/api/v1/health" > /dev/null; then \
			echo "  ready after $$((i*2))s"; exit 0; \
		fi; \
		sleep 2; \
	done; \
	echo "  timeout after 60s — check 'make ps' and 'make logs'"; exit 1

# === App-specific commands (placeholders for later milestones) ================

migrate: ## Apply DB migrations (alembic upgrade head, runs inside the api container)
	$(COMPOSE) exec api alembic upgrade head

migrate-down: ## Roll back the latest migration
	$(COMPOSE) exec api alembic downgrade -1

migrate-revision: ## Generate a new autogenerated migration: make migrate-revision MSG="my message"
	@test -n "$(MSG)" || (echo "Usage: make migrate-revision MSG=\"short description\""; exit 1)
	$(COMPOSE) exec api alembic revision --autogenerate -m "$(MSG)"

migrate-status: ## Show current revision and any pending migrations
	$(COMPOSE) exec api alembic current
	@echo "---"
	$(COMPOSE) exec api alembic heads

seed-mitre: ## Seed MITRE ATT&CK Enterprise dataset (M4)
	$(COMPOSE) exec api flask --app app.cli metamorph seed-mitre

print-install-token: ## Print the bootstrap install token (M2)
	$(COMPOSE) exec api flask --app app.cli metamorph print-install-token

print-install-token-force: ## Force-mint a fresh install token (M2, --force)
	$(COMPOSE) exec api flask --app app.cli metamorph print-install-token --force

clean: ## Remove containers, networks AND volumes (DESTRUCTIVE)
	$(COMPOSE) down -v
