# Baron SSO용 Docker Compose 헬퍼

# 환경 변수 로드
ifneq (,$(wildcard ./.env))
    include .env
    export
endif

# Compose 파일 경로
COMPOSE_INFRA := compose.infra.yaml
COMPOSE_ORY := compose.ory.yaml
COMPOSE_APP := docker-compose.yaml
AUTH_CONFIG_ENV := config/.generated/auth-config.env
DEV_SERVICES ?= backend adminfront devfront orgfront userfront
DEV_NETWORKS := baron_net ory-net hydranet kratosnet public_net
INFRA_CONTAINERS := baron_postgres baron_clickhouse baron_redis baron_gateway
ORY_CONTAINERS := ory_postgres ory_kratos ory_hydra ory_keto ory_oathkeeper ory_clickhouse ory_vector
APP_CONTAINERS := baron_backend baron_adminfront baron_devfront baron_orgfront baron_userfront
DROP_CONTAINERS := $(INFRA_CONTAINERS) $(ORY_CONTAINERS) $(APP_CONTAINERS) ory_stack_check

COMPOSE_CLI_ENV_ARGS :=
ifneq (,$(wildcard ./.env))
COMPOSE_CLI_ENV_ARGS += --env-file .env
endif
COMPOSE_CLI_ENV_ARGS += --env-file $(AUTH_CONFIG_ENV)

COMPOSE_DROP_ENV_ARGS :=
ifneq (,$(wildcard ./.env))
COMPOSE_DROP_ENV_ARGS += --env-file .env
endif

DUMP_SERVICES ?= all
RESTORE_SERVICES ?= all
FILE_PATH ?=
RESTORE_INPUT ?= $(or $(FILE_PATH),$(word 2,$(MAKECMDGOALS)))
CONFIRM_RESTORE ?=
ALLOW_NON_EMPTY_RESTORE ?= false
DUMP_MODE ?= maintenance
BACKUP_USE_DOCKER ?= true
BACKUP_TOOLS_IMAGE ?= baron-sso-backup-tools:local
BACKUP_TOOLS_DOCKERFILE ?= docker/backup-tools/Dockerfile
BACKUP_DOCKER_ENV_ARGS :=
ifneq (,$(wildcard ./.env))
BACKUP_DOCKER_ENV_ARGS += --env-file .env
endif
ifneq (,$(wildcard ./$(AUTH_CONFIG_ENV)))
BACKUP_DOCKER_ENV_ARGS += --env-file $(AUTH_CONFIG_ENV)
endif
BACKUP_DOCKER_RUN = docker run --rm $(BACKUP_DOCKER_ENV_ARGS) -e BACKUP_REPO_ROOT=/workspace -v /var/run/docker.sock:/var/run/docker.sock -v "$(CURDIR)":/workspace -v /tmp:/tmp -w /workspace $(BACKUP_TOOLS_IMAGE)
DOCKER_IMAGE_REF ?=
WORKS_DOCKER_COMMIT_CONTAINER ?=
WORKS_DOCKER_IMAGE_ARCHIVE_DIR ?= /tmp/baron-sso-docker-image-upload

.PHONY: help build-auth-config validate-auth-config verify-auth-config render-ory-config up up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory ensure-restore-containers up-dev up-front-dev dev dev-debug down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app backup-tools-build dump restore dump-verify restore-verify dump-list restore-plan upload-cloud works-drive-refresh-token dump-upload-cloud docker-image-upload-works

