#!/usr/bin/env bash
# #region build [C:2] [TYPE Module]
# @PURPOSE: Unified build script — local docker compose + release bundles + lightweight bundle
# @COMPLEXITY: 2
#
# Usage: ./build.sh <command> [options]
#
# Commands:
#   up [profile]           Build and start local docker compose (default: current)
#   down [profile]         Stop services
#   restart [profile]      Rebuild and restart services
#   logs [profile]         Tail logs
#   status                 Show running containers for all profiles
#   bundle <tag>           Full release bundle (backend + frontend + postgres .tar.xz's)
#   bundle:light <tag>     Lightweight all-in-one bundle (<200 MB, .tar.xz, no Playwright)
#   help                   Show this help
#
# Profiles: current (default), master, enterprise-clean
# #endregion build

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

BACKEND_ENV_FILE="$SCRIPT_DIR/backend/.env"
DIST_ROOT="$SCRIPT_DIR/dist/docker"

# ---- Docker Compose detection ----
if docker compose version >/dev/null 2>&1; then
  COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
  COMPOSE_CMD=(docker-compose)
else
  echo "Error: docker compose is not available."
  exit 1
fi

# ======================================================================
#  HELPERS
# ======================================================================

# shellcheck disable=SC2206
setup_compose_profile() {
  local profile="$1"
  case "$profile" in
    master)
      PROFILE_ENV_FILE="$SCRIPT_DIR/.env.master"
      PROJECT_NAME="ss-tools-master"
      COMPOSE_FILE="docker-compose.yml"
      ;;
    current|"")
      PROFILE_ENV_FILE="$SCRIPT_DIR/.env.current"
      PROJECT_NAME="ss-tools-current"
      COMPOSE_FILE="docker-compose.yml"
      ;;
    enterprise-clean)
      PROFILE_ENV_FILE="$SCRIPT_DIR/.env.enterprise-clean"
      PROJECT_NAME="ss-tools-enterprise"
      COMPOSE_FILE="docker-compose.enterprise-clean.yml"
      ;;
    *)
      echo "Error: unknown profile '$profile'. Use: current, master, enterprise-clean."
      exit 1
      ;;
  esac
  COMPOSE_ARGS=(-p "$PROJECT_NAME" -f "$COMPOSE_FILE")
  if [[ -f "$PROFILE_ENV_FILE" ]]; then
    COMPOSE_ARGS+=(--env-file "$PROFILE_ENV_FILE")
  fi
}

ensure_backend_encryption_key() {
  if command -v python3 >/dev/null 2>&1; then
    python3 - "$BACKEND_ENV_FILE" <<'PY'
import base64
import os
import sys
from pathlib import Path
from typing import List, Optional


def is_valid_fernet_key(raw_value: str) -> bool:
    value = raw_value.strip()
    if not value:
        return False
    try:
        decoded = base64.urlsafe_b64decode(value.encode())
    except Exception:
        return False
    return len(decoded) == 32


def generate_fernet_key() -> str:
    return base64.urlsafe_b64encode(os.urandom(32)).decode()


env_path = Path(sys.argv[1])
env_path.parent.mkdir(parents=True, exist_ok=True)

existing_lines: List[str] = []
existing_key: Optional[str] = None

if env_path.exists():
    existing_lines = env_path.read_text(encoding="utf-8").splitlines()
    for line in existing_lines:
        if line.startswith("ENCRYPTION_KEY="):
            candidate = line.partition("=")[2].strip()
            if is_valid_fernet_key(candidate):
                existing_key = candidate
            break

if existing_key is None:
    generated_key = generate_fernet_key()
    filtered_lines = [line for line in existing_lines if not line.startswith("ENCRYPTION_KEY=")]
    filtered_lines.append(f"ENCRYPTION_KEY={generated_key}")
    env_path.write_text("\n".join(filtered_lines) + "\n", encoding="utf-8")
    print(f"[build] ENCRYPTION_KEY ensured in {env_path}")
else:
    print(f"[build] Existing ENCRYPTION_KEY reused from {env_path}")
PY
    return
  fi

  echo "Error: python3 is required to generate backend/.env with ENCRYPTION_KEY."
  exit 1
}

