SHELL := /bin/bash

DEV_RUST_LOG := info,executor::candidate_loop=debug,stream::execution=debug
ifeq ($(origin RUST_LOG), command line)
DEV_RUST_LOG := $(RUST_LOG)
endif
export DEV_RUST_LOG

.PHONY: dev dev-backend dev-frontend db-status db-prepare migration backfill

define DEV_BACKEND_SCRIPT
set -euo pipefail

if [ ! -f .env ]; then
	echo "=> 未找到 .env，请先执行: cp .env.example .env"
	exit 1
fi

set -a
source .env
set +a

if [[ -n "$${ADMIN_EMAIL:-}" || -n "$${ADMIN_USERNAME:-}" || -n "$${ADMIN_PASSWORD:-}" ]]; then
	if [[ -z "$${ADMIN_USERNAME:-}" || -z "$${ADMIN_PASSWORD:-}" ]]; then
		echo "=> 管理员自举配置不完整，请在 .env 中设置 ADMIN_USERNAME 和 ADMIN_PASSWORD"
		exit 1
	fi
fi

dotenv_has_key() {
	local key="$$1"
	grep -Eq "^[[:space:]]*$${key}=" .env
}

lowercase() {
	printf '%s' "$$1" | tr '[:upper:]' '[:lower:]'
}

dev_uses_postgres_database() {
	local driver
	local url
	driver="$$(lowercase "$${AETHER_DATABASE_DRIVER:-}")"
	url="$${AETHER_DATABASE_URL:-$${DATABASE_URL:-}}"

	if [[ -z "$${driver}" && -z "$${url}" ]]; then
		return 0
	fi

	[[ "$${driver}" == "postgres" || "$${driver}" == "postgresql" || "$${url}" == postgres:* || "$${url}" == postgresql:* ]]
}

dev_uses_redis_runtime() {
	local backend
	backend="$$(lowercase "$${AETHER_RUNTIME_BACKEND:-}")"

	if [[ "$${backend}" == "memory" ]]; then
		return 1
	fi
	if [[ "$${backend}" == "redis" ]]; then
		return 0
	fi

	return 0
}

print_dev_infra_hint() {
	echo "=> 本地开发依赖未就绪。"
	echo "=> 可手动启动 Postgres / Redis:"
	echo "=>   docker compose up -d postgres redis"
}

check_postgres_ready() {
	local host="$$1"
	local port="$$2"

	if command -v pg_isready >/dev/null 2>&1; then
		pg_isready -h "$${host}" -p "$${port}" >/dev/null 2>&1
		return $$?
	fi

	if command -v nc >/dev/null 2>&1; then
		nc -z "$${host}" "$${port}" >/dev/null 2>&1
		return $$?
	fi

	return 0
}

check_redis_ready() {
	local host="$$1"
	local port="$$2"
	local password="$$3"

	if command -v redis-cli >/dev/null 2>&1; then
		REDISCLI_AUTH="$${password}" redis-cli -h "$${host}" -p "$${port}" ping >/dev/null 2>&1
		return $$?
	fi

	if command -v nc >/dev/null 2>&1; then
		nc -z "$${host}" "$${port}" >/dev/null 2>&1
		return $$?
	fi

	return 0
}

is_local_host() {
	case "$$1" in
		localhost|127.0.0.1|::1)
			return 0
			;;
	esac

	return 1
}

