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

APP_VERSION := $(shell head -1 VERSION)
GIT_COMMIT := $(shell git describe --dirty --always)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
BUILD_USER := $(shell whoami)
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
PYTHON ?= python3
OPENAPI_ADDR ?= 127.0.0.1:8080
TEST ?= .
TEST_DIR ?= ./...
# Aggregate package budget for race-enabled real-login tests.
TEST_TIMEOUT ?= 30m
# Leave CPU and memory available for the editor and operating system.
TEST_PACKAGE_PARALLELISM ?= 1
TEST_PARALLELISM ?= 2
TEST_GOMAXPROCS ?= 2
TEST_GO_MEMORY_MB ?= 512
TEST_WALL_TIMEOUT ?= 3200
TEST_MAX_PROCESSES ?= 128
TEST_ARTIFACT_MB ?= 256
QUICK_TEST_DIR ?= ./pkg/system
COVERAGE_DIR ?= .coverage
MINIMUM_COVERAGE ?= 1
export TEST TEST_DIR TEST_TIMEOUT QUICK_TEST_DIR COVERAGE_DIR MINIMUM_COVERAGE
CHANGE_BASE ?=
CHANGE_HEAD ?=
CHANGE_DRY_RUN ?= 0
export CHANGE_BASE CHANGE_HEAD CHANGE_DRY_RUN
CI_SHARD ?=
export CI_SHARD
export TEST_PACKAGE_PARALLELISM TEST_PARALLELISM TEST_GOMAXPROCS TEST_GO_MEMORY_MB
export TEST_WALL_TIMEOUT TEST_MAX_PROCESSES TEST_ARTIFACT_MB
# Unset means auto-detect a budget; do not export an undefined Make variable.
ifneq ($(origin TEST_MEMORY_MB),undefined)
export TEST_MEMORY_MB
endif
export APP_VERSION GIT_COMMIT GIT_BRANCH BUILD_USER BUILD_DATE
export PYTHONDONTWRITEBYTECODE := 1

.PHONY: all info build linter dep install-test-tools test change-test run-tests qtest run-quick-tests run-reports test-ui test-automation test-codeql ci-check ci-quality ci-test-shard brand-assets brand-assets-check generate-acl version-check version-sync artifact-id templates license docs clean upgrade mod-tidy release minor-release fast-release fast-minor-release release-git-check release-update-version release-git-commit

all: info build

info:
	@echo "Version: $$APP_VERSION, Branch: $$GIT_BRANCH, Revision: $$GIT_COMMIT"
	@echo "Built on $$BUILD_DATE by $$BUILD_USER"

# Validation and builds never rewrite source, licenses, or module manifests.
build: version-check
	@mkdir -p bin
	@set -e; for command in authdb authdbctl; do \
		CGO_ENABLED=0 go build -mod=readonly -trimpath -o "bin/$$command" \
			-ldflags="-w -s \
			-X main.appVersion=$$APP_VERSION \
			-X main.gitBranch=$$GIT_BRANCH \
			-X main.gitCommit=$$GIT_COMMIT \
			-X main.buildUser=$$BUILD_USER \
			-X main.buildDate=$$BUILD_DATE" "./cmd/$$command"; \
		"./bin/$$command" --version; \
		"./bin/$$command" --help; \
	done

linter:
	@go tool golint -set_exit_status . ./cmd/... ./internal/... ./pkg/... ./plugins/...

# Tools are pinned by go.mod/go.sum; no global installation is required.
dep:
	@go mod download
	@go mod verify
	@$(MAKE) install-test-tools
	@go tool versioned -version
	@go tool golint -h >/dev/null 2>&1

install-test-tools:
	@go tool tested version

test: run-tests

change-test:
	@$(PYTHON) assets/scripts/change_tests.py

run-tests:
	@$(PYTHON) assets/scripts/test_guard.py run go tool tested run --output-dir "$$COVERAGE_DIR" \
		--title "AuthCrunch Go tests" --minimum-coverage "$$MINIMUM_COVERAGE" \
		-- -mod=readonly -race -count=1 -p "$$TEST_PACKAGE_PARALLELISM" \
		-parallel "$$TEST_PARALLELISM" -timeout "$$TEST_TIMEOUT" -v -run "$$TEST" $$TEST_DIR

qtest: run-quick-tests

run-quick-tests:
	@$(MAKE) run-tests TEST_DIR="$$QUICK_TEST_DIR" COVERAGE_DIR="$$COVERAGE_DIR/quick"