help: ## 생성된 타깃과 옵션 목록 표시
	@printf "Usage:\n  make <target> [OPTION=value ...]\n\n"
	@printf "Targets:\n"
	@awk ' \
		BEGIN { current = ""; printed_section = 0 } \
		/^# --- .+ ---/ { \
			current = $$0; \
			gsub(/^# ---[[:space:]]*/, "", current); \
			gsub(/[[:space:]]*---$$/, "", current); \
			next; \
		} \
		/^[[:alnum:]_.-]+:([^=]|$$)/ { \
			line = $$0; \
			target = line; \
			sub(/:.*/, "", target); \
			if (target ~ /^\.|%/) { next } \
			if (seen[target]++) { next } \
			desc = ""; \
			if (line ~ /##/) { \
				desc = line; \
				sub(/^.*##[[:space:]]*/, "", desc); \
			} \
			if (current != "" && current != printed_section) { \
				printf "\n  %s\n", current; \
				printed_section = current; \
			} \
			if (desc != "") { \
				printf "    %-36s %s\n", target, desc; \
			} else { \
				printf "    %-36s\n", target; \
			} \
		} \
	' Makefile
	@printf "\nOptions:\n"
	@awk ' \
		/^[A-Z][A-Z0-9_]+[[:space:]]*\?=/ { \
			name = $$1; \
			value = $$0; \
			sub(/[[:space:]]*\?=.*/, "", name); \
			sub(/^[^?]+\?=[[:space:]]*/, "", value); \
			printf "  %-32s default: %s\n", name, value; \
		} \
	' Makefile
	@printf "\nRestore Safety:\n"
	@printf "  CONFIRM_RESTORE=baron-sso          복구 실행 의도를 명시하는 필수 확인값\n"
	@printf "  ALLOW_NON_EMPTY_RESTORE=true       비어 있지 않은 복구 대상에 덮어쓰는 승인된 복구에서만 사용\n"
	@printf "\nRestore Examples:\n"
	@printf "  make restore-plan FILE_PATH=stg.today.tar.gz CONFIRM_RESTORE=baron-sso\n"
	@printf "  make restore FILE_PATH=stg.today.tar.gz CONFIRM_RESTORE=baron-sso ALLOW_NON_EMPTY_RESTORE=true\n"

# --- 인증 설정 빌드/검증 ---
build-auth-config: ## 인증 설정 파일 생성
	@echo "Building auth config..."
	@mkdir -p config/.generated
	@bash scripts/auth_config.sh build

validate-auth-config: build-auth-config ## 인증 설정 값 검증
	@echo "Validating auth config..."
	@bash scripts/auth_config.sh validate

verify-auth-config: validate-auth-config ## 인증 설정 연결 상태 확인
	@echo "Verifying auth config wiring..."
	@bash scripts/auth_config.sh verify

render-ory-config: validate-auth-config ## Ory 설정 파일 렌더링
	@echo "Rendering Ory config..."
	@bash scripts/render_ory_config.sh

# --- 기본 실행 ---
# 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음)
up: up-all ## 전체 로컬 스택 실행

up-all: ensure-networks render-ory-config ## 인프라, Ory, 앱 스택 모두 실행
	@echo "Starting ALL stacks (infra + ory + app)..."
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up --build -d
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) restart kratos

# --- 개별 스택 실행 ---
up-infra: ensure-networks ## 인프라 스택 실행
	@echo "Starting Infra stack (postgres/clickhouse/redis)..."
	docker compose -f $(COMPOSE_INFRA) up -d

up-ory: ensure-networks render-ory-config ## Ory 스택 실행
	@echo "Starting Ory stack (kratos/hydra/keto/oathkeeper)..."
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) restart kratos

up-app: ensure-networks render-ory-config ## 앱 스택 실행
	@echo "Starting App stack (backend/userfront/adminfront/devfront/orgfront)..."
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build -d

up-backend: ensure-networks render-ory-config ## 백엔드 컨테이너만 실행
	@echo "Starting Backend only..."
	docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build -d backend

ensure-networks: ## 개발용 Docker 네트워크 보장
	@echo "Ensuring Docker networks..."
	@for network in $(DEV_NETWORKS); do \
		if ! docker network inspect "$$network" >/dev/null 2>&1; then \
			echo "Creating Docker network $$network..."; \
			docker network create "$$network"; \
		else \
			echo "Docker network $$network already exists."; \
		fi; \
	done

ensure-infra: ensure-networks ## 인프라 스택 실행 상태 보장
	@echo "Ensuring Infra stack..."
	@missing=0; \
	for container in $(INFRA_CONTAINERS); do \
		if [ "$$(docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null)" != "true" ]; then \
			missing=1; \
			break; \
		fi; \
	done; \
	if [ "$$missing" -eq 1 ]; then \
		echo "Starting missing Infra stack containers in daemon mode..."; \
		docker compose -f $(COMPOSE_INFRA) up -d; \
	else \
		echo "Infra stack is already running."; \
	fi

ensure-ory: ensure-networks render-ory-config ## Ory 스택 실행 상태 보장
	@echo "Ensuring Ory stack..."
	@missing=0; \
	for container in $(ORY_CONTAINERS); do \
		if [ "$$(docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null)" != "true" ]; then \
			missing=1; \
			break; \
		fi; \
	done; \
	if [ "$$missing" -eq 1 ]; then \
		echo "Starting missing Ory stack containers in daemon mode..."; \
		docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d; \
	else \
		echo "Ory stack is already running. Restarting Kratos to apply rendered dev config..."; \
		docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) restart kratos; \
	fi

