# ─── Fluksio app Makefile ───
# Convenience targets for development, testing, linting, and deployment.
# The workspace root delegates to these (see ../Makefile).

.PHONY: dev-utils dev dev-local up down update install dev-backend dev-frontend \
        generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
        lint-frontend umami clean help

COMPOSE_ROOT := $(CURDIR)
# Explicit project name keeps this stack isolated from the sibling website
# stack (otherwise both default to "docker", the directory their compose
# files live in).
COMPOSE_PROJECT := fluksio-app
# Compose interpolation needs the project-local .env before reading compose.yml.
COMPOSE := docker compose -p $(COMPOSE_PROJECT) --env-file $(COMPOSE_ROOT)/.env
COMPOSE_PROD := $(COMPOSE) -f docker/compose.yml
# Production also runs autoheal, which restarts the backend when its deep
# health check fails. It is profile-gated because it mounts the Docker socket.
COMPOSE_PROD_RUN := $(COMPOSE_PROD) --profile autoheal
COMPOSE_DEV := $(COMPOSE_PROD) -f docker/compose.dev.yml
# Integrated local stack: dev stack wired onto the shared `proxy` network.
COMPOSE_LOCAL := $(COMPOSE_DEV) -f docker/compose.local.yml

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

# ── Development (Docker) ──────────────────────────────────────────

dev-utils:  ## Start only the utility containers (db, adminer, proxy, mailcatcher, prestart)
	$(COMPOSE_DEV) up --build db adminer proxy mailcatcher prestart

dev:  ## Start the full dev stack (includes a local Traefik proxy)
	$(COMPOSE_DEV) up --build

dev-local:  ## Start the integrated local stack (called by the root `make dev`)
	DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} \
	  $(COMPOSE_LOCAL) up --build -d proxy db adminer prestart backend frontend mailcatcher

up:  ## Start the production stack
	$(COMPOSE_PROD_RUN) up --build -d