ensure_dev_infra() {
	local postgres_host="$${DB_HOST:-localhost}"
	local postgres_port="$${DB_PORT:-5432}"
	local redis_host="$${REDIS_HOST:-localhost}"
	local redis_port="$${REDIS_PORT:-6379}"
	local redis_password="$${REDIS_PASSWORD:-}"
	local need_postgres=false
	local need_redis=false
	local services=()

	if dev_uses_postgres_database; then
		if ! check_postgres_ready "$${postgres_host}" "$${postgres_port}"; then
			if is_local_host "$${postgres_host}"; then
				need_postgres=true
				services+=(postgres)
			else
				echo "=> PostgreSQL 不可用: $${postgres_host}:$${postgres_port}"
				print_dev_infra_hint
				return 1
			fi
		fi
	fi

	if dev_uses_redis_runtime; then
		if ! check_redis_ready "$${redis_host}" "$${redis_port}" "$${redis_password}"; then
			if is_local_host "$${redis_host}"; then
				need_redis=true
				services+=(redis)
			else
				echo "=> Redis 不可用: $${redis_host}:$${redis_port}"
				print_dev_infra_hint
				return 1
			fi
		fi
	fi

	if [ "$${#services[@]}" -eq 0 ]; then
		return 0
	fi

	if ! command -v docker >/dev/null 2>&1; then
		echo "=> 未找到 docker，无法自动启动本地开发依赖。"
		print_dev_infra_hint
		return 1
	fi

	echo "=> 本地开发依赖未就绪，正在启动: docker compose up -d $${services[*]}"
	if ! docker compose up -d "$${services[@]}"; then
		echo "=> docker compose 启动本地开发依赖失败。"
		print_dev_infra_hint
		return 1
	fi

	for _ in {1..100}; do
		local ready=true
		if [ "$${need_postgres}" = "true" ] && ! check_postgres_ready "$${postgres_host}" "$${postgres_port}"; then
			ready=false
		fi
		if [ "$${need_redis}" = "true" ] && ! check_redis_ready "$${redis_host}" "$${redis_port}" "$${redis_password}"; then
			ready=false
		fi
		if [ "$${ready}" = "true" ]; then
			return 0
		fi
		sleep 0.2
	done

	if [ "$${need_postgres}" = "true" ] && ! check_postgres_ready "$${postgres_host}" "$${postgres_port}"; then
		echo "=> PostgreSQL 不可用: $${postgres_host}:$${postgres_port}"
	fi
	if [ "$${need_redis}" = "true" ] && ! check_redis_ready "$${redis_host}" "$${redis_port}" "$${redis_password}"; then
		echo "=> Redis 不可用: $${redis_host}:$${redis_port}"
	fi
	print_dev_infra_hint
	return 1
}

print_startup_failure_hint() {
	local log_file="$$1"

	if [ -n "$${log_file}" ] && [ -f "$${log_file}" ]; then
		if grep -Eq "database schema is behind" "$${log_file}"; then
			echo "=> 检测到数据库尚未准备完成，请执行: make db-prepare"
			return
		fi

		if grep -Eq "database backfills are behind" "$${log_file}"; then
			echo "=> 检测到数据库尚未准备完成，请执行: make db-prepare"
			return
		fi

		if grep -Eq "bootstrap admin env is partially configured.*ADMIN_PASSWORD" "$${log_file}"; then
			echo "=> 首次启动需要管理员密码，请在 .env 中设置 ADMIN_PASSWORD"
			return
		fi
	fi

	echo "=> 未识别到明确的修复动作，请根据上面的日志继续排查。"
}

wait_for_startup() {
	local pid="$$1"
	local timeout_seconds="$$2"
	local service_name="$$3"
	shift 3

	STARTUP_WAIT_EARLY_EXIT=false

	local attempts=$$((timeout_seconds * 10))
	if [ "$${attempts}" -lt 1 ]; then
		attempts=1
	fi

	for ((i = 0; i < attempts; i++)); do
		if "$$@" >/dev/null 2>&1; then
			return 0
		fi

		if ! kill -0 "$${pid}" >/dev/null 2>&1; then
			STARTUP_WAIT_EARLY_EXIT=true
			echo "=> $${service_name} 启动进程已提前退出，请检查上面的日志。"
			print_startup_failure_hint "$${GATEWAY_LOG_FILE}"
			return 1
		fi

		sleep 0.1
	done

	if "$$@" >/dev/null 2>&1; then
		return 0
	fi

	if ! kill -0 "$${pid}" >/dev/null 2>&1; then
		STARTUP_WAIT_EARLY_EXIT=true
		echo "=> $${service_name} 启动进程已提前退出，请检查上面的日志。"
		print_startup_failure_hint "$${GATEWAY_LOG_FILE}"
		return 1
	fi

	echo "=> $${service_name} 在 $${timeout_seconds}s 内未通过启动检查。"
	echo "=> 如果这是冷编译或存在并发 cargo 构建，可调大启动超时后重试。"
	return 1
}

