#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PLATFORM_REPO="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
NAS_ROOT="${NAS_ROOT:-/Volumes/docker/nodedc-platform}"
DEPLOY_ROOT="${DEPLOY_ROOT:-$(cd -- "${NAS_ROOT}/.." && pwd)/nodedc-deploy}"
LAUNCHER_REPO="${LAUNCHER_REPO:-}"
TASKER_REPO="${TASKER_REPO:-}"
TASKER_SYNC_SOURCE="${TASKER_SYNC_SOURCE:-0}"
TASKER_CHANGED_BASE="${TASKER_CHANGED_BASE:-}"
GATEWAY_REPO="${GATEWAY_REPO:-}"
BIM_REPO="${BIM_REPO:-}"
SYNC_AUTHENTIK_TEMPLATES="${SYNC_AUTHENTIK_TEMPLATES:-0}"

RSYNC_METADATA_ARGS=()
if [[ "${RSYNC_PRESERVE_METADATA:-0}" != "1" ]]; then
  RSYNC_METADATA_ARGS=(--no-times --no-perms --no-owner --no-group)
fi

if [[ ! -d "${NAS_ROOT}" ]]; then
  echo "NAS_ROOT not found: ${NAS_ROOT}" >&2
  echo "Set NAS_ROOT=/path/to/nodedc-platform when the Synology share is mounted elsewhere." >&2
  exit 1
fi

mkdir -p "${NAS_ROOT}/platform"

rsync -av "${RSYNC_METADATA_ARGS[@]}" \
  "${PLATFORM_REPO}/infra/synology/.env.synology.example" \
  "${NAS_ROOT}/platform/.env.synology.example"

rsync -av "${RSYNC_METADATA_ARGS[@]}" \
  "${PLATFORM_REPO}/infra/synology/docker-compose.platform-http.yml" \
  "${NAS_ROOT}/platform/docker-compose.platform-http.yml"

rsync -av "${RSYNC_METADATA_ARGS[@]}" \
  "${PLATFORM_REPO}/infra/synology/Caddyfile.http" \
  "${NAS_ROOT}/platform/Caddyfile.http"

rsync -av "${RSYNC_METADATA_ARGS[@]}" \
  "${PLATFORM_REPO}/infra/synology/deploy-current.sh" \
  "${PLATFORM_REPO}/infra/synology/backup-current.sh" \
  "${PLATFORM_REPO}/infra/synology/apply-current-runtime.sh" \
  "${PLATFORM_REPO}/infra/synology/verify-current-runtime.sh" \
  "${PLATFORM_REPO}/infra/synology/clear-authentik-brand-css.sh" \
  "${NAS_ROOT}/platform/"

mkdir -p "${DEPLOY_ROOT}/runner-install"
rsync -av "${RSYNC_METADATA_ARGS[@]}" \
  "${PLATFORM_REPO}/infra/deploy-runner/nodedc-deploy" \
  "${DEPLOY_ROOT}/runner-install/nodedc-deploy"
chmod 0755 "${DEPLOY_ROOT}/runner-install/nodedc-deploy"

mkdir -p "${NAS_ROOT}/platform/notification-core"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/notification-core/" \
  "${NAS_ROOT}/platform/notification-core/"

mkdir -p "${NAS_ROOT}/platform/ai-workspace-hub"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/ai-workspace-hub/" \
  "${NAS_ROOT}/platform/ai-workspace-hub/"

mkdir -p "${NAS_ROOT}/platform/ai-workspace-assistant"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/ai-workspace-assistant/" \
  "${NAS_ROOT}/platform/ai-workspace-assistant/"

mkdir -p "${NAS_ROOT}/platform/ai-workspace-assistant/ontology-core"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/ontology-core/" \
  "${NAS_ROOT}/platform/ai-workspace-assistant/ontology-core/"

mkdir -p "${NAS_ROOT}/platform/ontology-core"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/ontology-core/" \
  "${NAS_ROOT}/platform/ontology-core/"

mkdir -p "${NAS_ROOT}/platform/gelios-gateway"
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
  --exclude='node_modules/' \
  --exclude='.env' \
  --exclude='.env.*' \
  "${PLATFORM_REPO}/services/gelios-gateway/" \
  "${NAS_ROOT}/platform/gelios-gateway/"

if [[ "${SYNC_AUTHENTIK_TEMPLATES}" == "1" ]]; then
  mkdir -p "${NAS_ROOT}/authentik/custom-templates"
  rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
    "${PLATFORM_REPO}/infra/authentik/custom-templates/" \
    "${NAS_ROOT}/authentik/custom-templates/"
