LOCUST_PORT ?= 10502
LOCUST_WEB_PORT ?= 10503
LOCUST_DB_DIR ?= /tmp/devplace_locust
LOCUST_DB ?= $(LOCUST_DB_DIR)/datastore.db
LOCUST_USERS ?= 20
LOCUST_SPAWN_RATE ?= 5
LOCUST_RUN_TIME ?= 120s
DEVPLACE_RATE_LIMIT ?= 1000000

.PHONY: install dev clean test test-headed coverage coverage-headed coverage-html locust locust-headless

install:
	pip install -e .

dev:
	uvicorn devplacepy.main:app --reload --reload-dir devplacepy --host 0.0.0.0 --port 10500 --backlog 4096

prod:
	DEVPLACE_WEB_WORKERS=2 uvicorn devplacepy.main:app --host 0.0.0.0 --port 10500 --workers 2 --backlog 8192 --proxy-headers --forwarded-allow-ips '*'

test:
	PLAYWRIGHT_HEADLESS=1 python -m pytest tests/ -x

test-headed:
	PLAYWRIGHT_HEADLESS=0 python -m pytest tests/ -x

coverage:
	rm -f .coverage .coverage.*
	COVERAGE_PROCESS_START=$(CURDIR)/.coveragerc PLAYWRIGHT_HEADLESS=1 \
		python -m coverage run -m pytest tests/
	python -m coverage combine
	python -m coverage report

coverage-headed:
	rm -f .coverage .coverage.*
	COVERAGE_PROCESS_START=$(CURDIR)/.coveragerc PLAYWRIGHT_HEADLESS=0 \
		python -m coverage run -m pytest tests/
	python -m coverage combine
	python -m coverage report

coverage-html: coverage
	python -m coverage html
	@echo "Report written to htmlcov/index.html"

locust:
	export DEVPLACE_DATABASE_URL="sqlite:///$(LOCUST_DB)"; \
	export DEVPLACE_RATE_LIMIT=$(DEVPLACE_RATE_LIMIT); \
	fuser -k $(LOCUST_PORT)/tcp 2>/dev/null || true; \
	sleep 1; \
	mkdir -p $(LOCUST_DB_DIR); \
	rm -f $(LOCUST_DB); \
	uvicorn devplacepy.main:app --host 127.0.0.1 --port $(LOCUST_PORT) --backlog 8192 > /tmp/devplace_locust_server.log 2>&1 & \
	PID=$$!; \
	while kill -0 $$PID 2>/dev/null && ! curl -s http://127.0.0.1:$(LOCUST_PORT)/ > /dev/null 2>&1; do sleep 0.5; done; \
	if ! kill -0 $$PID 2>/dev/null; then echo "Server failed to start (port $(LOCUST_PORT) busy?). See /tmp/devplace_locust_server.log"; exit 1; fi; \
	locust --host http://127.0.0.1:$(LOCUST_PORT) --web-port $(LOCUST_WEB_PORT); \
	kill $$PID 2>/dev/null || true; \
	rm -rf $(LOCUST_DB_DIR)

locust-headless:
	export DEVPLACE_DATABASE_URL="sqlite:///$(LOCUST_DB)"; \
	export DEVPLACE_RATE_LIMIT=$(DEVPLACE_RATE_LIMIT); \
	fuser -k $(LOCUST_PORT)/tcp 2>/dev/null || true; \
	sleep 1; \
	mkdir -p $(LOCUST_DB_DIR); \
	rm -f $(LOCUST_DB); \
	uvicorn devplacepy.main:app --host 127.0.0.1 --port $(LOCUST_PORT) --backlog 8192 > /tmp/devplace_locust_server.log 2>&1 & \
	PID=$$!; \
	while kill -0 $$PID 2>/dev/null && ! curl -s http://127.0.0.1:$(LOCUST_PORT)/ > /dev/null 2>&1; do sleep 0.5; done; \
	if ! kill -0 $$PID 2>/dev/null; then echo "Server failed to start (port $(LOCUST_PORT) busy?). See /tmp/devplace_locust_server.log"; exit 1; fi; \
	locust --host http://127.0.0.1:$(LOCUST_PORT) --headless -u $(LOCUST_USERS) -r $(LOCUST_SPAWN_RATE) --run-time $(LOCUST_RUN_TIME) --html=$(LOCUST_DB_DIR)/report.html; \
	kill $$PID 2>/dev/null || true; \
	rm -rf $(LOCUST_DB_DIR)

