# fancy-dns
#
# `make` alone gives you a help screen. The two targets you want most are
# `make build` (binary with the console embedded) and `make check` (what CI
# runs).

SHELL      := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help

BINARY     := fancy-dns
PKG        := git.k8s-git-k6n.net/mats/fancy-dns
CMD        := ./cmd/fancy-dns
VERSION    ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LDFLAGS    := -s -w -X $(PKG)/internal/app.Version=$(VERSION)
DIST       := dist
IMAGE      ?= fancydns/fancy-dns:$(VERSION)

GO         ?= go
NPM        ?= npm
GOFILES    := $(shell find . -name '*.go' -not -path './web/*')

# Test knobs: the integration suite binds loopback ports, so it cannot run with
# -p 1 alongside other packages. Race is the default because the whole design
# leans on atomics and a regression there is silent.
TESTFLAGS  ?= -race -count=1

.PHONY: help
help: ## Show this help
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage: make <target>\n"} \
	  /^[a-zA-Z_-]+:.*?##/ { printf "  \033[36m%-16s\033[0m %s\n", $$1, $$2 } \
	  /^## / { printf "\n%s\n", substr($$0, 4) }' $(MAKEFILE_LIST)

## Build

.PHONY: build
build: web ## Build the binary with the console embedded
	@mkdir -p $(DIST)
	CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' -o $(DIST)/$(BINARY) $(CMD)
	@echo "built $(DIST)/$(BINARY) ($(VERSION))"

.PHONY: go-build
go-build: ## Build without the console (fast; used by the Go test loop)
	CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' -o $(DIST)/$(BINARY)-noassets $(CMD)

.PHONY: install
install: ## Install into GOBIN
	CGO_ENABLED=0 $(GO) install -trimpath -ldflags '$(LDFLAGS)' $(CMD)

.PHONY: web
web: ## Build the web console into internal/web/dist
	@if [ ! -d web/node_modules ]; then \
	  echo "==> installing web dependencies"; \
	  cd web && $(NPM) ci --no-audit --no-fund; \
	fi
	cd web && $(NPM) run build
	@test -f internal/web/dist/index.html || { echo "console build produced no index.html"; exit 1; }

.PHONY: web-dev
web-dev: ## Run the Vite dev server (proxies /api to 127.0.0.1:8080)
	cd web && $(NPM) run dev

## Verify

.PHONY: check
check: check-go test ## What CI runs (format, vet, compile, then the suite)

.PHONY: check-go
check-go: fmt-check vet go-build ## The tight inner loop: format, vet, compile. No npm, no tests
	@echo "check-go: ok"

.PHONY: gofmt-w
gofmt-w: ## Format every Go file in the tree, including ones just created
	@gofmt -w $(GOFILES)
	@echo "gofmt: ok"
.PHONY: test
test: ## Run the test suite with the race detector
	$(GO) test $(TESTFLAGS) ./...

.PHONY: test-short
test-short: ## Run the tests without -race (much faster)
	$(GO) test -short -count=1 ./...

.PHONY: cover
cover: ## Test with a coverage report at coverage.html
	$(GO) test -race -count=1 -coverprofile=coverage.out ./...
	$(GO) tool cover -html=coverage.out -o coverage.html
	@$(GO) tool cover -func=coverage.out | tail -1

.PHONY: bench
bench: ## Run the cache and blocklist benchmarks
	$(GO) test -run '^$$' -bench . -benchmem ./internal/cache/... ./internal/blocklist/...

.PHONY: vet
vet: ## go vet
	$(GO) vet ./...

.PHONY: fmt
fmt: ## Format the Go code
	gofmt -w $(GOFILES)

.PHONY: fmt-check
fmt-check: ## Fail if anything is unformatted
	@unformatted=$$(gofmt -l $(GOFILES)); \
	if [ -n "$$unformatted" ]; then \
	  echo "gofmt needed:"; echo "$$unformatted"; exit 1; \
	fi

.PHONY: lint
lint: ## go vet, with the name it has always had
	$(GO) vet ./...
	@echo "lint: ok"

.PHONY: web-check
web-check: ## Typecheck and build the console
	cd web && $(NPM) ci --no-audit --no-fund && npx tsc -b --noEmit && $(NPM) run build

## Run

.PHONY: run
run: go-build ## Run with deploy/config.example.yaml (binds :53 — rewrite the ports first)
	$(DIST)/$(BINARY)-noassets -config deploy/config.example.yaml -log-level debug

.PHONY: check-config
check-config: go-build ## Validate a config file without starting
	$(DIST)/$(BINARY)-noassets -config $(or $(CONFIG),deploy/config.example.yaml) -check

## End-to-end

.PHONY: smoke
smoke: build ## Boot the real binary on unprivileged ports; check the API, metrics and the embedded console
	@set -euo pipefail; \
	dir=$$(mktemp -d); \
	pid=""; \
	trap '[ -n "$$pid" ] && kill "$$pid" 2>/dev/null || true; rm -rf "$$dir"' EXIT; \
	sed \
	  -e 's#127.0.0.1:8080#127.0.0.1:18080#' \
	  -e 's#0.0.0.0:53#127.0.0.1:15353#g' \
	  -e 's#\[::\]:53#[::1]:15353#g' \
	  -e 's#trust_loopback: true#trust_loopback: false#' \
	  deploy/config.example.yaml > "$$dir/config.yaml"; \
	$(DIST)/$(BINARY) -config "$$dir/config.yaml" -check; \
	$(DIST)/$(BINARY) -config "$$dir/config.yaml" > "$$dir/log" 2>&1 & \
	pid=$$!; \
	for i in $$(seq 1 40); do \
	  curl -fsS http://127.0.0.1:18080/api/healthz >/dev/null 2>&1 && break; \
	  sleep 0.5; \
	done; \
	echo "==> health";  curl -fsS http://127.0.0.1:18080/api/healthz; echo; \
	echo "==> console"; curl -fsS http://127.0.0.1:18080/ | grep -qi '<!doctype html' && echo "embedded"; \
	echo "==> auth"; \
	  test "$$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:18080/api/whoami)" = 401 && echo "unauthenticated -> 401"; \
	  curl -fsS -H "X-API-Key: CHANGE-ME-long-random-string" http://127.0.0.1:18080/api/whoami | grep -q '"can_write":true' && echo "authenticated -> can_write"; \
	echo "==> metrics"; curl -fsS -H "X-API-Key: CHANGE-ME-long-random-string" http://127.0.0.1:18080/api/metrics | grep -c '^fancydns_'

## Container

.PHONY: image
image: ## Build the container image
	docker build --build-arg VERSION=$(VERSION) -t $(IMAGE) .

.PHONY: push
push: ## Push the container image
	docker push $(IMAGE)

## Housekeeping

.PHONY: clean
clean: ## Remove build output
	rm -rf $(DIST) coverage.out coverage.html

.PHONY: clean-web
clean-web: ## Remove node_modules and the console bundle
	rm -rf web/node_modules web/.vite internal/web/dist/assets internal/web/dist/index.html