ensure-restore-containers: ## 복구 대상 저장소 컨테이너 실행 상태 보장
	@echo "Ensuring restore target containers..."
	@if [ "$(CONFIRM_RESTORE)" != "baron-sso" ]; then \
		echo "Skipping restore target container startup until CONFIRM_RESTORE=baron-sso is provided."; \
		exit 0; \
	fi
	@$(MAKE) --no-print-directory ensure-networks
	@ensure_restore_container() { \
		container="$$1"; \
		compose_file="$$2"; \
		compose_service="$$3"; \
		if docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null | grep -qx 'true'; then \
			echo "Restore target container $$container is already running."; \
			return 0; \
		fi; \
		if docker inspect "$$container" >/dev/null 2>&1; then \
			echo "Starting stopped restore target container $$container..."; \
			docker start "$$container"; \
		else \
			echo "Creating restore target container $$container via $$compose_file service $$compose_service..."; \
			docker compose -f "$$compose_file" up -d "$$compose_service"; \
		fi; \
		for attempt in 1 2 3 4 5 6 7 8 9 10; do \
			if docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null | grep -qx 'true'; then \
				return 0; \
			fi; \
			sleep 1; \
		done; \
		echo "ERROR: restore target container $$container did not reach running state." >&2; \
		return 1; \
	}; \
	services="$(RESTORE_SERVICES)"; \
	if [ -z "$$services" ] || [ "$$services" = "all" ]; then \
		services="postgres ory-postgres clickhouse ory-clickhouse config"; \
	else \
		services="$$(printf '%s' "$$services" | tr ',' ' ')"; \
	fi; \
	for service in $$services; do \
		case "$$service" in \
			postgres) ensure_restore_container baron_postgres compose.infra.yaml postgres ;; \
			ory-postgres) ensure_restore_container ory_postgres compose.ory.yaml postgres ;; \
			clickhouse) ensure_restore_container baron_clickhouse compose.infra.yaml clickhouse ;; \
			ory-clickhouse) ensure_restore_container ory_clickhouse compose.ory.yaml ory_clickhouse ;; \
			config) ;; \
			*) echo "ERROR: unknown restore service: $$service" >&2; exit 1 ;; \
		esac; \
	done

up-dev: ensure-infra ensure-ory ## 개발 기본 스택 준비
	@echo "Dev stack is up (infra + ory)."

up-front-dev: up-infra up-ory up-backend ## 프론트 개발용 의존 스택 준비
	@echo "Dev stack is up (infra + ory + backend)."

dev: up-dev ## 개발 앱 컨테이너를 포그라운드로 실행
	@echo "Starting development app containers in foreground attach mode..."
	BACKEND_LOG_LEVEL=info CLIENT_LOG_DEBUG=false VITE_CLIENT_LOG_DEBUG=false docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build $(DEV_SERVICES)

dev-debug: up-dev ## 디버그 로그로 개발 앱 컨테이너 실행
	@echo "Starting development app containers in foreground attach debug mode..."
	BACKEND_LOG_LEVEL=debug CLIENT_LOG_DEBUG=true VITE_CLIENT_LOG_DEBUG=true USERFRONT_FLUTTER_RUN_FLAGS=--debug docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build $(DEV_SERVICES)

# --- 종료 (Down) ---
down: ## 전체 로컬 스택 중지
	@echo "Stopping ALL stacks (infra + ory + app)..."
	docker compose -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) down

drop: ## 로컬 스택 컨테이너, 볼륨, 로컬 이미지 제거
	@echo "Dropping Baron SSO local Docker stack containers, volumes, and local images..."
	-docker compose $(COMPOSE_DROP_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) down -v --rmi local
	@echo "Removing any remaining fixed-name Baron SSO containers..."
	@for container in $(DROP_CONTAINERS); do \
		docker rm -f "$$container" >/dev/null 2>&1 || true; \
	done
	@echo "Drop complete. External Docker networks are preserved."

down-app: ## 앱 스택 중지
	@echo "Stopping App stack..."
	docker compose -f $(COMPOSE_APP) down

down-backend: ## 백엔드 컨테이너 중지
	@echo "Stopping Backend only..."
	docker compose -f $(COMPOSE_APP) stop backend

