# go-zero 生成風格
GO_ZERO_STYLE := go_zero
GO ?= go
GOFMT ?= gofmt
GOFILES := $(shell find . -name '*.go' -not -path './generate/doc-generate/*')

GO_DOC_DIR  := generate/doc-generate
GO_DOC_BIN  := $(GO_DOC_DIR)/bin/go-doc
API_ENTRY   := ./generate/api/gateway.api
DOC_OUT     := ./docs/openapi

GOCTL ?= goctl
GOCTL_PKG := github.com/zeromicro/go-zero/tools/goctl@latest
GOLANGCI_PKG := github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2

.DEFAULT_GOAL := help

help: ## 顯示可用指令
	@echo "Gateway Makefile"
	@echo ""
	@echo "首次開發："
	@echo "  make tools      安裝 goctl、goimports、golangci-lint（寫入 \$$GOPATH/bin）"
	@echo "  go mod download"
	@echo ""
	@echo "常用："
	@grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf "  make %-14s %s\n", $$1, $$2}'

tools: ## 安裝 goctl、goimports、golangci-lint（需 Go，且 GOPATH/bin 在 PATH）
	@command -v $(GOCTL) >/dev/null 2>&1 || (echo ">> installing goctl" && $(GO) install $(GOCTL_PKG))
	@command -v goimports >/dev/null 2>&1 || (echo ">> installing goimports" && $(GO) install golang.org/x/tools/cmd/goimports@latest)
	@if ! command -v golangci-lint >/dev/null 2>&1 || ! golangci-lint version 2>/dev/null | grep -q 'version 2\.'; then \
		echo ">> installing golangci-lint v2"; \
		$(GO) install $(GOLANGCI_PKG); \
	fi
	@echo "tools OK"
	@echo "  goctl:          $$(goctl --version 2>/dev/null || echo missing)"
	@echo "  golangci-lint:  $$(golangci-lint version 2>/dev/null | head -1 || echo missing)"

gen-api: tools ## 由 .api 生成 handler / logic / types（自訂 handler 模板）
	$(GOCTL) api go -api $(API_ENTRY) -dir . -style $(GO_ZERO_STYLE) -home generate/goctl

gen-mock: ## 依 go:generate 產生 internal/model/*/mock（gomock）
	$(GO) generate ./internal/model/...

build-go-doc: ## 編譯 go-doc（OpenAPI 文件生成器）
	@echo ">> building $(GO_DOC_BIN)"
	@mkdir -p $(GO_DOC_DIR)/bin
	@cd $(GO_DOC_DIR) && $(GO) build -o bin/go-doc ./cmd/go-doc

gen-doc: build-go-doc ## 從 .api 生成 OpenAPI 3.0 YAML
	@mkdir -p $(DOC_OUT)
	$(GO_DOC_BIN) -a $(API_ENTRY) -d $(DOC_OUT) -f gateway -s openapi3.0 -y
	@echo "Generated: $(DOC_OUT)/gateway.yaml"

test: ## 執行測試
	$(GO) test ./...
fmt: ## gofmt + goimports（不含 lint）
	$(GOFMT) -s -w $(GOFILES)
	@command -v goimports >/dev/null 2>&1 && goimports -w . || (echo "goimports not found; run: make tools" && exit 1)

lint: tools ## golangci-lint 靜態檢查
	golangci-lint run ./...

lint-fix: tools ## 自動修正可修的 lint / formatter 問題（見 .golangci.yml）
	golangci-lint run --fix ./...

fix: fmt lint-fix lint ## 格式化 + 自動修 lint + 再檢查（提交前建議）

check: fix test ## 提交 / PR 前完整檢查（fmt、lint、test）

# ============================================================
# Docker compose（本機依賴）
# ============================================================

DOCKER_COMPOSE ?= docker compose
COMPOSE_FILE   := deploy/docker-compose.yml
COMPOSE        := $(DOCKER_COMPOSE) -f $(COMPOSE_FILE)

deps-up: ## 起 Mongo + Redis（最小本機依賴）
	$(COMPOSE) up -d mongo redis

deps-up-smtp: ## 起 Mongo + Redis + MailHog
	$(COMPOSE) --profile smtp up -d mongo redis mailhog