create_gateway_log_file() {
	local tmp_root="$${TMPDIR:-/tmp}"
	tmp_root="$${tmp_root%/}"

	GATEWAY_LOG_DIR="$$(mktemp -d "$${tmp_root}/aether-dev-startup.XXXXXX")"
	GATEWAY_LOG_FILE="$${GATEWAY_LOG_DIR}/gateway.log"
	: > "$${GATEWAY_LOG_FILE}"
}

cleanup() {
	local status="$${1:-0}"
	trap - INT TERM EXIT

	if [ -n "$${GATEWAY_PID:-}" ]; then
		echo ""
		echo "=> 停止 aether-gateway..."
		kill "$${GATEWAY_PID}" >/dev/null 2>&1 || true
		wait "$${GATEWAY_PID}" >/dev/null 2>&1 || true
	fi

	if [ -n "$${GATEWAY_LOG_FILE:-}" ] && [ -f "$${GATEWAY_LOG_FILE}" ]; then
		rm -f "$${GATEWAY_LOG_FILE}"
	fi

	if [ -n "$${GATEWAY_LOG_DIR:-}" ] && [ -d "$${GATEWAY_LOG_DIR}" ]; then
		rmdir "$${GATEWAY_LOG_DIR}" >/dev/null 2>&1 || true
	fi

	exit "$${status}"
}

trap 'cleanup 130' INT
trap 'cleanup 143' TERM
trap 'cleanup $$?' EXIT

export APP_PORT="$${APP_PORT:-8084}"
export RUST_LOG="$${DEV_RUST_LOG}"
RUST_SERVICE_STARTUP_TIMEOUT_SECONDS="$${RUST_SERVICE_STARTUP_TIMEOUT_SECONDS:-180}"
GATEWAY_STARTUP_TIMEOUT_SECONDS="$${GATEWAY_STARTUP_TIMEOUT_SECONDS:-$${RUST_SERVICE_STARTUP_TIMEOUT_SECONDS}}"
export AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE="$${AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE:-rust-authoritative}"

if dev_uses_postgres_database; then
	export DATABASE_URL="postgresql://$${DB_USER:-postgres}:$${DB_PASSWORD:-}@$${DB_HOST:-localhost}:$${DB_PORT:-5432}/$${DB_NAME:-aether}"
	if ! dotenv_has_key "AETHER_GATEWAY_DATA_POSTGRES_URL"; then
		export AETHER_GATEWAY_DATA_POSTGRES_URL="$${DATABASE_URL}"
	fi
fi

if dev_uses_redis_runtime; then
	export REDIS_URL="redis://:$${REDIS_PASSWORD:-}@$${REDIS_HOST:-localhost}:$${REDIS_PORT:-6379}/0"
	if ! dotenv_has_key "AETHER_GATEWAY_DATA_REDIS_URL"; then
		export AETHER_GATEWAY_DATA_REDIS_URL="$${REDIS_URL}"
	fi
else
	unset REDIS_URL
	unset AETHER_GATEWAY_DATA_REDIS_URL
fi

if ! dotenv_has_key "AETHER_GATEWAY_DATA_ENCRYPTION_KEY"; then
	export AETHER_GATEWAY_DATA_ENCRYPTION_KEY="$${ENCRYPTION_KEY:-}"
fi

export DB_POOL_SIZE="$${DB_POOL_SIZE:-5}"
export DB_MAX_OVERFLOW="$${DB_MAX_OVERFLOW:-5}"
export HTTP_MAX_CONNECTIONS="$${HTTP_MAX_CONNECTIONS:-20}"
export HTTP_KEEPALIVE_CONNECTIONS="$${HTTP_KEEPALIVE_CONNECTIONS:-5}"

if ! command -v cargo >/dev/null 2>&1; then
	echo "=> 未找到 cargo，无法启动 aether-gateway。请先安装 Rust toolchain。"
	exit 1
fi

if ! command -v curl >/dev/null 2>&1; then
	echo "=> 未找到 curl，无法检查 aether-gateway 健康状态。请先安装 curl。"
	exit 1
fi

if [ -z "$${RUSTC_WRAPPER:-}" ] && command -v sccache >/dev/null 2>&1; then
	export RUSTC_WRAPPER="$$(command -v sccache)"
	echo "=> 启用 Rust 编译缓存: $${RUSTC_WRAPPER}"
fi

if ! ensure_dev_infra; then
	exit 1
fi

