VENV := .venv
PY   := $(VENV)/bin/python
PIP  := $(VENV)/bin/pip

# Resolve black config from the repo root (one directory up).
# This avoids duplicating line-length / target-version across the sub-packages.
ROOT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)

# setuptools_scm version, resolved on the host from the installed _version.py.
# Injected via SETUPTOOLS_SCM_PRETEND_VERSION_FOR_* so the sdist build skips the
# VCS file-finder (which root=".." would otherwise use to pull in the whole
# monorepo), and so in-container builds (no git / no repo-root mount) get it too.
DIST_ENV := SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LR_LUMENTEST_SDK
SCM_VERSION = $(shell $(PY) -c "import lumentest_station._version as v; print(v.version)" 2>/dev/null)

# Only the sdist and wheel are valid PyPI distributions.
DIST_FILES := dist/*.whl dist/*.tar.gz

PYPI_REPOSITORY ?= https://upload.pypi.org/legacy/
TEST_PYPI_REPOSITORY ?= https://test.pypi.org/legacy/

# The PyPI token lives in the releasing developer's shell, exported by hand
# before running `publish`/`publish-test` — the same arrangement lr-gladiator
# and lr-qrm use. No CI job holds it, so a release reaches PyPI only when a
# person runs the target.
#
# setuptools_scm derives SCM_VERSION from the `lumentest-*` tag: any commit
# past the tag carries a `.devN+g<sha>` suffix. PyPI rejects a local (`+…`)
# segment outright, and a `.dev` upload permanently spends a version number
# the index can never reuse — so both upload targets refuse anything but a
# clean tag.
CHECK_RELEASE_VERSION = case "$(SCM_VERSION)" in *dev*|*+*|"") echo "error: refusing to upload $(SCM_VERSION) — build from a clean 'lumentest-X.Y.Z' tag"; exit 2 ;; esac

# The staging tree this package ships is generated, not authored: each module
# has exactly one canonical home, under service/src/lumentest_station/. This
# list is an allowlist — sdk/tests/ asserts that its closure (every
# lumentest_station.X import reachable from these files) never reaches outside
# it, so a station-side change that adds a new intra-package import fails the
# SDK's own test suite instead of silently shipping a broken wheel.
STATION_SRC := ../service/src/lumentest_station
SDK_MODULES := __init__.py decorators.py limits.py manifest.py run_tool.py \
               sequence.py step_executor.py step_main.py test_context.py

SRC_DIR := src/lumentest_station

.DEFAULT_GOAL := install

.PHONY: install dev sync test test-only format dist check publish publish-test shell clean

$(VENV)/bin/activate:
	python3.12 -m venv $(VENV)
	$(PIP) install --upgrade pip

# Regenerates the staging tree from scratch every time: a module dropped from
# SDK_MODULES (or renamed upstream) must disappear from the wheel too, not
# linger from a previous sync. _version.py is never copied here — setuptools_scm
# writes it into $(SRC_DIR) itself at build/install time.
sync:
	rm -rf $(SRC_DIR)
	mkdir -p $(SRC_DIR)
	@for f in $(SDK_MODULES); do \
		test -f $(STATION_SRC)/$$f || { echo "error: $(STATION_SRC)/$$f not found — sdk/Makefile's SDK_MODULES names a module the station does not have"; exit 2; }; \
		cp $(STATION_SRC)/$$f $(SRC_DIR)/$$f; \
	done

install: $(VENV)/bin/activate sync
	$(PIP) install .

dev: $(VENV)/bin/activate sync
	$(PIP) install -e ".[dev]"

# `test` is the whole job: sync the venv, then run pytest. `test-only` is the
# pytest half on its own, for a driver that has already done the `dev` pass and
# wants to run several sub-trees at once -- the editable installs `dev` performs
# write in-tree build artefacts, so they must not overlap; pytest runs may.
test: dev test-only

test-only:
	$(VENV)/bin/pytest \
		--junit-xml=test-results.xml \
		tests/

# src/ is generated from already-formatted upstream sources; formatting it here
# would edit a tree `sync` throws away on the next run.
format: dev
	$(VENV)/bin/black --config $(ROOT_DIR)/pyproject.toml tests/

# Clearing the previous sdist and wheel is what keeps `$(DIST_FILES)` meaning
# "this build": every artefact accumulates under its own version, and an upload
# globbing the directory would carry a stale version along with the current one.
dist: dev
	rm -f dist/*.whl dist/*.tar.gz
	$(DIST_ENV)="$(SCM_VERSION)" $(PY) -m build

check: dist
	$(VENV)/bin/twine check $(DIST_FILES)

# Both upload targets pass credentials through TWINE_USERNAME/TWINE_PASSWORD,
# which twine reads natively, and silence the upload line with `@`. `-u`/`-p`
# would put the token in the echoed recipe line and in the upload process's
# argv, where `ps` can read it — visible in a developer's scrollback and, since
# this target also runs as a CI job, in a captured job log.
publish-test: check
	@$(CHECK_RELEASE_VERSION)
	@if [ -z "$$TEST_PYPI_TOKEN" ]; then echo "error: TEST_PYPI_TOKEN is required — create one at https://test.pypi.org/manage/account/token/"; exit 2; fi
	@TWINE_USERNAME=__token__ TWINE_PASSWORD="$$TEST_PYPI_TOKEN" $(VENV)/bin/twine upload --repository-url $(TEST_PYPI_REPOSITORY) $(DIST_FILES)

publish: check
	@$(CHECK_RELEASE_VERSION)
	@if [ -z "$$PYPI_TOKEN" ]; then echo "error: PYPI_TOKEN is required — create one at https://pypi.org/manage/account/token/"; exit 2; fi
	@TWINE_USERNAME=__token__ TWINE_PASSWORD="$$PYPI_TOKEN" $(VENV)/bin/twine upload --repository-url $(PYPI_REPOSITORY) $(DIST_FILES)

shell:
	@echo "source $(VENV)/bin/activate"

clean:
	rm -rf $(VENV) dist/ build/ htmlcov/ test-results.xml .coverage $(SRC_DIR)
	find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null; true
	find . -name "*.pyc" -delete 2>/dev/null; true
	find . -name "*.egg-info" -type d -exec rm -rf {} + 2>/dev/null; true