get_image_id() {
  docker image inspect --format '{{.Id}}' "$1"
}

get_repo_digest() {
  local digest
  digest="$(docker image inspect --format '{{join .RepoDigests ","}}' "$1" 2>/dev/null || true)"
  if [[ -n "${digest}" && "${digest}" != "<no value>" ]]; then
    printf '%s' "${digest}"
  else
    printf '%s' "unavailable"
  fi
}

# ======================================================================
#  COMPOSE COMMANDS
# ======================================================================

compose_up() {
  local profile="${1:-current}"
  setup_compose_profile "$profile"
  ensure_backend_encryption_key

  echo "[build] Profile: $profile (project: $PROJECT_NAME)"
  if [[ -f "$PROFILE_ENV_FILE" ]]; then
    echo "[build] Env file: $PROFILE_ENV_FILE"
  fi

  echo "[1/2] Building project images..."
  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" build

  echo "[2/2] Starting Docker services..."
  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" up -d

  echo "Done. Services are running."
  echo "  ${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} ps"
  echo "  ${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} logs -f"
}

compose_down() {
  local profile="${1:-current}"
  setup_compose_profile "$profile"

  echo "[build] Stopping $profile (project: $PROJECT_NAME)..."
  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" down
  echo "[build] Done."
}

compose_restart() {
  local profile="${1:-current}"
  setup_compose_profile "$profile"
  ensure_backend_encryption_key

  echo "[build] Restarting $profile (project: $PROJECT_NAME)..."
  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" build
  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" up -d --force-recreate
  echo "[build] Done."
}

compose_logs() {
  local profile="${1:-current}"
  setup_compose_profile "$profile"

  "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" logs -f
}

compose_status() {
  echo "=== ss-tools docker status ==="
  for profile in current master enterprise-clean; do
    setup_compose_profile "$profile"
    echo "--- $profile (project: $PROJECT_NAME) ---"
    "${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" ps 2>/dev/null || echo "(not running)"
  done
}

# ======================================================================
#  BUNDLE: full release (backend + frontend + postgres .tar's)
# ======================================================================