down-infra: ## 인프라 스택 중지
	@echo "Stopping Infra stack..."
	docker compose -f $(COMPOSE_INFRA) down

down-ory: ## Ory 스택 중지
	@echo "Stopping Ory stack..."
	docker compose -f $(COMPOSE_ORY) down

# --- 유틸리티 ---
# 인프라 상태 확인
check-infra: ## 인프라 헬스 상태 확인
	@echo "Checking infra status..."
	@if [ "$$(docker inspect -f '{{.State.Health.Status}}' baron_postgres 2>/dev/null)" != "healthy" ]; then \
			echo "Error: PostgreSQL is not running or not healthy."; \
			echo "Please run 'make up-infra' first."; \
			exit 1; \
	else \
			echo "PostgreSQL is healthy."; \
	fi

ps: ## 전체 Compose 컨테이너 상태 조회
	docker compose -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) ps

logs-infra: ## 인프라 스택 로그 팔로우
	docker compose -f $(COMPOSE_INFRA) logs -f

logs-ory: ## Ory 스택 로그 팔로우
	docker compose -f $(COMPOSE_ORY) logs -f

logs-app: ## 앱 스택 로그 팔로우
	docker compose -f $(COMPOSE_APP) logs -f

# --- 백업/복구 ---
backup-tools-build: ## 백업 도구 Docker 이미지 빌드
	docker build -f $(BACKUP_TOOLS_DOCKERFILE) -t $(BACKUP_TOOLS_IMAGE) .

ifeq ($(BACKUP_USE_DOCKER),true)
dump: backup-tools-build ## 백업 덤프 생성
	$(BACKUP_DOCKER_RUN) bash -lc 'DUMP_SERVICES="$(DUMP_SERVICES)" DUMP_MODE="$(DUMP_MODE)" BACKUP="$(BACKUP)" BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump.sh'

restore: backup-tools-build ensure-restore-containers ## 백업 덤프 복구
	$(BACKUP_DOCKER_RUN) bash -lc 'RESTORE_INPUT="$(RESTORE_INPUT)" BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" ALLOW_NON_EMPTY_RESTORE="$(ALLOW_NON_EMPTY_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore.sh'