echo "=> 编译 aether-gateway..."
cargo build -p aether-gateway --bin aether-gateway

GATEWAY_PID=""
GATEWAY_LOG_DIR=""
GATEWAY_LOG_FILE=""
STARTUP_WAIT_EARLY_EXIT=false
create_gateway_log_file

echo "=> 启动 aether-gateway (Rust frontdoor: 0.0.0.0:$${APP_PORT})..."
echo "=> 日志过滤: $${RUST_LOG}"
echo "=> 执行命令: target/debug/aether-gateway --app-port $${APP_PORT}"
target/debug/aether-gateway --app-port "$${APP_PORT}" > >(
	tee -a "$${GATEWAY_LOG_FILE}"
) 2>&1 &
GATEWAY_PID=$$!

if ! wait_for_startup "$${GATEWAY_PID}" "$${GATEWAY_STARTUP_TIMEOUT_SECONDS}" "aether-gateway" curl -sf "http://127.0.0.1:$${APP_PORT}/_gateway/health"; then
	if [ "$${STARTUP_WAIT_EARLY_EXIT}" = "true" ]; then
		GATEWAY_PID=""
	fi
	exit 1
fi

if wait "$${GATEWAY_PID}"; then
	gateway_exit_code=0
else
	gateway_exit_code=$$?
fi

GATEWAY_PID=""

if [ "$${gateway_exit_code}" -ne 130 ] && [ "$${gateway_exit_code}" -ne 143 ]; then
	echo "=> aether-gateway 运行失败并已退出，请检查上面的日志。"
	print_startup_failure_hint "$${GATEWAY_LOG_FILE}"
fi

exit "$${gateway_exit_code}"
endef
export DEV_BACKEND_SCRIPT

define DEV_SCRIPT
set -euo pipefail

backend_pid=""
frontend_pid=""

cleanup() {
	local status="$${1:-0}"
	trap - INT TERM EXIT

	if [ -n "$${backend_pid}" ] || [ -n "$${frontend_pid}" ]; then
		echo ""
		echo "=> 停止本地开发服务..."
		if [ -n "$${backend_pid}" ]; then
			kill "$${backend_pid}" >/dev/null 2>&1 || true
			wait "$${backend_pid}" >/dev/null 2>&1 || true
		fi
		if [ -n "$${frontend_pid}" ]; then
			kill "$${frontend_pid}" >/dev/null 2>&1 || true
			wait "$${frontend_pid}" >/dev/null 2>&1 || true
		fi
	fi

	exit "$${status}"
}

wait_for_backend_ready() {
	while :; do
		if curl -sf "http://127.0.0.1:$${APP_PORT}/_gateway/health" >/dev/null 2>&1; then
			return 0
		fi

		if ! kill -0 "$${backend_pid}" >/dev/null 2>&1; then
			if wait "$${backend_pid}"; then
				status=0
			else
				status=$$?
			fi
			if [ "$${status}" -ne 0 ]; then
				echo "=> 后端进程已退出 (status $${status})"
			else
				echo "=> 后端进程已退出"
			fi
			backend_pid=""
			cleanup "$${status}"
		fi

		sleep 0.2
	done
}

trap 'cleanup 130' INT
trap 'cleanup 143' TERM
trap 'cleanup $$?' EXIT

if [ -f .env ]; then
	set -a
	source .env
	set +a
fi
export APP_PORT="$${APP_PORT:-8084}"

echo "=> 启动后端: 先编译 aether-gateway，再运行 target/debug/aether-gateway --app-port $${APP_PORT:-8084}"
/bin/bash -euo pipefail -c "$$DEV_BACKEND_SCRIPT" &
backend_pid=$$!

echo "=> 等待后端健康检查: http://127.0.0.1:$${APP_PORT}/_gateway/health"
wait_for_backend_ready

echo "=> 启动前端: cd frontend && npm run dev"
( cd frontend && exec npm run dev ) &
frontend_pid=$$!