umami:  ## Provision + start the optional Umami site-analytics service (idempotent)
	# Guard: the dashboard is internet-facing on analytics.$(DOMAIN); refuse to
	# start when its session-signing secret is unset or still the placeholder.
	@secret=$$(grep -E '^UMAMI_APP_SECRET=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
	if [ -z "$$secret" ] || [ "$$secret" = "changethis" ]; then \
		echo "ERROR: UMAMI_APP_SECRET is empty or 'changethis' in .env — run the workspace root's scripts/setup.sh (or set it: openssl rand -hex 32) before 'make umami'." >&2; \
		exit 1; \
	fi
	# --no-recreate: reuse an already-running db instead of recreating it under
	# the prod compose set, which would blip every service that depends on it.
	$(COMPOSE_PROD) up -d --wait --no-recreate db
	# Least-privilege role owning only the umami database, so the third-party
	# image never holds the shared superuser credentials.
	@pguser=$$(grep -E '^POSTGRES_USER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); pguser=$${pguser:-postgres}; \
	role=$$(grep -E '^UMAMI_DB_USER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); role=$${role:-umami}; \
	pw=$$(grep -E '^UMAMI_DB_PASSWORD=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
	db=$$(grep -E '^UMAMI_DB=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); db=$${db:-umami}; \
	if [ -z "$$pw" ] || [ "$$pw" = "changethis" ]; then \
		echo "ERROR: UMAMI_DB_PASSWORD is empty or 'changethis' in .env — run the workspace root's scripts/setup.sh before 'make umami'." >&2; \
		exit 1; \
	fi; \
	$(COMPOSE_PROD) exec -T db psql -v ON_ERROR_STOP=1 -U "$$pguser" -d postgres \
		-v role="$$role" -v pw="$$pw" -v db="$$db" < docker/provision-umami-db.sql
	# --no-deps: only (re)create umami, never restart the shared db underneath it.
	$(COMPOSE_PROD) --profile analytics up -d --no-deps umami

update:  ## Pull, rebuild using the layer cache, and recreate changed containers
	git pull
	$(COMPOSE_PROD_RUN) build
	$(COMPOSE_PROD_RUN) up -d --remove-orphans
	docker image prune -f

down:  ## Stop all running containers
	-$(COMPOSE_LOCAL) down
	-$(COMPOSE_PROD_RUN) down

# ── Development (local, no Docker) ───────────────────────────────
# Run `make dev-backend` and `make dev-frontend` in two separate terminals.

install:  ## Install all dependencies (backend + frontend)
	cd backend && uv sync
	cd frontend && bun install

dev-backend:  ## Start the FastAPI backend with hot-reload (local)
	cd backend && uv run fastapi dev app/main.py

dev-frontend:  ## Start the Vite dev server (local)
	cd frontend && bun dev

generate-client:  ## Regenerate the frontend SDK from the backend's OpenAPI schema
	bash scripts/generate-client.sh

seed-example:  ## Seed the querying-chart example (needs a running stack + InfluxDB)
	cd backend && uv run python ../scripts/seed_example_chart.py

seed-demo:  ## Seed the training-run example: a batch flow and its dashboard
	cd backend && uv run python ../scripts/seed_demo_training.py

seed-house:  ## Seed the house write-path rig (needs the real broker reachable)
	cd backend && uv run python ../scripts/seed_house_control.py

seed-aircon:  ## Seed the aircon write-path rig (needs the unit reachable)
	cd backend && uv run python ../scripts/seed_aircon_control.py

# Operators of the hosted demo only — NOT part of any deployment, and nothing a
# self-hosted instance needs. It wipes and recreates its three flows and its
# dashboard, so re-running it is how the public demo is reset.
# `uv run` does not read .env, so the credentials are lifted out of it here;
# anything already exported wins, which is how a remote instance is targeted:
#   API_URL=https://api.example.com make seed-hosted-demo
seed-hosted-demo:  ## Seed the hosted demo panel (operators only; API_URL selects the instance)
	@user=$$(grep -E '^FIRST_SUPERUSER=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
	pass=$$(grep -E '^FIRST_SUPERUSER_PASSWORD=' $(COMPOSE_ROOT)/.env 2>/dev/null | head -1 | cut -d= -f2-); \
	cd backend && \
	  FIRST_SUPERUSER="$${FIRST_SUPERUSER:-$$user}" \
	  FIRST_SUPERUSER_PASSWORD="$${FIRST_SUPERUSER_PASSWORD:-$$pass}" \
	  uv run python ../scripts/seed_demo.py

# ── Testing ───────────────────────────────────────────────────────

test: test-backend test-frontend  ## Run all tests (backend + frontend)

# The integrated stack publishes no host port for Postgres (docker/compose.local.yml
# drops them all), so the suite reaches the container on the compose network
# instead. Empty when the stack is down — then .env's localhost:5432 stands.
DB_HOST = $(shell docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' fluksio-db 2>/dev/null | awk '{print $$1}')

test-backend:  ## Run backend tests (pytest + coverage)
	cd backend && $(if $(DB_HOST),POSTGRES_SERVER=$(DB_HOST)) uv run bash scripts/tests-start.sh

# The hostname the stack is served under. A checkout configured for a
# deployment carries the deployment's domain, and *.fluksio.com resolves to the
# live instance from here — which is why the run below maps both names onto the
# local Traefik by address and never lets DNS decide.
DOMAIN = $(shell sed -n 's/^DOMAIN=//p' $(COMPOSE_ROOT)/.env | head -1)
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)

test-frontend:  ## Run frontend tests (Playwright e2e) against the local stack
	@ip=$$(docker network inspect proxy \
	  --format '{{range .Containers}}{{if eq .Name "fluksio-app-proxy-1"}}{{.IPv4Address}}{{end}}{{end}}' \
	  2>/dev/null | cut -d/ -f1); \
	[ -n "$$ip" ] || { echo "  ✗ proxy network or Traefik container not found — is the stack up?"; exit 1; }; \
	docker run --rm --network proxy --ipc=host \
	  --user $$(id -u):$$(id -g) -e HOME=/tmp \
	  --add-host app.$(DOMAIN):$$ip --add-host api.$(DOMAIN):$$ip \
	  -v $(COMPOSE_ROOT):/app -w /app/frontend \
	  -e PLAYWRIGHT_BASE_URL=http://app.$(DOMAIN) \
	  -e PLAYWRIGHT_API_URL=http://api.$(DOMAIN) \
	  -e HOST_RESOLVER_RULES="MAP app.$(DOMAIN) $$ip, MAP api.$(DOMAIN) $$ip" \
	  -e CI=$${CI:-1} \
	  -e FIRST_SUPERUSER="$$(sed -n 's/^FIRST_SUPERUSER=//p' $(COMPOSE_ROOT)/.env | head -1)" \
	  -e FIRST_SUPERUSER_PASSWORD="$$(sed -n 's/^FIRST_SUPERUSER_PASSWORD=//p' $(COMPOSE_ROOT)/.env | head -1)" \
	  mcr.microsoft.com/playwright:v$(PW_VERSION)-noble \
	  npx playwright test $(PLAYWRIGHT_ARGS)

# Load and chaos against a *running* stack, never part of `make test`: it
# restarts this stack's containers. `SOAK_ARGS="--dry-run"` only looks.
soak:  ## Run the soak/chaos harness (SOAK_ARGS="--minutes 30 --scenario redis")
	cd backend && uv run python scripts/soak.py $(SOAK_ARGS)

bench-startup:  ## Time submitting a run (BENCH_ARGS="--kedro ../some/kedro/project")
	cd backend && uv run python scripts/bench_startup.py $(BENCH_ARGS)

# ── Linting ───────────────────────────────────────────────────────

lint: lint-backend lint-frontend  ## Run all linters

lint-backend:  ## Lint backend with ruff + mypy
	cd backend && uv run ruff check .
	cd backend && uv run ruff format --check .
	cd backend && uv run mypy app

lint-frontend:  ## Lint frontend with biome
	cd frontend && bun run lint

# ── Cleanup ───────────────────────────────────────────────────────

clean:  ## Remove build artifacts and caches
	rm -rf frontend/dist frontend/blob-report frontend/test-results
	rm -rf backend/.pytest_cache backend/htmlcov
	find backend -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