run-reports:
	@$(PYTHON) assets/scripts/test_guard.py report go tool tested report --output-dir "$$COVERAGE_DIR" --title "AuthCrunch Go tests"

test-ui:
	@node --test --test-reporter=spec pkg/authn/ui/testdata/*_client_test.cjs

.PHONY: ui-apps
UI_APPS_SOURCE ?= ../../authcrunch/authcrunch-ui/dist/embedded
ui-apps:
	@$(PYTHON) assets/scripts/update_ui_apps.py --source "$(UI_APPS_SOURCE)"

test-automation:
	@$(PYTHON) -m unittest discover -s assets/scripts/tests -p '*_test.py' -v

.PHONY: openapi openapi-artifact serve-openapi openapi-check openapi-test openapi-browser-test
openapi:
	@go run -mod=readonly ./cmd/openapi generate

openapi-artifact:
	@go run -mod=readonly ./cmd/openapi artifact

serve-openapi:
	@go run -mod=readonly ./cmd/openapi -listen "$(OPENAPI_ADDR)" serve

openapi-check:
	@go run -mod=readonly ./cmd/openapi check

openapi-test:
	@$(MAKE) test TEST_DIR='./internal/openapi ./cmd/openapi' TEST='.' COVERAGE_DIR="$(COVERAGE_DIR)/openapi-tools"
	@$(MAKE) test TEST_DIR='. ./pkg/authn' TEST='^TestE2EOpenAPIContract' COVERAGE_DIR="$(COVERAGE_DIR)/openapi-contracts"
	@node --test assets/scripts/tests/openapi_scalar_test.mjs

openapi-browser-test:
	@COVERAGE_DIR="$(COVERAGE_DIR)/openapi-browser" $(PYTHON) assets/scripts/test_guard.py run node --test assets/scripts/tests/openapi_browser_test.mjs

test-codeql:
	@$(PYTHON) .github/codeql/test_scan.py

brand-assets:
	@$(PYTHON) assets/scripts/update_brand_assets.py

brand-assets-check:
	@$(PYTHON) assets/scripts/update_brand_assets.py --check

# Actions runs these gates on a separate runner from each Go test shard.
ci-quality:
	@$(MAKE) version-check
	@$(MAKE) openapi
	@$(MAKE) openapi-check
	@node --test assets/scripts/tests/openapi_scalar_test.mjs
	@$(MAKE) brand-assets-check
	@$(MAKE) test-automation
	@$(MAKE) linter
	@$(MAKE) test-ui
	@$(MAKE) build

ci-test-shard:
	@$(PYTHON) assets/scripts/ci_tests.py run "$$CI_SHARD"

# Local/release validation remains complete and serial, even with make -j.
ci-check:
	@$(MAKE) ci-quality
	@$(MAKE) test TEST=. TEST_DIR=./... COVERAGE_DIR=.coverage MINIMUM_COVERAGE=1

version-check:
	@$(PYTHON) assets/scripts/version.py check

version-sync:
	@$(PYTHON) assets/scripts/version.py sync

artifact-id:
	@$(PYTHON) assets/scripts/version.py artifact

# Explicit maintenance actions are separate from test/build entry points.
generate-acl:
	@$(PYTHON) assets/scripts/generate_acl.py

templates: license

license:
	@find . -type f -name '*.go' -not -path './vendor/*' -not -path './tmp/*' -not -path './.tmp/*' \
		-exec go tool versioned -addlicense -copyright="Paul Greenberg greenpau@outlook.com" -year=2022 -filepath={} \;
	@go tool versioned -toc -filepath cmd/authdbctl/README.md

docs:
	@mkdir -p .doc
	@go doc -all > .doc/index.txt
	@cat .doc/index.txt

clean:
	@rm -rf .doc .coverage bin

upgrade:
	@go get -u ./...
	@go mod tidy

mod-tidy:
	@go mod tidy
	@go mod verify

# Only run these publishing commands when a release is explicitly requested.
release:
	@bash assets/scripts/release.sh patch

minor-release:
	@bash assets/scripts/release.sh minor

# Skip the local quality gate; GitHub release validation still runs.
fast-release:
	@bash assets/scripts/release.sh patch --skip-tests

fast-minor-release:
	@bash assets/scripts/release.sh minor --skip-tests

release-git-check:
	@bash assets/scripts/release.sh check

# Old partial entry points could bypass release checks and publish unrelated tags.
release-update-version release-git-commit:
	@echo "Use make release (patch) or make minor-release for the complete checked workflow." >&2
	@exit 1