else
  echo "SYNC_AUTHENTIK_TEMPLATES=0; Authentik custom templates were not synced."
fi

if [[ -n "${LAUNCHER_REPO}" ]]; then
  if [[ ! -d "${LAUNCHER_REPO}" ]]; then
    echo "LAUNCHER_REPO not found: ${LAUNCHER_REPO}" >&2
    exit 1
  fi

  mkdir -p "${NAS_ROOT}/launcher/source"
  rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
    --exclude='.git/' \
    --exclude='.DS_Store' \
    --exclude='node_modules/' \
    --exclude='dist/' \
    --exclude='server/storage/*' \
    --exclude='public/storage/uploads/*' \
    "${LAUNCHER_REPO}/" \
    "${NAS_ROOT}/launcher/source/"
else
  echo "LAUNCHER_REPO is not set; launcher source was not synced."
fi

if [[ -n "${BIM_REPO}" ]]; then
  if [[ ! -f "${BIM_REPO}/docker-compose.beam.yml" || ! -d "${BIM_REPO}/server" || ! -d "${BIM_REPO}/frontend" ]]; then
    echo "BIM_REPO must point to NODEDC_BIM_VIEWER with docker-compose.beam.yml, server and frontend: ${BIM_REPO}" >&2
    exit 1
  fi

  mkdir -p "${NAS_ROOT}/bim-viewer/source"
  rm -rf \
    "${NAS_ROOT}/bim-viewer/source/app" \
    "${NAS_ROOT}/bim-viewer/source/docs"
  rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
    --exclude='.git/' \
    --exclude='.DS_Store' \
    --exclude='node_modules/' \
    --exclude='dist/' \
    --exclude='app/' \
    --exclude='docs/' \
    --exclude='server/node_modules/' \
    --exclude='frontend/node_modules/' \
    --exclude='server/data/' \
    --include='.env.synology.example' \
    --exclude='.env' \
    --exclude='.env.*' \
    "${BIM_REPO}/" \
    "${NAS_ROOT}/bim-viewer/source/"
else
  echo "BIM_REPO is not set; BIM Viewer source was not synced."
fi

