#!/usr/bin/env bash

set -Eeuo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="${ROOT_DIR}/backend"
FRONTEND_DIR="${ROOT_DIR}/frontend"
BACKEND_PORT=17861
FRONTEND_PORT=16801
BACKEND_PYTHON=""
BACKEND_PID=""
FRONTEND_PID=""
SHUTTING_DOWN=0
CHECK_ONLY=0
DATABASE_URL_SOURCE=""

info() {
  printf '[启动] %s\n' "$*"
}

warn() {
  printf '[警告] %s\n' "$*" >&2
}

fail() {
  printf '[错误] %s\n' "$*" >&2
  exit 1
}

usage() {
  cat <<'EOF'
用法：bash ./start.sh [--check] [--help]

  --check  仅检查运行环境和端口，不启动服务
  --help   显示帮助

脚本会继承当前终端的环境变量，例如：
  DATABASE_HOST='db.example.com' bash ./start.sh
EOF
}

parse_args() {
  if [ "$#" -gt 1 ]; then
    usage >&2
    exit 2
  fi

  case "${1:-}" in
    "") ;;
    --check) CHECK_ONLY=1 ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      usage >&2
      exit 2
      ;;
  esac
}

select_backend_python() {
  if [ -x "${BACKEND_DIR}/.venv/bin/python" ]; then
    BACKEND_PYTHON="${BACKEND_DIR}/.venv/bin/python"
  elif command -v python3 >/dev/null 2>&1; then
    BACKEND_PYTHON="$(command -v python3)"
    warn "未找到 backend/.venv，将使用 ${BACKEND_PYTHON}。"
  else
    fail "未找到 Python 3。请先创建 backend/.venv 并安装后端依赖。"
  fi
}

load_database_url() {
  local env_file="${ROOT_DIR}/docker/app/.env"
  local value=""

  if ! value="$(DATABASE_CONFIG_FILE="${env_file}" "${BACKEND_PYTHON}" -c '
import ipaddress
import os
from pathlib import Path
from urllib.parse import quote


def read_env(path: str) -> dict[str, str]:
    values: dict[str, str] = {}
    env_path = Path(path)
    if not env_path.is_file():
        return values
    for raw_line in env_path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in {"\x27", "\""}:
            value = value[1:-1]
        values[key.strip()] = value
    return values


file_values = read_env(os.environ["DATABASE_CONFIG_FILE"])


def setting(*names: str, default: str = "") -> str:
    for name in names:
        env_value = os.environ.get(name, "").strip()
        if env_value:
            return env_value
    for name in names:
        file_value = file_values.get(name, "").strip()
        if file_value:
            return file_value
    return default


shell_url = os.environ.get("DATABASE_URL", "").strip()
shell_host = os.environ.get("DATABASE_HOST", "").strip()
file_host = file_values.get("DATABASE_HOST", "").strip()
file_url = file_values.get("DATABASE_URL", "").strip()

candidate = shell_url or shell_host or file_host or file_url
if not candidate:
    raise SystemExit(0)

if "://" in candidate:
    print(candidate)
    raise SystemExit(0)

host = candidate.strip().rstrip("/")
if not host or any(character in host for character in "/@"):
    raise SystemExit(1)

port_text = setting("DATABASE_PORT", default="5432")
try:
    port = int(port_text)
except ValueError:
    raise SystemExit(1)
if not 1 <= port <= 65535:
    raise SystemExit(1)

username = setting("DATABASE_USER", "POSTGRES_USER", default="yg_ft")
password = setting("DATABASE_PASSWORD", "POSTGRES_PASSWORD", default="change_me")
database = setting("DATABASE_NAME", "POSTGRES_DB", default="yg_ft")
if not username or not database:
    raise SystemExit(1)

try:
    parsed_host = ipaddress.ip_address(host)
    url_host = f"[{host}]" if parsed_host.version == 6 else host
except ValueError:
    url_host = host

credentials = quote(username, safe="")
if password:
    credentials += ":" + quote(password, safe="")
encoded_database = quote(database, safe="")
print(f"postgresql+psycopg://{credentials}@{url_host}:{port}/{encoded_database}")
' 2>/dev/null)"; then
    fail "数据库分项配置无效，请检查 DATABASE_HOST 和 DATABASE_PORT。"
  fi

  if [ -z "${value}" ]; then
    return
  fi

  if [ -n "${DATABASE_URL:-}" ] || [ -n "${DATABASE_HOST:-}" ]; then
    DATABASE_URL_SOURCE="当前终端环境变量"
  elif [ -f "${env_file}" ] && grep -Eq '^[[:space:]]*DATABASE_HOST=' "${env_file}"; then
    DATABASE_URL_SOURCE="docker/app/.env 的 DATABASE_HOST"
  else
    DATABASE_URL_SOURCE="docker/app/.env 的 DATABASE_URL"
  fi
  export DATABASE_URL="${value}"
}