while :; do
	if ! kill -0 "$${backend_pid}" >/dev/null 2>&1; then
		if wait "$${backend_pid}"; then
			status=0
		else
			status=$$?
		fi
		if [ "$${status}" -ne 0 ]; then
			echo "=> 后端进程已退出 (status $${status})"
		else
			echo "=> 后端进程已退出"
		fi
		backend_pid=""
		cleanup "$${status}"
	fi

	if ! kill -0 "$${frontend_pid}" >/dev/null 2>&1; then
		if wait "$${frontend_pid}"; then
			status=0
		else
			status=$$?
		fi
		if [ "$${status}" -ne 0 ]; then
			echo "=> 前端进程已退出 (status $${status})"
		else
			echo "=> 前端进程已退出"
		fi
		frontend_pid=""
		cleanup "$${status}"
	fi

	sleep 1
done
endef
export DEV_SCRIPT

define DB_TASK_SCRIPT
set -euo pipefail

if [ -z "$${DB_TASK_COMMAND:-}" ] || [ -z "$${DB_TASK_LABEL:-}" ]; then
	echo "=> 内部错误: DB_TASK_COMMAND / DB_TASK_LABEL 未设置"
	exit 1
fi

read -r -a db_task_args <<< "$${DB_TASK_COMMAND}"
if [ "$${#db_task_args[@]}" -eq 0 ]; then
	echo "=> 内部错误: DB_TASK_COMMAND 为空"
	exit 1
fi

if [ ! -f .env ]; then
	echo "=> 未找到 .env，请先执行: cp .env.example .env"
	exit 1
fi

set -a
source .env
set +a

dotenv_has_key() {
	local key="$$1"
	grep -Eq "^[[:space:]]*$${key}=" .env
}

lowercase() {
	printf '%s' "$$1" | tr '[:upper:]' '[:lower:]'
}

uses_postgres_database() {
	local driver
	local url
	driver="$$(lowercase "$${AETHER_DATABASE_DRIVER:-}")"
	url="$${AETHER_DATABASE_URL:-$${DATABASE_URL:-}}"

	if [[ -z "$${driver}" && -z "$${url}" ]]; then
		return 0
	fi

	[[ "$${driver}" == "postgres" || "$${driver}" == "postgresql" || "$${url}" == postgres:* || "$${url}" == postgresql:* ]]
}

if uses_postgres_database; then
	export DATABASE_URL="postgresql://$${DB_USER:-postgres}:$${DB_PASSWORD:-}@$${DB_HOST:-localhost}:$${DB_PORT:-5432}/$${DB_NAME:-aether}"
	if ! dotenv_has_key "AETHER_GATEWAY_DATA_POSTGRES_URL"; then
		export AETHER_GATEWAY_DATA_POSTGRES_URL="$${DATABASE_URL}"
	fi
fi

if ! dotenv_has_key "AETHER_GATEWAY_DATA_ENCRYPTION_KEY"; then
	export AETHER_GATEWAY_DATA_ENCRYPTION_KEY="$${ENCRYPTION_KEY:-}"
fi

if ! command -v cargo >/dev/null 2>&1; then
	echo "=> 未找到 cargo，无法执行 $${DB_TASK_LABEL}。请先安装 Rust toolchain。"
	exit 1
fi

echo "=> 执行 $${DB_TASK_LABEL}: cargo run -p aether-gateway --bin aether-gateway -- $${db_task_args[*]}"
exec cargo run -p aether-gateway --bin aether-gateway -- "$${db_task_args[@]}"
endef
export DB_TASK_SCRIPT

dev:
	@$(SHELL) -euo pipefail -c "$$DEV_SCRIPT"

dev-backend:
	@$(SHELL) -euo pipefail -c "$$DEV_BACKEND_SCRIPT"

dev-frontend:
	@cd frontend && npm run dev

db-status:
	@DB_TASK_COMMAND="db status" DB_TASK_LABEL="数据库状态检查" $(SHELL) -euo pipefail -c "$$DB_TASK_SCRIPT"

db-prepare:
	@DB_TASK_COMMAND="db prepare" DB_TASK_LABEL="数据库准备" $(SHELL) -euo pipefail -c "$$DB_TASK_SCRIPT"

migration:
	@DB_TASK_COMMAND="--migrate" DB_TASK_LABEL="数据库迁移" $(SHELL) -euo pipefail -c "$$DB_TASK_SCRIPT"

backfill:
	@DB_TASK_COMMAND="--apply-backfills" DB_TASK_LABEL="数据库 backfill" $(SHELL) -euo pipefail -c "$$DB_TASK_SCRIPT"