bundle_release() {
  if [[ $# -lt 1 ]]; then
    echo "Error: tag is required."
    echo "Usage: ./build.sh bundle <tag> (e.g. v1.0.0)"
    exit 1
  fi
  local tag="$1"
  local backend_tag="ss-tools-backend:${tag}"
  local frontend_tag="ss-tools-frontend:${tag}"

  mkdir -p "$DIST_ROOT"

  echo "[bundle] Building backend image ${backend_tag}..."
  docker build -f docker/backend.Dockerfile -t "${backend_tag}" .

  echo "[bundle] Building frontend image ${frontend_tag}..."
  docker build -f docker/frontend.Dockerfile -t "${frontend_tag}" .

  echo "[bundle] Exporting .tar.xz archives..."
  echo "[bundle] Compressing backend image..."
  docker save "${backend_tag}" | xz -T0 -6 > "${DIST_ROOT}/ss-tools-backend.${tag}.tar.xz"
  echo "[bundle] Compressing frontend image..."
  docker save "${frontend_tag}" | xz -T0 -6 > "${DIST_ROOT}/ss-tools-frontend.${tag}.tar.xz"

  echo "[bundle] Calculating checksums..."
  (
    cd "${DIST_ROOT}"
    sha256sum "ss-tools-backend.${tag}.tar.xz" "ss-tools-frontend.${tag}.tar.xz" > "sha256sums.${tag}.txt"
  )

  local backend_id frontend_id
  local backend_digest frontend_digest
  backend_id="$(get_image_id "${backend_tag}")"
  frontend_id="$(get_image_id "${frontend_tag}")"
  backend_digest="$(get_repo_digest "${backend_tag}")"
  frontend_digest="$(get_repo_digest "${frontend_tag}")"

  # Генерируем deploy-compose для загрузки pre-built образов (image: + pull_policy: never)
  # Без postgres — используется внешний корпоративный PostgreSQL.
  cat > "${DIST_ROOT}/docker-compose.enterprise-clean.yml" <<DEPLOY
services:
  backend:
    image: ${backend_tag}
    pull_policy: never
    restart: unless-stopped
    environment:
      DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@\${POSTGRES_HOST:?Set POSTGRES_HOST}:\${POSTGRES_PORT:-5432}/\${POSTGRES_DB:-ss_tools}
      TASKS_DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@\${POSTGRES_HOST:?Set POSTGRES_HOST}:\${POSTGRES_PORT:-5432}/\${POSTGRES_DB:-ss_tools}
      AUTH_DATABASE_URL: postgresql+psycopg2://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@\${POSTGRES_HOST:?Set POSTGRES_HOST}:\${POSTGRES_PORT:-5432}/\${POSTGRES_DB:-ss_tools}
      BACKEND_PORT: 8000
      ENABLE_BELIEF_STATE_LOGGING: \${ENABLE_BELIEF_STATE_LOGGING:-true}
      TASK_LOG_LEVEL: \${TASK_LOG_LEVEL:-INFO}
      INITIAL_ADMIN_CREATE: \${INITIAL_ADMIN_CREATE:-false}
      INITIAL_ADMIN_USERNAME: \${INITIAL_ADMIN_USERNAME:-admin}
      INITIAL_ADMIN_PASSWORD: \${INITIAL_ADMIN_PASSWORD:-}
      INITIAL_ADMIN_EMAIL: \${INITIAL_ADMIN_EMAIL:-}
      OPENAI_API_KEY: \${OPENAI_API_KEY:-}
      ANTHROPIC_API_KEY: \${ANTHROPIC_API_KEY:-}
      FEATURES__DATASET_REVIEW: \${FEATURES__DATASET_REVIEW:-true}
      FEATURES__HEALTH_MONITOR: \${FEATURES__HEALTH_MONITOR:-true}
      CERTS_PATH: /opt/certs
    ports:
      - "\${BACKEND_HOST_PORT:-8001}:8000"
    volumes:
      - ./storage:/app/storage
      - \${CERTS_PATH:-./certs}:/opt/certs:ro

  frontend:
    image: ${frontend_tag}
    pull_policy: never
    restart: unless-stopped
    depends_on:
      - backend
    ports:
      - "\${FRONTEND_HOST_PORT:-8000}:80"
      - "\${FRONTEND_SSL_PORT:-443}:443"
    volumes:
      - \${CERTS_PATH:-./certs}:/opt/certs:ro
DEPLOY

  cat > "${DIST_ROOT}/manifest.${tag}.txt" <<EOF
release_tag=${tag}
backend_image=${backend_tag}
backend_image_id=${backend_id}
backend_repo_digest=${backend_digest}
backend_archive=ss-tools-backend.${tag}.tar.xz
frontend_image=${frontend_tag}
frontend_image_id=${frontend_id}
frontend_repo_digest=${frontend_digest}
frontend_archive=ss-tools-frontend.${tag}.tar.xz
compose_file=docker-compose.enterprise-clean.yml
env_template=.env.enterprise-clean.example
env_bootstrap_fields=INITIAL_ADMIN_CREATE,INITIAL_ADMIN_USERNAME,INITIAL_ADMIN_PASSWORD,INITIAL_ADMIN_EMAIL
checksums_file=sha256sums.${tag}.txt
generated_at_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
EOF

  cat > "${DIST_ROOT}/manifest.${tag}.json" <<EOF
{
  "release_tag": "${tag}",
  "generated_at_utc": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
  "images": [
    {
      "role": "backend",
      "image": "${backend_tag}",
      "image_id": "${backend_id}",
      "repo_digest": "${backend_digest}",
      "archive": "ss-tools-backend.${tag}.tar.xz"
    },
    {
      "role": "frontend",
      "image": "${frontend_tag}",
      "image_id": "${frontend_id}",
      "repo_digest": "${frontend_digest}",
      "archive": "ss-tools-frontend.${tag}.tar.xz"
    }
  ],
  "compose_file": "docker-compose.enterprise-clean.yml",
  "env_template": ".env.enterprise-clean.example",
  "env_bootstrap_fields": [
    "INITIAL_ADMIN_CREATE",
    "INITIAL_ADMIN_USERNAME",
    "INITIAL_ADMIN_PASSWORD",
    "INITIAL_ADMIN_EMAIL"
  ],
  "checksums_file": "sha256sums.${tag}.txt"
}
EOF

  cp "${SCRIPT_DIR}/.env.enterprise-clean.example" "${DIST_ROOT}/.env.enterprise-clean.example"

  echo "[bundle] ✅ Bundle created in ${DIST_ROOT}"
  echo "  # Load images (decompress stream):"
  echo "  xz -dc ${DIST_ROOT}/ss-tools-backend.${tag}.tar.xz | docker load"
  echo "  xz -dc ${DIST_ROOT}/ss-tools-frontend.${tag}.tar.xz | docker load"
  echo "  # Edit .env.enterprise-clean.example -> .env.enterprise-clean, then:"
  echo "  docker compose -f ${DIST_ROOT}/docker-compose.enterprise-clean.yml up"
  echo ""
  echo "  NOTE: '${tag}' bundle uses external PostgreSQL (set POSTGRES_HOST)."
  echo "  Corporate certs: mount .crt files via CERTS_PATH=./certs (optional)."
}

# ======================================================================
#  BUNDLE: lightweight all-in-one (single image, no Playwright, <200 MB)
# ======================================================================

bundle_light() {
  if [[ $# -lt 1 ]]; then
    echo "Error: tag is required."
    echo "Usage: ./build.sh bundle:light <tag> (e.g. v1.0.0)"
    exit 1
  fi
  local tag="$1"
  local image_tag="ss-tools:${tag}"

  mkdir -p "$DIST_ROOT"

  echo "[bundle:light] Building all-in-one image ${image_tag}..."
  docker build -f docker/all-in-one.Dockerfile -t "${image_tag}" .

  echo "[bundle:light] Exporting .tar.xz..."
  docker save "${image_tag}" | xz -T0 -6 > "${DIST_ROOT}/ss-tools.${tag}.tar.xz"

  echo "[bundle:light] Calculating checksums..."
  (
    cd "${DIST_ROOT}"
    sha256sum "ss-tools.${tag}.tar.xz" > "sha256sums-light.${tag}.txt"
  )

  local img_id repo_digest
  img_id="$(get_image_id "${image_tag}")"
  repo_digest="$(get_repo_digest "${image_tag}")"

  cat > "${DIST_ROOT}/manifest-light.${tag}.txt" <<EOF
release_tag=${tag}
image=${image_tag}
image_id=${img_id}
repo_digest=${repo_digest}
archive=ss-tools.${tag}.tar.xz
compose_file=docker-compose.light.yml
checksums_file=sha256sums-light.${tag}.txt
generated_at_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
EOF

  cat > "${DIST_ROOT}/manifest-light.${tag}.json" <<EOF
{
  "release_tag": "${tag}",
  "generated_at_utc": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
  "image": "${image_tag}",
  "image_id": "${img_id}",
  "repo_digest": "${repo_digest}",
  "archive": "ss-tools.${tag}.tar.xz",
  "compose_file": "docker-compose.light.yml",
  "checksums_file": "sha256sums-light.${tag}.txt"
}
EOF

  cat > "${DIST_ROOT}/docker-compose.light.yml" <<'COMPOSE'
services:
  db:
    image: postgres:16-alpine
    pull_policy: never
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-ss_tools}
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
    ports:
      - "${POSTGRES_HOST_PORT:-5432}:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-ss_tools}"]
      interval: 10s
      timeout: 5s
      retries: 10

  app:
    image: ${IMAGE:-ss-tools:latest}
    pull_policy: never
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql+psycopg2://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-ss_tools}
      BACKEND_PORT: 8000
      INITIAL_ADMIN_CREATE: ${INITIAL_ADMIN_CREATE:-false}
      INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
      INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
      INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-}
      OPENAI_API_KEY: ${OPENAI_API_KEY:-}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
      FEATURES__DATASET_REVIEW: ${FEATURES__DATASET_REVIEW:-true}
      FEATURES__HEALTH_MONITOR: ${FEATURES__HEALTH_MONITOR:-true}
    ports:
      - "${APP_HOST_PORT:-8000}:8000"
    volumes:
      - ./storage:/app/storage

volumes:
  postgres_data:
COMPOSE

  cp "${SCRIPT_DIR}/.env.enterprise-clean.example" "${DIST_ROOT}/.env.light.example"

  echo "[bundle:light] ✅ Lightweight bundle created in ${DIST_ROOT}"
  echo "  # Load image:"
  echo "  xz -dc ${DIST_ROOT}/ss-tools.${tag}.tar.xz | docker load"
  echo "  docker compose -f ${DIST_ROOT}/docker-compose.light.yml up"
  echo ""
  echo "  Single container (without compose):"
  echo "  docker run -p 8000:8000 --env-file backend/.env ${image_tag}"
}

# ======================================================================
#  HELP
# ======================================================================

show_help() {
  cat <<'HELP'
Usage: ./build.sh <command> [options]

Commands for local docker:
  up [profile]           Build and start services (default: current)
  down [profile]         Stop and remove containers
  restart [profile]      Rebuild and restart services
  logs [profile]         Tail logs from all services
  status                 Show running containers for all profiles

Commands for release bundles:
  bundle <tag>           Build release images (backend + frontend .tar.xz's).
                         NOTE: external PostgreSQL only — no bundled postgres image.
                         Example: ./build.sh bundle v1.0.0

  bundle:light <tag>     Lightweight all-in-one image (.tar.xz, <200 MB, no Playwright)
                         Example: ./build.sh bundle:light v1.0.0

Profiles: current (default), master, enterprise-clean

Backward-compatible shortcuts:
  ./build.sh                  ->  ./build.sh up current
  ./build.sh master           ->  ./build.sh up master
  ./build.sh enterprise-clean ->  ./build.sh up enterprise-clean

Output:
  Local docker:     docker compose -p ss-tools-{profile}
  Bundle artifacts: dist/docker/

Notes:
  - 'current' and 'master' use docker-compose.yml (dev, with local postgres).
  - 'enterprise-clean' uses docker-compose.enterprise-clean.yml (build from source,
    external postgres, corporate certs, optional nginx SSL).
HELP
}

# ======================================================================
#  MAIN DISPATCH
# ======================================================================

main() {
  local CMD="${1:-up}"
  shift 2>/dev/null || true

  case "$CMD" in
    up|down|restart|logs|bundle|bundle:light|status|help|-h|--help)
      # Valid commands — proceed
      ;;
    *)
      # Backward compat: treat unknown arg as profile for "up"
      local profile="$CMD"
      CMD="up"
      set -- "$profile" "$@"
      ;;
  esac

  case "$CMD" in
    up)            compose_up "${1:-current}" ;;
    down)          compose_down "${1:-current}" ;;
    restart)       compose_restart "${1:-current}" ;;
    logs)          compose_logs "${1:-current}" ;;
    bundle)        bundle_release "$@" ;;
    bundle:light)  bundle_light "$@" ;;
    status)        compose_status ;;
    help|-h|--help) show_help ;;
    *)
      echo "Error: unknown command '$CMD'. See ./build.sh help"
      exit 1
      ;;
  esac
}

main "$@"