dump-verify: backup-tools-build ## 백업 덤프 검증
	$(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" scripts/backup/verify-dump.sh'

restore-verify: backup-tools-build ## 복구 결과 검증
	$(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" scripts/backup/verify-restore.sh'

dump-list: backup-tools-build ## 사용 가능한 백업 덤프 목록 조회
	$(BACKUP_DOCKER_RUN) bash -lc 'BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump-list.sh'

restore-plan: backup-tools-build ## 복구 실행 계획 출력
	$(BACKUP_DOCKER_RUN) bash -lc 'RESTORE_INPUT="$(RESTORE_INPUT)" BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore-plan.sh'

upload-cloud: backup-tools-build ## 백업 덤프 클라우드 업로드
	$(BACKUP_DOCKER_RUN) bash -lc '$(if $(WORKS_DRIVE_DRY_RUN),WORKS_DRIVE_DRY_RUN="$(WORKS_DRIVE_DRY_RUN)" )$(if $(WORKS_DRIVE_AUTH_MODE),WORKS_DRIVE_AUTH_MODE="$(WORKS_DRIVE_AUTH_MODE)" )BACKUP="$(BACKUP)" scripts/backup/upload_cloud.sh'

works-drive-refresh-token: ## WORKS Drive OAuth refresh token 갱신
	WORKS_DRIVE_TOKEN_GRANT="$(WORKS_DRIVE_TOKEN_GRANT)" WORKS_DRIVE_AUTH_CODE="$(WORKS_DRIVE_AUTH_CODE)" WORKS_DRIVE_AUTH_CALLBACK_URL="$(WORKS_DRIVE_AUTH_CALLBACK_URL)" scripts/backup/refresh_works_drive_token.sh
else
dump: ## 백업 덤프 생성
	DUMP_SERVICES="$(DUMP_SERVICES)" DUMP_MODE="$(DUMP_MODE)" BACKUP="$(BACKUP)" BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump.sh

restore: ensure-restore-containers ## 백업 덤프 복구
	RESTORE_INPUT="$(RESTORE_INPUT)" BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" ALLOW_NON_EMPTY_RESTORE="$(ALLOW_NON_EMPTY_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore.sh

dump-verify: ## 백업 덤프 검증
	BACKUP="$(BACKUP)" scripts/backup/verify-dump.sh

restore-verify: ## 복구 결과 검증
	BACKUP="$(BACKUP)" scripts/backup/verify-restore.sh

dump-list: ## 사용 가능한 백업 덤프 목록 조회
	BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump-list.sh

restore-plan: ## 복구 실행 계획 출력
	RESTORE_INPUT="$(RESTORE_INPUT)" BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore-plan.sh

upload-cloud: ## 백업 덤프 클라우드 업로드
	$(if $(WORKS_DRIVE_DRY_RUN),WORKS_DRIVE_DRY_RUN="$(WORKS_DRIVE_DRY_RUN)" )$(if $(WORKS_DRIVE_AUTH_MODE),WORKS_DRIVE_AUTH_MODE="$(WORKS_DRIVE_AUTH_MODE)" )BACKUP="$(BACKUP)" scripts/backup/upload_cloud.sh

works-drive-refresh-token: ## WORKS Drive OAuth refresh token 갱신
	WORKS_DRIVE_TOKEN_GRANT="$(WORKS_DRIVE_TOKEN_GRANT)" WORKS_DRIVE_AUTH_CODE="$(WORKS_DRIVE_AUTH_CODE)" WORKS_DRIVE_AUTH_CALLBACK_URL="$(WORKS_DRIVE_AUTH_CALLBACK_URL)" scripts/backup/refresh_works_drive_token.sh
endif

dump-upload-cloud: dump upload-cloud ## 백업 덤프 생성 후 클라우드 업로드

docker-image-upload-works: ## Docker 이미지를 WORKS Shared Drive archive로 업로드
	WORKS_DOCKER_COMMIT_CONTAINER="$(WORKS_DOCKER_COMMIT_CONTAINER)" DOCKER_IMAGE_REF="$(DOCKER_IMAGE_REF)" WORKS_DOCKER_IMAGE_ARCHIVE_DIR="$(WORKS_DOCKER_IMAGE_ARCHIVE_DIR)" scripts/docker-image/upload_works_drive.sh

# --- 로컬 통합 코드 체크 ---
PLAYWRIGHT_BROWSERS_PATH := $(HOME)/.cache/ms-playwright
PLAYWRIGHT_CHROMIUM_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/chromium-1208/INSTALLATION_COMPLETE
PLAYWRIGHT_FIREFOX_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/firefox-1509/INSTALLATION_COMPLETE
PLAYWRIGHT_WEBKIT_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/webkit-2248/INSTALLATION_COMPLETE

PLAYWRIGHT_INSTALL_ALL := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_FIREFOX_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_WEBKIT_COMPLETE)" ]; then echo "Playwright browsers already installed"; else npx playwright install; fi'
PLAYWRIGHT_INSTALL_CHROMIUM := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ]; then echo "Playwright chromium already installed"; else npx playwright install chromium; fi'

.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-i18n-values code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-orgfront-tests code-check-userfront-e2e-tests

CODE_CHECK_TEST_JOBS ?= 1
PLAYWRIGHT_WORKERS ?= 1
FLUTTER_TEST_CONCURRENCY ?= 1

code-check: code-check-lint code-check-test-jobs ## 로컬 CI 상당 코드 검사 실행
	@echo "code-check complete."

code-check-lint: code-check-i18n code-check-i18n-values code-check-front-lint code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint ## 로컬 린트와 정적 검사 실행

code-check-test-jobs: ## 코드 검사 테스트 작업 실행
	@echo "==> run CI-equivalent test jobs (parallel)"
	@$(MAKE) --no-print-directory -j$(CODE_CHECK_TEST_JOBS) --output-sync=target \
		code-check-backend-tests \
		code-check-userfront-tests \
		code-check-userfront-e2e-tests \
		code-check-adminfront-tests \
		code-check-devfront-tests \
		code-check-orgfront-tests

code-check-i18n: ## i18n 리소스 검사
	@echo "==> i18n resource check"
	@mkdir -p reports
	node tools/i18n-scanner/index.js
	node tools/i18n-scanner/report.js
	@cat reports/i18n-report.txt

code-check-i18n-values: ## i18n 번역 값 품질 검사
	@echo "==> i18n value quality check"
	@mkdir -p reports
	node tools/i18n-scanner/value-check.js
	@cat reports/i18n-value-report.txt

code-check-go-lint: ## Go 포맷과 린트 검사
	@echo "==> go lint/format check"
	@if command -v golangci-lint >/dev/null 2>&1; then \
		cd backend && golangci-lint fmt -E gofmt -E gofumpt -d; \
	elif command -v docker >/dev/null 2>&1; then \
		docker run --rm \
			-v "$$(pwd)/backend:/app" \
			-w /app \
			golangci/golangci-lint:v2.10.1 \
			golangci-lint fmt -E gofmt -E gofumpt -d; \
	else \
		echo "ERROR: golangci-lint not found and docker is unavailable."; \
		echo "Install golangci-lint v2.10.1 or Docker to match CI lint step."; \
		exit 1; \
	fi

code-check-sync-userfront-locales: ## UserFront 로케일 동기화 검사
	@echo "==> sync userfront locales"
	/bin/sh ./scripts/sync_userfront_locales.sh

code-check-userfront-install: ## UserFront 의존성 설치
	@echo "==> install userfront dependencies"
	@if command -v flutter >/dev/null 2>&1; then \
		cd userfront && flutter pub get; \
	else \
		echo "WARNING: flutter not found, skipping userfront dependencies install."; \
	fi

code-check-userfront-lint: ## UserFront 포맷과 analyze 검사
	@echo "==> userfront format/analyze"
	@if command -v dart >/dev/null 2>&1; then \
		cd userfront && dart format --output=none --set-exit-if-changed lib test; \
	else \
		echo "WARNING: dart not found, skipping userfront format check."; \
	fi
	@if command -v flutter >/dev/null 2>&1; then \
		cd userfront && flutter analyze --no-fatal-warnings --no-fatal-infos; \
	else \
		echo "WARNING: flutter not found, skipping userfront analyze."; \
	fi

code-check-front-lint: ## 프론트엔드 Biome 린트와 포맷 검사
	@echo "==> adminfront biome lint/format check"
	rm -rf adminfront/playwright-report adminfront/test-results
	@if [ -d adminfront/node_modules ]; then \
		echo "adminfront/node_modules already present; skipping pnpm install."; \
	else \
		cd adminfront && CI=true npx pnpm install --frozen-lockfile --ignore-scripts; \
	fi
	cd adminfront && npx biome lint .
	cd adminfront && npx biome format .
	@echo "==> devfront biome lint/format check"
	rm -rf devfront/playwright-report devfront/test-results
	@if [ -d devfront/node_modules ]; then \
		echo "devfront/node_modules already present; skipping npm install."; \
	else \
		cd devfront && npm ci --ignore-scripts; \
	fi
	cd devfront && npx biome lint .
	cd devfront && npx biome format .
	@echo "==> orgfront biome lint/format check"
	rm -rf orgfront/playwright-report orgfront/test-results
	@if [ -d orgfront/node_modules ]; then \
		echo "orgfront/node_modules already present; skipping npm install."; \
	else \
		cd orgfront && npm ci --ignore-scripts; \
	fi
	cd orgfront && ./node_modules/@biomejs/biome/bin/biome lint .
	cd orgfront && ./node_modules/@biomejs/biome/bin/biome format .

code-check-backend-tests: ## 백엔드 Go 테스트 실행
	@echo "==> backend tests"
	cd backend && GOCACHE=/tmp/baron-sso-go-cache go test -v ./...

code-check-userfront-tests: ## UserFront Flutter 테스트 실행
	@echo "==> userfront tests (isolated workspace)"
	@if ! command -v flutter >/dev/null 2>&1; then \
		echo "WARNING: flutter not found, skipping userfront tests."; \
		exit 0; \
	fi; \
	tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-tests.XXXXXX)"; \
	trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
	mkdir -p "$$tmp_dir/scripts"; \
	cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
	cp -R locales "$$tmp_dir/locales"; \
	if command -v rsync >/dev/null 2>&1; then \
		rsync -a --delete \
			--exclude '.dart_tool' \
			--exclude 'build' \
			--exclude '.pub-cache' \
			--exclude '.flutter-plugins' \
			--exclude '.flutter-plugins-dependencies' \
			userfront/ "$$tmp_dir/userfront/"; \
	else \
		cp -R userfront "$$tmp_dir/userfront"; \
		rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \
	fi; \
	cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh; \
	cd "$$tmp_dir/userfront" && flutter test --concurrency=$(FLUTTER_TEST_CONCURRENCY)