clean:
	find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
	find . -type f -name '*.pyc' -delete
	rm -rf devplacepy.egg-info
	rm -rf .venv

# Container Manager works out of the box: the overlay installs the docker CLI in
# the image and mounts the host socket. DOCKER_GID is read straight from the
# socket so the UID-1000 app can use it; the data dir is the project's own var/
# at its real host path, so the DooD bind-mount (host == container path) holds
# with no /srv dir and no sudo.
COMPOSE := docker compose -f docker-compose.yml -f docker-compose.containers.yml
DEVPLACE_DATA_DIR ?= $(CURDIR)/var
DOCKER_GID ?= $(shell stat -c '%g' /var/run/docker.sock 2>/dev/null)
export DEVPLACE_DATA_DIR
export DOCKER_GID

.PHONY: docker-build docker-up docker-down docker-logs docker-clean docker-prep ppy

# Build the single shared container image every instance runs. Build once;
# rebuild only when ppy.Dockerfile, the sudo shim, or pagent change.
ppy:
	docker build --network=host -f ppy.Dockerfile -t ppy:latest devplacepy/services/containers/files

docker-prep:
	mkdir -p $(DEVPLACE_DATA_DIR)

docker-build: docker-prep
	$(COMPOSE) build

docker-up: docker-prep
	$(COMPOSE) up -d

docker-down:
	$(COMPOSE) down

docker-logs:
	$(COMPOSE) logs -f

docker-clean:
	$(COMPOSE) down -v

docker-bup: docker-build docker-up

deploy:
	git checkout production
	git merge master
	git push origin production

# ---------------------------------------------------------------------------
# Code validation. Dependency-free, no external tools: Python ast+py_compile,
# JavaScript node --check, CSS brace balance, HTML/Jinja template parse.
# ---------------------------------------------------------------------------
.PHONY: validate

validate:
	python -m agents.validator .

# ---------------------------------------------------------------------------
# Maintenance agent fleet (see agents.md). Default mode is fix; pass CHECK=1
# for read-only reporting with a non-zero exit on findings.
#   make audit-agent          # autonomous fix
#   make audit-agent CHECK=1   # report only
#   make agents-all            # run the whole fleet
#   make maestro               # talk to the conductor; it runs the rest
#   make maintenance          # whole fleet, concurrent read-only, only git-changed files
#   make maintenance-fix      # whole fleet, fix mode, only git-changed files
# ---------------------------------------------------------------------------
CHECK ?=
AGENT_MODE := $(if $(CHECK),--check,--fix)

.PHONY: security-agent audit-agent devii-agent docs-agent fanout-agent \
        dry-agent style-agent frontend-agent seo-agent test-agent \
        agents-all maestro maintenance maintenance-fix

security-agent:
	python -m agents.security $(AGENT_MODE)

audit-agent:
	python -m agents.audit $(AGENT_MODE)

devii-agent:
	python -m agents.devii $(AGENT_MODE)

docs-agent:
	python -m agents.docs $(AGENT_MODE)

fanout-agent:
	python -m agents.fanout $(AGENT_MODE)

dry-agent:
	python -m agents.dry $(AGENT_MODE)

style-agent:
	python -m agents.style $(AGENT_MODE)

frontend-agent:
	python -m agents.frontend $(AGENT_MODE)

seo-agent:
	python -m agents.seo $(AGENT_MODE)

test-agent:
	python -m agents.test_coverage $(AGENT_MODE)

agents-all:
	python -m agents.orchestrator $(AGENT_MODE)

maestro:
	python -m agents.maestro

maintenance:
	python -m agents.orchestrator --check --changed

maintenance-fix:
	python -m agents.orchestrator --fix --changed