validate_database_url() {
  if [ -z "${DATABASE_URL:-}" ]; then
    return
  fi

  case "${DATABASE_URL}" in
    *"@postgres:"*|*"//postgres:"*)
      fail "DATABASE_URL 使用了 Docker 服务名 postgres；本地启动请填写真实数据库主机名或 IP。"
      ;;
  esac

  if ! "${BACKEND_PYTHON}" -c '
import os
from urllib.parse import urlsplit

try:
    parsed = urlsplit(os.environ["DATABASE_URL"])
    port = parsed.port or 5432
except (KeyError, ValueError):
    raise SystemExit(1)

valid = (
    parsed.scheme in {"postgresql", "postgresql+psycopg"}
    and bool(parsed.hostname)
    and bool(parsed.path.strip("/"))
    and 1 <= port <= 65535
)
raise SystemExit(0 if valid else 1)
'; then
    fail "${DATABASE_URL_SOURCE:-DATABASE_URL} 中的数据库连接串格式无效。格式应为：postgresql+psycopg://用户名:密码@主机:端口/数据库名"
  fi

  info "已使用 ${DATABASE_URL_SOURCE:-DATABASE_URL} 中的数据库连接配置（敏感信息已隐藏）。"
}

check_backend_dependencies() {
  if ! "${BACKEND_PYTHON}" -c \
    'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)'; then
    fail "后端要求 Python >= 3.12，当前解释器为 $(${BACKEND_PYTHON} --version 2>&1)。"
  fi

  if ! "${BACKEND_PYTHON}" -c \
    'import alembic, dotenv, fastapi, httpx, jwt, multipart, passlib, psycopg, pydantic, redis, sqlalchemy, uvicorn' \
    >/dev/null 2>&1; then
    fail "后端依赖不完整。请执行：${BACKEND_PYTHON} -m pip install -r backend/requirements.txt"
  fi
}

check_frontend_dependencies() {
  command -v node >/dev/null 2>&1 || fail "未找到 Node.js。"
  command -v npm >/dev/null 2>&1 || fail "未找到 npm。"

  if [ ! -x "${FRONTEND_DIR}/node_modules/.bin/vite" ]; then
    fail "前端依赖未安装。请执行：cd frontend && npm ci"
  fi

  if ! npm --prefix "${FRONTEND_DIR}" ls --depth=0 >/dev/null 2>&1; then
    fail "前端依赖状态异常。请执行：cd frontend && npm ci"
  fi
}

check_port() {
  local port="$1"
  local service_name="$2"

  if command -v lsof >/dev/null 2>&1 && \
    lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1; then
    fail "${service_name}端口 ${port} 已被占用，请先停止占用该端口的进程。"
  fi
}

warn_if_default_database_is_unavailable() {
  if [ -n "${DATABASE_URL:-}" ] || ! command -v lsof >/dev/null 2>&1; then
    return
  fi

  if ! lsof -nP -iTCP:15432 -sTCP:LISTEN >/dev/null 2>&1; then
    warn "未检测到默认 PostgreSQL 端口 15432；后端可启动，但健康检查和业务接口可能失败。"
  fi
}