code-check-adminfront-tests: ## AdminFront 테스트 실행
	@echo "==> adminfront tests"
	PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) ./scripts/run_adminfront_ci_tests.sh adminfront-tests

code-check-devfront-tests: ## DevFront 테스트 실행
	@echo "==> devfront tests"
	@mkdir -p reports/devfront
	@rm -rf reports/devfront/playwright-report reports/devfront/test-results
	@status=0; \
	preview_pattern='[v]ite preview --host 127.0.0.1 --strictPort --port 4174'; \
	pkill -f "$$preview_pattern" >/dev/null 2>&1 || true; \
	trap 'pkill -f "$$preview_pattern" >/dev/null 2>&1 || true' EXIT INT TERM; \
	if [ -d devfront/node_modules ]; then \
		echo "devfront/node_modules already present; skipping npm install."; \
	else \
		(cd devfront && npm ci --ignore-scripts) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		(cd devfront && PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \
	fi; \
	[ -d devfront/playwright-report ] && cp -R devfront/playwright-report reports/devfront/ || true; \
	[ -d devfront/test-results ] && cp -R devfront/test-results reports/devfront/ || true; \
	exit $$status

code-check-orgfront-tests: ## OrgFront 테스트 실행
	@echo "==> orgfront tests"
	@mkdir -p reports/orgfront
	@rm -rf reports/orgfront/playwright-report reports/orgfront/test-results
	@status=0; \
	(cd orgfront && npm ci --ignore-scripts) || status=$$?; \
	if [ $$status -eq 0 ]; then \
		(cd orgfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		(cd orgfront && PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \
	fi; \
	[ -d orgfront/playwright-report ] && cp -R orgfront/playwright-report reports/orgfront/ || true; \
	[ -d orgfront/test-results ] && cp -R orgfront/test-results reports/orgfront/ || true; \
	exit $$status

code-check-userfront-e2e-tests: ## UserFront WASM E2E 테스트 실행
	@echo "==> userfront wasm playwright e2e tests (isolated workspace)"
	@if ! command -v flutter >/dev/null 2>&1; then \
		echo "WARNING: flutter not found, skipping userfront e2e tests."; \
		exit 0; \
	fi; \
	mkdir -p reports/userfront-e2e; \
	rm -rf reports/userfront-e2e/playwright-report reports/userfront-e2e/test-results; \
	tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-e2e-tests.XXXXXX)"; \
	trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
	mkdir -p "$$tmp_dir/scripts"; \
	cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
	cp -R locales "$$tmp_dir/locales"; \
	if command -v rsync >/dev/null 2>&1; then \
		rsync -a --delete \
			--exclude '.dart_tool' \
			--exclude 'build' \
			--exclude '.pub-cache' \
			--exclude '.flutter-plugins' \
			--exclude '.flutter-plugins-dependencies' \
			userfront/ "$$tmp_dir/userfront/"; \
		rsync -a --delete \
			--exclude 'node_modules' \
			--exclude 'playwright-report' \
			--exclude 'test-results' \
			userfront-e2e/ "$$tmp_dir/userfront-e2e/"; \
	else \
		cp -R userfront "$$tmp_dir/userfront"; \
		rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \
		cp -R userfront-e2e "$$tmp_dir/userfront-e2e"; \
		rm -rf "$$tmp_dir/userfront-e2e/node_modules" "$$tmp_dir/userfront-e2e/playwright-report" "$$tmp_dir/userfront-e2e/test-results"; \
	fi; \
	status=0; \
	(cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh) || status=$$?; \
	if [ $$status -eq 0 ]; then \
		(cd "$$tmp_dir/userfront-e2e" && npm ci) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		(cd "$$tmp_dir/userfront" && flutter build web --wasm --release) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		(cd "$$tmp_dir/userfront-e2e" && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
	fi; \
	if [ $$status -eq 0 ]; then \
		port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \
		echo "==> userfront-e2e using PORT=$$port"; \
		(cd "$$tmp_dir/userfront-e2e" && PORT=$$port PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \
	fi; \
	[ -d "$$tmp_dir/userfront-e2e/playwright-report" ] && cp -R "$$tmp_dir/userfront-e2e/playwright-report" reports/userfront-e2e/ || true; \
	[ -d "$$tmp_dir/userfront-e2e/test-results" ] && cp -R "$$tmp_dir/userfront-e2e/test-results" reports/userfront-e2e/ || true; \
	exit $$status