if [[ -n "${TASKER_REPO}" ]]; then
  if [[ ! -d "${TASKER_REPO}/plane-src" || ! -d "${TASKER_REPO}/plane-app" ]]; then
    echo "TASKER_REPO must point to NODEDC_TASKMANAGER with plane-src and plane-app: ${TASKER_REPO}" >&2
    exit 1
  fi

  mkdir -p "${NAS_ROOT}/tasker/plane-src" "${NAS_ROOT}/tasker/plane-app"

  rsync -av "${RSYNC_METADATA_ARGS[@]}" \
    "${TASKER_REPO}/plane-app/docker-compose.yaml" \
    "${NAS_ROOT}/tasker/plane-app/docker-compose.yaml"

  if [[ -n "${TASKER_CHANGED_BASE}" ]]; then
    echo "Syncing Tasker changed files from ${TASKER_CHANGED_BASE}..HEAD"
    git -C "${TASKER_REPO}" diff --name-only "${TASKER_CHANGED_BASE}..HEAD" -- plane-src plane-app/docker-compose.yaml |
      while IFS= read -r changed_file; do
        [[ -z "${changed_file}" ]] && continue

        case "${changed_file}" in
          plane-src/*)
            source_path="${TASKER_REPO}/${changed_file}"
            target_path="${NAS_ROOT}/tasker/plane-src/${changed_file#plane-src/}"
            ;;
          plane-app/docker-compose.yaml)
            source_path="${TASKER_REPO}/${changed_file}"
            target_path="${NAS_ROOT}/tasker/plane-app/docker-compose.yaml"
            ;;
          *)
            continue
            ;;
        esac

        if [[ -f "${source_path}" ]]; then
          mkdir -p "$(dirname -- "${target_path}")"
          rsync -av "${RSYNC_METADATA_ARGS[@]}" "${source_path}" "${target_path}"
        else
          echo "skip deleted Tasker file in changed sync: ${changed_file}"
        fi
      done
  elif [[ "${TASKER_SYNC_SOURCE}" == "1" ]]; then
    echo "TASKER_SYNC_SOURCE=1: syncing full Tasker source without delete"
    rsync -av "${RSYNC_METADATA_ARGS[@]}" \
      --exclude='.git/' \
      --exclude='node_modules/' \
      --exclude='.pnpm-store/' \
      --exclude='.turbo/' \
      --exclude='.cache/' \
      --exclude='.react-router/' \
      --exclude='build/' \
      --exclude='dist/' \
      --exclude='coverage/' \
      --exclude='playwright-report/' \
      --exclude='test-results/' \
      --exclude='__pycache__/' \
      --exclude='@eaDir/' \
      --exclude='.DS_Store' \
      --exclude='.env' \
      --exclude='.env.*' \
      --exclude='*.pyc' \
      "${TASKER_REPO}/plane-src/" \
      "${NAS_ROOT}/tasker/plane-src/"
  else
    echo "TASKER_REPO set; synced compose only. Set TASKER_CHANGED_BASE=<commit> or TASKER_SYNC_SOURCE=1 to sync source."
  fi

  if [[ -f "${TASKER_REPO}/plane-app/docker-compose.synology.override.yml" ]]; then
    rsync -av "${RSYNC_METADATA_ARGS[@]}" \
      "${TASKER_REPO}/plane-app/docker-compose.synology.override.yml" \
      "${NAS_ROOT}/tasker/plane-app/docker-compose.synology.override.yml"
  else
    echo "TASKER_REPO has no plane-app/docker-compose.synology.override.yml; existing NAS override, if any, was preserved."
  fi
else
  echo "TASKER_REPO is not set; tasker source and compose were not synced."
fi

if [[ -n "${GATEWAY_REPO}" ]]; then
  if [[ ! -f "${GATEWAY_REPO}/docker-compose.synology.yml" || ! -f "${GATEWAY_REPO}/.env.synology.example" ]]; then
    echo "GATEWAY_REPO must point to NODEDC_TASKMANAGER_CODEXAPI with docker-compose.synology.yml: ${GATEWAY_REPO}" >&2
    exit 1
  fi

  mkdir -p "${NAS_ROOT}/ops-agents"
  rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
    --exclude='.git/' \
    --exclude='node_modules/' \
    --exclude='dist/' \
    --include='.env.synology.example' \
    --exclude='.env' \
    --exclude='.env.*' \
    "${GATEWAY_REPO}/" \
    "${NAS_ROOT}/ops-agents/"
else
  echo "GATEWAY_REPO is not set; Ops Agents Gateway source was not synced."
fi

cat <<'EOF'

Synced source to the NAS mount and staged the root-owned runner candidate.

For a canonical deploy, do not use the legacy direct runtime scripts below.
An administrator first installs/verifies the staged runner, then plans and
applies a data-only artifact from /volume1/docker/nodedc-deploy/inbox:

sudo install -o root -g root -m 0755 \
  /volume1/docker/nodedc-deploy/runner-install/nodedc-deploy \
  /usr/local/sbin/nodedc-deploy
sudo /usr/local/sbin/nodedc-deploy verify-install
sudo /usr/local/sbin/nodedc-deploy plan /volume1/docker/nodedc-deploy/inbox/<artifact>.tgz
sudo /usr/local/sbin/nodedc-deploy apply /volume1/docker/nodedc-deploy/inbox/<artifact>.tgz

Legacy direct runtime scripts (emergency/manual recovery only):

cd /volume1/docker/nodedc-platform/platform
sudo bash apply-current-runtime.sh

If runtime was already applied and only verification is needed:

cd /volume1/docker/nodedc-platform/platform
sudo bash verify-current-runtime.sh

Optional BIM Viewer apply after BIM_REPO sync:

cd /volume1/docker/nodedc-platform/bim-viewer/source
sudo cp -n .env.synology.example .env
# Edit .env before first production start: NODEDC_INTERNAL_ACCESS_TOKEN must match platform .env.synology.
sudo /usr/local/bin/docker compose \
  --env-file .env \
  -f docker-compose.beam.yml \
  up -d --build --force-recreate ndc-beam-viewer nodedc-bim-converter

curl -k -fsS http://127.0.0.1:18100/api/auth/session

Manual equivalent:

cd /volume1/docker/nodedc-platform/platform
sudo mkdir -p ../launcher/server-storage ../launcher/uploads
sudo chown -R 1000:1000 ../launcher/server-storage ../launcher/uploads
sudo chmod -R u+rwX,g+rwX ../launcher/server-storage ../launcher/uploads

cd /volume1/docker/nodedc-platform/launcher/source
sudo /usr/local/bin/docker build --no-cache -t nodedc/launcher:local .

cd /volume1/docker/nodedc-platform/platform
sudo /usr/local/bin/docker compose \
  --env-file /volume1/docker/nodedc-platform/platform/.env.synology \
  -f /volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml \
  up -d --build --force-recreate --no-deps ontology-core ai-workspace-hub ai-workspace-assistant notification-core launcher

Optional Platform/Auth infra apply, only after deliberate compose/proxy/Auth templates changes:

sudo /usr/local/bin/docker compose \
  --env-file /volume1/docker/nodedc-platform/platform/.env.synology \
  -f /volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml \
  up -d --build --force-recreate --no-deps reverse-proxy authentik-server authentik-worker ontology-core ai-workspace-hub ai-workspace-assistant notification-core launcher

After Authentik template rollout, clear global Brand custom CSS from the live DB.
NODE.DC login CSS must be template-scoped, not stored in Brand.branding_custom_css:

cd /volume1/docker/nodedc-platform/platform
sudo bash clear-authentik-brand-css.sh

Verify:

sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc \
  'touch /app/server/storage/.write-test /app/server/storage/uploads/.write-test && rm /app/server/storage/.write-test /app/server/storage/uploads/.write-test && echo storage-ok'

sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc '
  echo "$NODEDC_AUTHENTIK_BASE_URL"
  getent hosts nodedc-platform-authentik-server
  for attempt in 1 2 3 4 5 6 7 8 9 10; do
    wget -qSO- \
      --header "Authorization: Bearer $NODEDC_AUTHENTIK_SERVICE_TOKEN" \
      "$NODEDC_AUTHENTIK_BASE_URL/api/v3/core/groups/?search=nodedc_admin" \
      2>&1 | head -n 25 && exit 0
    echo "authentik-api-not-ready attempt=$attempt"
    sleep 10
  done
  exit 1
'

curl -k -sS --compressed https://id.nodedc.ru/if/flow/default-authentication-flow/ \
  | grep -aE 'syncFormFieldsBeforeSubmit|branding_custom_css = ""|hub.nodedc.ru|launcher.local|getLauncherBaseUrl|Запросить доступ'

curl -fsS http://172.22.0.222:18081/healthz

id_admin_status="$(
  curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/
)"
if [[ "$id_admin_status" != "404" ]]; then
  echo "public id admin is not closed: status=${id_admin_status}"
  exit 1
fi
echo 'public-id-admin-closed-ok'

auth_admin_page="$(
  curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/admin/
)"
printf '%s' "$auth_admin_page" \
  | grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'

auth_admin_flow="$(
  curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/flow/default-authentication-flow/
)"
if printf '%s' "$auth_admin_flow" | grep -aq '<style data-id="nodedc-auth-login-css">'; then
  echo 'admin host still has NODE.DC auth CSS'
  exit 1
fi
echo 'auth-admin-css-ok'

launcher_asset="$(
  curl -k -sS --compressed -H 'Accept: text/html' https://hub.nodedc.ru/ \
    | grep -aoE 'index-[A-Za-z0-9_-]+\.js' \
    | head -n 1
)"
test -n "$launcher_asset"
if curl -k -sS --compressed "https://hub.nodedc.ru/assets/${launcher_asset}" \
  | grep -aq 'Заявка ожидает подтверждения'; then
  echo 'old pending gate still present'
  exit 1
fi
echo 'launcher-pending-gate-ok'

Optional Tasker apply after TASKER_REPO sync:

cd /volume1/docker/nodedc-platform/tasker/plane-src
BUILD_BACKEND=1 BUILD_WEB=1 BUILD_ADMIN=0 sh rebuild-nas-legacy.sh

Optional Ops Agents Gateway first deploy after GATEWAY_REPO sync:

cd /volume1/docker/nodedc-platform/ops-agents
cp -n .env.synology.example .env
# Edit .env before first start:
# - NODEDC_AGENT_GATEWAY_INTERNAL_TOKEN
# - NODEDC_INTERNAL_ACCESS_TOKEN
# - POSTGRES_PASSWORD

sudo /usr/local/bin/docker compose \
  --env-file .env \
  -f docker-compose.synology.yml \
  up -d --build

Verify Ops Agents Gateway:

curl -fsS http://172.22.0.222:18190/healthz
curl -fsS http://172.22.0.222:18190/readyz
curl -fsS -i http://172.22.0.222:18190/mcp | head
EOF