deps-down: ## 停服務（保留 volume）
	$(COMPOSE) --profile smtp --profile k6 --profile ldap down

deps-down-v: ## 停服務並刪 volume（會清資料）
	$(COMPOSE) --profile smtp --profile k6 --profile ldap down -v

deps-logs: ## 看 compose log
	$(COMPOSE) --profile smtp --profile k6 --profile ldap logs -f

# OpenLDAP（本機 LDAP 測試，見 deploy/openldap/）
LDAP_COMPOSE := $(DOCKER_COMPOSE) -f deploy/openldap/docker-compose.yml

ldap-up: ## 只起 OpenLDAP（profile ldap；ZITADEL 用 ldap://openldap:389）
	$(COMPOSE) --profile ldap up -d openldap
	@echo "→ run 'make ldap-wait' to seed alice/bob"
	@echo "→ 測試帳號見 deploy/openldap/README.md"

ldap-down: ## 停 OpenLDAP
	$(COMPOSE) --profile ldap stop openldap

ldap-wait: ## 等 OpenLDAP ready 並 seed alice/bob
	@$(MAKE) -s k6-wait-ldap

# ============================================================
# k6 測試（test/k6/）
# ============================================================

K6                 ?= k6
K6_GATEWAY_CONFIG  := etc/gateway.k6.yaml
K6_GATEWAY_BIN     := bin/gateway-k6
K6_DIR             := test/k6
K6_PAT_FILE        := deploy/zitadel/machinekey/zitadel-admin-sa.token
K6_ENV_FILE        := deploy/zitadel/machinekey/k6.env
K6_BOOTSTRAP_ENV   := deploy/zitadel/machinekey/dev-bootstrap.env
ZITADEL_HEALTH_URL := http://localhost:8080/debug/healthz

# k6 安裝指引（macOS / Linux）
define K6_INSTALL_HINT
k6 not found in PATH.

Install:
  macOS (Homebrew):  brew install k6
  Linux (apt):       sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 \
                     && echo 'deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main' | sudo tee /etc/apt/sources.list.d/k6.list \
                     && sudo apt update && sudo apt install k6
  Other:             https://grafana.com/docs/k6/latest/set-up/install-k6/

Or use docker (no install): K6='docker run --rm -i --network host -v $$PWD:/app -w /app grafana/k6:latest' make k6-smoke
endef
export K6_INSTALL_HINT

k6-check: ## 檢查 k6 是否安裝（沒裝會印 install 指引）
	@command -v $(K6) >/dev/null 2>&1 || (echo "$$K6_INSTALL_HINT"; exit 1)
	@echo "k6: $$($(K6) version 2>&1 | head -1)"

k6-up: ## 起 k6 全棧（mongo + redis + mailhog + postgres + openldap + zitadel）
	$(COMPOSE) --profile k6 up -d mongo redis mailhog postgres openldap zitadel
	@echo "OpenLDAP + ZITADEL bootstrapping (ZITADEL 首次約 30–90s)…"
	@echo "→ run 'make k6-wait' to block until ready"
	@echo "→ LDAP 測試帳號見 deploy/openldap/README.md（alice / Password1!）"

k6-wait-ldap: ## 等 OpenLDAP ready 並 seed alice/bob
	@echo "waiting for OpenLDAP (gateway-openldap)…"
	@for i in $$(seq 1 90); do \
		if docker exec gateway-openldap ldapsearch -x -H ldap://localhost \
			-b "dc=gateway,dc=local" \
			-D "cn=admin,dc=gateway,dc=local" -w admin -LLL -s base "(objectClass=*)" dn 2>/dev/null | grep -q 'dc=gateway'; then \
			echo "openldap ready ($$i s)"; \
			$(MAKE) -s ldap-seed; exit 0; \
		fi; \
		sleep 1; \
	done; \
	echo "openldap did not become ready in 90s — check: docker logs gateway-openldap"; exit 1