preflight() {
  [ -d "${BACKEND_DIR}" ] || fail "缺少后端目录：${BACKEND_DIR}"
  [ -d "${FRONTEND_DIR}" ] || fail "缺少前端目录：${FRONTEND_DIR}"
  [ -f "${FRONTEND_DIR}/package-lock.json" ] || fail "缺少 frontend/package-lock.json"

  select_backend_python
  load_database_url
  validate_database_url
  check_backend_dependencies
  check_frontend_dependencies
  check_port "${BACKEND_PORT}" "后端"
  check_port "${FRONTEND_PORT}" "前端"
  warn_if_default_database_is_unavailable

  info "环境检查通过：$(${BACKEND_PYTHON} --version 2>&1)，Node $(node --version)。"
}

terminate_process_tree() {
  local pid="$1"
  local child=""

  if [ -z "${pid}" ] || ! kill -0 "${pid}" 2>/dev/null; then
    return
  fi

  if command -v pgrep >/dev/null 2>&1; then
    for child in $(pgrep -P "${pid}" 2>/dev/null || true); do
      terminate_process_tree "${child}"
    done
  fi

  kill -TERM "${pid}" 2>/dev/null || true
}

cleanup() {
  local exit_code="${1:-0}"

  if [ "${SHUTTING_DOWN}" -eq 1 ]; then
    return
  fi
  SHUTTING_DOWN=1
  trap - EXIT INT TERM

  if [ -n "${BACKEND_PID}" ] || [ -n "${FRONTEND_PID}" ]; then
    info "正在停止前端和后端服务……"
  fi
  terminate_process_tree "${FRONTEND_PID}"
  terminate_process_tree "${BACKEND_PID}"
  [ -z "${FRONTEND_PID}" ] || wait "${FRONTEND_PID}" 2>/dev/null || true
  [ -z "${BACKEND_PID}" ] || wait "${BACKEND_PID}" 2>/dev/null || true

  exit "${exit_code}"
}

start_backend() {
  info "启动后端：http://127.0.0.1:${BACKEND_PORT}/modelTF/health"
  (
    cd "${BACKEND_DIR}"
    exec "${BACKEND_PYTHON}" -m uvicorn app.main:app \
      --reload \
      --host 127.0.0.1 \
      --port "${BACKEND_PORT}"
  ) &
  BACKEND_PID=$!
}

start_frontend() {
  info "启动前端：http://localhost:${FRONTEND_PORT}"
  (
    cd "${FRONTEND_DIR}"
    exec npm run dev -- --strictPort
  ) &
  FRONTEND_PID=$!
}

wait_for_services() {
  local exit_code=0

  while :; do
    if ! kill -0 "${BACKEND_PID}" 2>/dev/null; then
      if wait "${BACKEND_PID}"; then
        exit_code=0
      else
        exit_code=$?
      fi
      warn "后端服务已退出（状态码 ${exit_code}）。"
      [ "${exit_code}" -ne 0 ] || exit_code=1
      return "${exit_code}"
    fi

    if ! kill -0 "${FRONTEND_PID}" 2>/dev/null; then
      if wait "${FRONTEND_PID}"; then
        exit_code=0
      else
        exit_code=$?
      fi
      warn "前端服务已退出（状态码 ${exit_code}）。"
      [ "${exit_code}" -ne 0 ] || exit_code=1
      return "${exit_code}"
    fi

    sleep 1
  done
}

main() {
  parse_args "$@"
  preflight

  if [ "${CHECK_ONLY}" -eq 1 ]; then
    info "前后端均可启动。"
    return
  fi

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

  start_backend
  start_frontend
  info "前后端已启动，按 Ctrl+C 同时停止。"
  wait_for_services
}

main "$@"
