# SPDX-FileCopyrightText: 2025 Forkbomb BV
#
# SPDX-License-Identifier: AGPL-3.0-or-later

PROJECT_NAME 	?= credimi
ORGANIZATION 	?= forkbombeu
ROOT_DIR		?= $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
COMPOSE_PROJECT_NAME ?= $(shell basename "$(ROOT_DIR)" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$$//')
COMPOSE_DEV_OVERRIDE_FILE ?= /tmp/$(COMPOSE_PROJECT_NAME)-docker-compose.dev.yaml
BINARY_NAME 	?= $(PROJECT_NAME)
CLI_NAME		?= $(PROJECT_NAME)-cli
SUBDIRS			?= ./...
MAIN_SRC 		?= $(ROOT_DIR)/main.go
CLI_SRC			?= $(ROOT_DIR)/cli/main.go
DATA			?= $(ROOT_DIR)/pb_data
WEBAPP			?= $(ROOT_DIR)/webapp
GO_SRC 			:= $(wildcard **/*.go)
GODIRS			:= ./pkg/... ./cmd/...
UI_SRC			:= $(shell find $(WEBAPP)/src -type f \( -name '*.svelte' -o -name '*.js' -o -name '*.ts' -o -name '*.css' \) ! -name '*.generated.ts' ! -path 'webapp/src/modules/i18n/paraglide/*')
DOCS			?= $(ROOT_DIR)/docs

GOCMD 			?= go
GOBUILD			?= $(GOCMD) build
GOCLEAN			?= $(GOCMD) clean
GOTEST			?= $(GOCMD) test
GOTOOL			?= $(GOCMD) tool
GOGET			?= $(GOCMD) get
GOFMT			?= $(GOCMD) fmt
GOMOD			?= $(GOCMD) mod
GOINST			?= $(GOCMD) install
GOGEN			?= $(GOCMD) generate
GOPATH 			?= $(shell $(GOCMD) env GOPATH)
GOBIN 			?= $(GOPATH)/bin
GOMOD_FILES 	:= go.mod go.sum
COVOUT			:= coverage.out
COVERAGE_FILE	?= $(COVOUT)
COVERAGE_MIN	?= 80
COVERAGE_PKGS ?= $(shell go list ./... | grep -v '/webapp/node_modules/')

# Submodules
WEBENV			= $(WEBAPP)/.env
BIN				= $(ROOT_DIR)/.bin
DEPS 			= mise git temporal wget
DEV_DEPS		= pre-commit
TEST_DEPS		= mise

# Generic tool checker
define require_tools
	@missing=0; \
	for t in $(1); do \
		if ! command -v $$t >/dev/null 2>&1; then \
			echo "🧧 \`$$t\` not found in PATH, please install it." >&2; \
			missing=1; \
		fi; \
	done; \
	if [ $$missing -ne 0 ]; then \
		echo "🥶 Missing required tools, aborting."; \
		exit 1; \
	fi
endef

define write_compose_dev_override
	@printf '%s\n' \
	'services:' \
	'  elasticsearch:' \
	'    container_name: $(COMPOSE_PROJECT_NAME)-temporal-elasticsearch' \
	'  postgresql:' \
	'    container_name: $(COMPOSE_PROJECT_NAME)-temporal-postgresql' \
	'  temporal_ui:' \
	'    container_name: $(COMPOSE_PROJECT_NAME)-temporal-ui' \
	> $(COMPOSE_DEV_OVERRIDE_FILE)
endef

all: help
.PHONY: submodules version dev test test.all lint tidy purge build docker docker-tunnel doc clean tools help w devtools coverage-check

$(BIN):
	@mkdir -p $@

submodules:
	git submodule update --recursive --init

## Hacking
GREEN  := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE  := $(shell tput -Txterm setaf 7)
CYAN   := $(shell tput -Txterm setaf 6)
RESET  := $(shell tput -Txterm sgr0)

VERSION_STRATEGY 	= semver # git, semver, date
VERSION 			:= $(shell cat VERSION 2>/dev/null || echo "0.1.0")
GIT_COMMIT 			?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_BRANCH 			?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
BUILD_TIME 			?= $(shell date -u '+%Y-%m-%d_%H:%M:%S')
BUILD_BY 			?= $(shell whoami)

version: ## ℹ️ Display version information
	@echo "$(CYAN)Version:	$(RESET)$(VERSION)"
	@echo "$(CYAN)Commit:		$(RESET)$(GIT_COMMIT)"
	@echo "$(CYAN)Branch:		$(RESET)$(GIT_BRANCH)"
	@echo "$(CYAN)Built:		$(RESET)$(BUILD_TIME)"
	@echo "$(CYAN)Built by: 	$(RESET)$(BUILD_BY)"
	@echo "$(CYAN)Go version:	$(RESET)$(shell $(GOCMD) version)"

$(WEBENV):
	cp $(WEBAPP)/.env.example $(WEBAPP)/.env

$(DATA):
	mkdir -p $(DATA)

dev: $(WEBENV) tools devtools submodules $(BIN) $(DATA) ## 🚀 run in watch mode
	$(call require_tools,$(DEPS) $(DEV_DEPS))
	$(call write_compose_dev_override)
	COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) COMPOSE_DEV_OVERRIDE_FILE=$(COMPOSE_DEV_OVERRIDE_FILE) bash -c 'export CREDIMI_ELASTIC_PASSWORD=$${CREDIMI_ELASTIC_PASSWORD:-devpassword} PUBLIC_TURNSTILE_SITE_KEY=$${PUBLIC_TURNSTILE_SITE_KEY:-2x00000000000000000000AA} TURNSTILE_SECRET_KEY=$${TURNSTILE_SECRET_KEY:-1x0000000000000000000000000000000AA}; trap "docker compose -f docker-compose.yaml $${COMPOSE_DEV_OVERRIDE_FILE:+-f $${COMPOSE_DEV_OVERRIDE_FILE}} stop elasticsearch postgresql temporal temporal_ui" EXIT; POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 TEMPORAL_ADMIN_TOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0 docker compose -f docker-compose.yaml $${COMPOSE_DEV_OVERRIDE_FILE:+-f $${COMPOSE_DEV_OVERRIDE_FILE}} up --build -d elasticsearch postgresql temporal temporal_ui temporal_setup; DEBUG=1 $(GOTOOL) hivemind -T -l API,UI Procfile.dev'

test: ## 🧪 run tests
	$(call require_tools,$(TEST_DEPS))
	bash ./scripts/test-summary.sh

test.all: ## 🧪 run all tests, including long tests skipped by test
	$(call require_tools,$(TEST_DEPS))
	TEST_SHORT=0 bash ./scripts/test-summary.sh
ifeq (test.p, $(firstword $(MAKECMDGOALS)))
  test_name := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
  $(eval $(test_name):;@true)
endif
test.p: tools ## 🍷 watch tests and run on change for a certain folder
	$(GOTOOL) gow test -run "^$(test_name)$$" $(GODIRS)

coverage: devtools # ☂️ run test and open code coverage report
	$(GOTEST) -tags=unit -covermode=atomic -coverprofile=$(COVOUT) ./...
	$(GOTOOL) cover -html=$(COVOUT) -o coverage.html
	$(GOTOOL) go-cover-treemap -coverprofile $(COVOUT) > coverage.svg && open coverage.svg

coverage-check: ## ☂️ run tests and fail if total coverage is below COVERAGE_MIN
	@$(GOTEST) -tags=unit -covermode=atomic -coverprofile=$(COVERAGE_FILE) $(COVERAGE_PKGS)
	@awk -v min="$(COVERAGE_MIN)" -v file="$(COVERAGE_FILE)" '\
		BEGIN { covered = 0; total = 0 } \
		NR == 1 && $$1 == "mode:" { next } \
		NF >= 3 { \
			n = $$2 + 0; \
			c = $$3 + 0; \
			total += n; \
			if (c > 0) covered += n; \
		} \
		END { \
			if (total == 0) { \
				printf("Failed to compute coverage from %s: no statement data found\n", file) > "/dev/stderr"; \
				exit 1; \
			} \
			cov = (covered * 100) / total; \
			printf("Total coverage: %.1f%% (required: %s%%)\n", cov, min); \
			if ((cov + 0) < (min + 0)) exit 1; \
		} \
	' $(COVERAGE_FILE)

lint: devtools ## 📑 lint rules checks
	$(call require_tools,$(TEST_DEPS))
	$(GOMOD) tidy -diff
	$(GOMOD) verify
	$(GOCMD) vet $(SUBDIRS)
	$(GOTOOL) govulncheck $(SUBDIRS)
	$(GOTOOL) golangci-lint run $(SUBDIRS)

fmt: devtools ## 🗿 format rules checks
	$(GOFMT) $(GODIRS)

tidy: $(GOMOD_FILES)
	@$(GOMOD) tidy

purge: ## ⛔ Purge the database
	@echo "⛔ Purge the database"
	$(call write_compose_dev_override)
	@COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) COMPOSE_DEV_OVERRIDE_FILE=$(COMPOSE_DEV_OVERRIDE_FILE) POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 docker compose -f docker-compose.yaml -f $(COMPOSE_DEV_OVERRIDE_FILE) down -v --remove-orphans
	@rm -rf $(DATA)
	@mkdir $(DATA)

## Deployment

$(BINARY_NAME): $(GO_SRC) tools tidy submodules $(WEBENV)
	@$(GOBUILD) -o $(BINARY_NAME) $(MAIN_SRC)

$(WEBAPP)/build: $(UI_SRC)
	@./$(BINARY_NAME) serve & \
	PID=$$!; \
	./scripts/wait-for-it.sh localhost:8090 --timeout=60; \
	cd $(WEBAPP) && bun i && bun run build; \
	kill $$PID;

$(BINARY_NAME)-ui: $(UI_SRC)
	@./$(BINARY_NAME) serve & \
	PID=$$!; \
	./scripts/wait-for-it.sh localhost:8090 --timeout=60; \
	cd $(WEBAPP) && bun i && bun run bin; \
	kill $$PID;

docker: $(DATA) submodules ## 🐳 run docker with all the infrastructure services
	EXTRA_BUILD_ARGS=""; [ -n "$$CREDIMI_EXTRA_PAT" ] && EXTRA_BUILD_ARGS="--build-arg CREDIMI_EXTRA_PAT=$$CREDIMI_EXTRA_PAT"; \
	COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 TEMPORAL_ADMIN_TOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0 docker compose build --build-arg PUBLIC_POCKETBASE_URL="http://localhost:8090" --build-arg PUBLIC_TURNSTILE_SITE_KEY="$${PUBLIC_TURNSTILE_SITE_KEY:-}" $$EXTRA_BUILD_ARGS
	COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 TEMPORAL_ADMIN_TOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0 docker compose up

docker-tunnel: $(DATA) submodules ## 🌐 run docker (detached, logs hidden) and expose http://localhost:8090 over a public cloudflared tunnel
	$(call require_tools,cloudflared)
	EXTRA_BUILD_ARGS=""; [ -n "$$CREDIMI_EXTRA_PAT" ] && EXTRA_BUILD_ARGS="--build-arg CREDIMI_EXTRA_PAT=$$CREDIMI_EXTRA_PAT"; \
	COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 TEMPORAL_ADMIN_TOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0 docker compose build --build-arg PUBLIC_POCKETBASE_URL="" --build-arg PUBLIC_TURNSTILE_SITE_KEY="$${PUBLIC_TURNSTILE_SITE_KEY:-}" $$EXTRA_BUILD_ARGS
	@COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME) POSTGRESQL_VERSION=16 ELASTICSEARCH_VERSION=7.17.27 TEMPORAL_VERSION=1.29.1 TEMPORAL_UI_VERSION=2.43.2 TEMPORAL_ADMIN_TOOLS_VERSION=1.29.1-tctl-1.18.4-cli-1.5.0 bash -euc '\
		printf "$(CYAN)🐳 Starting docker compose detached (runtime logs hidden — run \`docker compose logs -f\` to view)...$(RESET)\n"; \
		docker compose up -d --remove-orphans; \
		trap "printf \"\n$(YELLOW)🛑 Tunnel closed; stopping containers...$(RESET)\n\"; docker compose down" EXIT INT TERM; \
		printf "$(CYAN)⏳ Waiting for http://localhost:8090 ...$(RESET)\n"; \
		./scripts/wait-for-it.sh localhost:8090 --timeout=180 --quiet || printf "$(YELLOW)⚠️  Service not reachable yet, starting tunnel anyway$(RESET)\n"; \
		printf "\n$(GREEN)🌍 Public URL will appear in the cloudflared banner below:$(RESET)\n\n"; \
		cloudflared tunnel --url http://localhost:8090 \
	'

## Misc

doc: ## 📚 Serve documentation on localhost with --host
	cd $(DOCS) && bun install
	cd $(DOCS) && bun run docs:dev --open --host

clean: ## 🧹 Clean files and caches
	@$(GOCLEAN)
	@rm -f $(BINARY_NAME)
	@rm -f $(BINARY_NAME)-ui
	@rm -fr $(WEBAPP)/build
	@rm -fr $(WEBAPP)/node_modules
	@rm -fr $(WEBAPP)/.svelte-kit
	@rm -f $(DOCS)/.astro/*
	@rm -f $(COVOUT) coverage.html coverage.svg
	@echo "🧹 cleaned"

generate: $(ROOT_DIR)/pkg/gen.go
	$(GOGEN) $(ROOT_DIR)/pkg/gen.go

devtools: generate
	pre-commit install
	pre-commit autoupdate

tools: generate $(BIN) $(BIN)/stepci-captured-runner $(BIN)/et-tu-cesr 
	mise install

$(BIN)/stepci-captured-runner:
	wget https://github.com/ForkbombEu/stepci-captured-runner/releases/latest/download/stepci-captured-runner-$(shell uname)-$(shell uname -m) -O $(BIN)/stepci-captured-runner && chmod +x $(BIN)/stepci-captured-runner

$(BIN)/et-tu-cesr:
	wget https://github.com/ForkbombEu/et-tu-cesr/releases/latest/download/et-tu-cesr-$(shell go env GOOS)-$(shell go env GOARCH) -O $(BIN)/et-tu-cesr && chmod +x $(BIN)/et-tu-cesr

## Help:
help: ## Show this help.
	@echo ''
	@echo 'Usage:'
	@echo '  ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
	@echo ''
	@echo 'Targets:'
	@awk 'BEGIN {FS = ":.*?## "} { \
		if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf "    ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
		else if (/^## .*$$/) {printf "  ${CYAN}%s${RESET}\n", substr($$1,4)} \
		}' $(MAKEFILE_LIST)

kill-pocketbase: ## 🔪 Kill any running PocketBase instance
	@echo "Killing any existing PocketBase instance..."
	@-lsof -ti:8090 -sTCP:LISTEN | xargs kill -9 2>/dev/null || true

seed: ## 🌱 Seed the database
	@$(GOCMD) run main.go migrate up && $(GOCMD) run cmd/seeds/seed.go 