ldap-seed: ## 將 deploy/openldap/bootstrap 測試帳號寫入目錄（可重複執行）
	@docker cp deploy/openldap/bootstrap/10-people.ldif gateway-openldap:/tmp/10-people.ldif
	@docker exec gateway-openldap ldapadd -x -H ldap://localhost \
		-D "cn=admin,dc=gateway,dc=local" -w admin -c -f /tmp/10-people.ldif 2>&1 \
		| grep -Ev '^(ldap_add: Already exists|adding new entry)' || true
	@echo "ldap seed done (alice / bob @ ou=people,dc=gateway,dc=local)"

ldap-test: ## 列出 LDAP 測試使用者 alice / bob
	@docker exec gateway-openldap ldapsearch -x -H ldap://localhost \
		-b "ou=people,dc=gateway,dc=local" \
		-D "cn=admin,dc=gateway,dc=local" -w admin \
		"(|(uid=alice)(uid=bob))" uid mail cn

k6-bootstrap-zitadel: ## 自動建立 ZITADEL LDAP IdP + OIDC App（dev / k6）
	@if [ ! -s "$(K6_PAT_FILE)" ]; then echo "PAT missing — run 'make k6-wait' first"; exit 1; fi
	@python3 deploy/zitadel/bootstrap_dev.py
	@echo "→ OAuth / LDAP IdP 寫入 $(K6_BOOTSTRAP_ENV)"

k6-wait: k6-wait-ldap ## 等 OpenLDAP + ZITADEL ready + bootstrap IdP/OAuth + 寫 k6.env
	@echo "waiting for ZITADEL at $(ZITADEL_HEALTH_URL)…"
	@for i in $$(seq 1 120); do \
		if curl -fsS $(ZITADEL_HEALTH_URL) >/dev/null 2>&1; then \
			echo "zitadel ready ($$i s)"; break; \
		fi; \
		sleep 1; \
		if [ $$i -eq 120 ]; then echo "zitadel did not become ready in 120s"; exit 1; fi; \
	done
	@for i in $$(seq 1 30); do \
		if [ -s "$(K6_PAT_FILE)" ]; then break; fi; \
		sleep 1; \
	done
	@if [ ! -s "$(K6_PAT_FILE)" ]; then \
		echo "PAT file $(K6_PAT_FILE) missing — check 'docker logs gateway-zitadel'"; \
		exit 1; \
	fi
	@$(MAKE) -s k6-bootstrap-zitadel
	@PAT=$$(tr -d '\n' < $(K6_PAT_FILE)); \
		printf 'export ZITADEL_SERVICE_TOKEN=%s\nexport BASE_URL=http://localhost:8888\nexport MAILHOG_URL=http://localhost:8025\nexport REDIS_ADDR=localhost:6379\n' "$$PAT" > $(K6_ENV_FILE); \
		if [ -s "$(K6_BOOTSTRAP_ENV)" ]; then cat $(K6_BOOTSTRAP_ENV) >> $(K6_ENV_FILE); fi; \
		echo "wrote $(K6_ENV_FILE)"
	@$(MAKE) -s k6-seed-fixtures
	@echo "tip: 'source $(K6_ENV_FILE)' to load into your shell"
	@echo "tip: run 'make dev-restart-gateway' if gateway was already running"

# k6-seed-fixtures idempotently upserts the k6-tenant + K6INVITE invite code
# into gateway_k6 so /auth/register resolves successfully. The invite hash is
# sha256("K6INVITE") — see internal/model/auth/domain/const.go HashInviteCode.
# Uses mongosh inside the container; no extra Go binary needed.
K6_TENANT_ID    := k6-tenant
K6_INVITE_HASH  := e62a291f0dcf88c50c91fdc78e3539c55e1e20ef645c640b1ec778fe4fabb4cb
k6-seed-fixtures: ## upsert k6-tenant + K6INVITE 到 mongo
	@docker exec gateway-mongo mongosh --quiet --eval '\
db = db.getSiblingDB("gateway_k6"); \
var now = NumberLong(Date.now()); \
var t = db.tenants.updateOne( \
  {tenant_id: "$(K6_TENANT_ID)"}, \
  {$$set: {tenant_id: "$(K6_TENANT_ID)", slug: "$(K6_TENANT_ID)", name: "k6 Tenant", uid_prefix: "K6", status: "active", update_at: now}, $$setOnInsert: {create_at: now}}, \
  {upsert: true} \
); \
var i = db.invite_codes.updateOne( \
  {tenant_id: "$(K6_TENANT_ID)", code_hash: "$(K6_INVITE_HASH)"}, \
  {$$set: {tenant_id: "$(K6_TENANT_ID)", code_hash: "$(K6_INVITE_HASH)", max_uses: NumberLong(1000000), expires_at: NumberLong(0), new_users_only: false, update_at: now}, $$setOnInsert: {used_count: NumberLong(0), create_at: now}}, \
  {upsert: true} \
); \
print("tenant matched=" + t.matchedCount + " upserted=" + (t.upsertedId?1:0) + ", invite matched=" + i.matchedCount + " upserted=" + (i.upsertedId?1:0));' && \
	echo "seeded fixtures (tenant=$(K6_TENANT_ID) invite=K6INVITE) into gateway_k6"

# Back-compat alias
k6-seed-tenant: k6-seed-fixtures ## (alias for k6-seed-fixtures)

k6-build: ## 建 gateway binary 給 k6 使用
	@mkdir -p $(dir $(K6_GATEWAY_BIN))
	$(GO) build -o $(K6_GATEWAY_BIN) ./gateway.go

k6-gateway: k6-build ## 前景啟 gateway（吃 etc/gateway.k6.yaml + ZITADEL env）
	@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
	@set -a; . $(K6_ENV_FILE); set +a; \
		$(K6_GATEWAY_BIN) -f $(K6_GATEWAY_CONFIG)

k6-smoke: k6-check ## 跑 smoke 測試（每個端點一發）
	@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
	@set -a; . $(K6_ENV_FILE); set +a; \
		for f in $(K6_DIR)/smoke/*.js; do \
			echo "==> $$f"; $(K6) run "$$f" || exit 1; \
		done

k6-journey: k6-check ## 跑 journey 測試（多步驟流程）
	@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
	@set -a; . $(K6_ENV_FILE); set +a; \
		for f in $(K6_DIR)/journeys/*.js; do \
			echo "==> $$f"; $(K6) run "$$f" || exit 1; \
		done

k6-all: k6-smoke k6-journey ## smoke + journey

k6-seed-admin: k6-build ## 註冊 k6-admin 並 seed tenant_admin role（rbac journey 用）
	@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
	@mkdir -p bin
	$(GO) build -o bin/k6-seed-admin ./cmd/k6-seed-admin
	@set -a; . $(K6_ENV_FILE); set +a; \
		./bin/k6-seed-admin > $(K6_ENV_FILE).admin || (echo "k6-seed-admin failed"; exit 1); \
		cat $(K6_ENV_FILE).admin >> $(K6_ENV_FILE); \
		echo "admin credentials appended to $(K6_ENV_FILE):"; \
		cat $(K6_ENV_FILE).admin

k6-down: ## 停 k6 stack 並清 volume（會清 Postgres / Mongo / Redis 資料）
	$(COMPOSE) --profile k6 down -v
	@rm -f $(K6_PAT_FILE) $(K6_ENV_FILE) $(K6_ENV_FILE).admin $(K6_ENV_FILE).tmp deploy/zitadel/machinekey/zitadel-admin-sa.json
	@echo "k6 stack stopped, volumes & PAT removed"

# ============================================================
# 一鍵本機測試環境（Docker + Gateway + 種子資料）
# ============================================================

DEV_DIR            := .dev
DEV_GATEWAY_PID    := $(DEV_DIR)/gateway.pid
DEV_GATEWAY_LOG    := $(DEV_DIR)/gateway.log

.PHONY: dev-up dev-down dev-status dev-restart-gateway

dev-up: k6-up k6-wait k6-build dev-restart-gateway ## 一鍵起全套：mongo/redis/mailhog/zitadel + seed + Gateway 背景
	@echo ""
	@echo "=========================================="
	@echo " 本機測試環境已就緒"
	@echo "=========================================="
	@echo " Gateway API    http://localhost:8888"
	@echo " 前端（另開終端） make frontend-dev  →  http://localhost:5173"
	@echo " MailHog 收信   http://localhost:8025  （註冊 OTP 在這裡看）"
	@echo " ZITADEL 主控台 http://localhost:8080/ui/console"
	@echo ""
	@echo " 註冊預設：租戶 k6-tenant · 邀請碼 K6INVITE"
	@echo " LDAP 登入：alice / Password1!（make k6-wait 已自動設定 ZITADEL IdP）"
	@echo ""
	@echo " 管理後台：make dev-seed-admin 後用輸出的帳密登入"
	@echo " 關閉環境：make dev-down"
	@echo " 查看狀態：make dev-status"
	@echo " OAuth/LDAP 設定變更後：make dev-restart-gateway"
	@echo "=========================================="

dev-seed-admin: k6-seed-admin ## 建立具 tenant_admin 的管理員（dev-up 之後執行）

dev-down: ## 停 Gateway 背景行程 + k6 docker stack
	@if [ -f $(DEV_GATEWAY_PID) ]; then \
		pid=$$(cat $(DEV_GATEWAY_PID)); \
		if kill -0 $$pid 2>/dev/null; then kill $$pid && echo "stopped gateway (pid $$pid)"; fi; \
		rm -f $(DEV_GATEWAY_PID); \
	fi
	@$(MAKE) -s k6-down
	@rm -rf $(DEV_DIR)
	@echo "dev environment stopped"

dev-status: ## 顯示 docker / gateway / health 狀態
	@echo "=== docker (k6 profile) ==="
	@$(COMPOSE) --profile k6 ps 2>/dev/null || true
	@echo ""
	@echo "=== gateway :8888 ==="
	@if [ -f $(DEV_GATEWAY_PID) ] && kill -0 $$(cat $(DEV_GATEWAY_PID)) 2>/dev/null; then \
		echo "running pid $$(cat $(DEV_GATEWAY_PID))"; \
		curl -fsS http://localhost:8888/api/v1/health 2>/dev/null | head -c 200 || echo "(health check failed)"; \
		echo ""; \
	else \
		echo "not running (run: make dev-up)"; \
	fi

dev-restart-gateway: k6-build ## 重啟 Gateway（載入 k6.env 的 OAuth / LDAP 設定）
	@if [ ! -s "$(K6_ENV_FILE)" ]; then echo "run 'make k6-wait' first"; exit 1; fi
	@if [ -f $(DEV_GATEWAY_PID) ]; then \
		pid=$$(cat $(DEV_GATEWAY_PID)); \
		kill -0 $$pid 2>/dev/null && kill $$pid || true; \
		rm -f $(DEV_GATEWAY_PID); \
	fi
	@if command -v lsof >/dev/null 2>&1; then \
		orphan=$$(lsof -ti :8888 2>/dev/null || true); \
		if [ -n "$$orphan" ]; then kill $$orphan 2>/dev/null || true; sleep 1; fi; \
	fi
	@mkdir -p $(DEV_DIR)
	@set -a; . $(K6_ENV_FILE); set +a; \
		nohup $(K6_GATEWAY_BIN) -f $(K6_GATEWAY_CONFIG) > $(DEV_GATEWAY_LOG) 2>&1 & \
		echo $$! > $(DEV_GATEWAY_PID); \
		echo "gateway restarted (pid $$(cat $(DEV_GATEWAY_PID)))"
	@for i in $$(seq 1 30); do \
		if curl -fsS http://localhost:8888/api/v1/health >/dev/null 2>&1; then \
			echo "gateway ready ($$i s)"; exit 0; \
		fi; \
		sleep 1; \
	done; \
	echo "gateway did not become ready — tail $(DEV_GATEWAY_LOG)"; tail -20 $(DEV_GATEWAY_LOG); exit 1

# ============================================================
# Frontend（使用者前台 + 管理後台）
# ============================================================

frontend-install: ## 安裝 frontend 依賴
	cd frontend && npm install

frontend-dev: frontend-install ## 啟動前端 dev server（:5173，proxy /api → :8888）
	cd frontend && npm run dev

frontend-build: frontend-install ## 建置前端靜態檔 → frontend/dist/
	cd frontend && npm run build