#!/usr/bin/env bash
#
# Deploy the business centre CRM to a single Linux host.
#
# Safe to run twice: every step either re-does the same work or does nothing.
# It never writes .env, never generates an APP_KEY and never seeds. Those are
# first-install steps and they are in docs/deployment.md §6.
#
# Usage, from the application directory:
#
#   ./deploy.sh              # pull the current branch, then deploy
#   ./deploy.sh --no-pull    # deploy what is already checked out
#
# Requirements: bash, git, composer, php 8.3, node (20.19+ or 22.12+ preferred),
#               npm, sudo rights
# to restart the queue worker unit. See docs/deployment.md §2.

set -euo pipefail

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

PULL=1
if [ "${1:-}" = "--no-pull" ]; then
    PULL=0
fi

say() {
    printf '\n\033[1m==> %s\033[0m\n' "$1"
}

fail() {
    printf '\n\033[31mdeploy.sh: %s\033[0m\n' "$1" >&2
    exit 1
}

# --- 0. Refuse to run against a half-configured host ------------------------

[ -f .env ] || fail ".env is missing. This is a first install: follow docs/deployment.md §6 before running deploy.sh."

grep -q '^APP_KEY=base64:' .env || fail "APP_KEY is not set in .env. Run 'php artisan key:generate' ONCE (docs/deployment.md §6 step 3) and never again — it decrypts employee IBANs."

# Node's version IS enforced, and the floor is the toolchain's own:
# rolldown (which vite 8 builds on) declares engines ^20.19.0 || >=22.12.0, and
# npm prints EBADENGINE below that. Node 21 is excluded by that range too.
#
# On 22.11 a clean `npm ci && npm run build` fails, and NEITHER error names the
# Node version - which is why this check exists rather than letting the build
# speak for itself. Both observed on 2026-10-02:
#
#   1. "Cannot find native binding" for @rolldown/binding-win32-x64-msvc. The
#      message blames an npm optional-dependency bug and tells you to delete
#      package-lock.json, which is a wrong and destructive suggestion here.
#   2. After installing that binding by hand, a different failure:
#      TypeError: The "paths[1]" argument must be of type string.
#
# A WARNING IS NOT ENOUGH, and this check was briefly relaxed to one on the
# strength of a `npm run build` that succeeded on 22.11. That test was invalid:
# node_modules had been installed earlier, under conditions that worked, so it
# proved nothing about a fresh install. `npm ci && npm run build` is the only
# meaningful test, and it fails. CI proves the other side - the same two
# commands on Node 22.12 have gone green repeatedly.
#
# So: test the whole operation, not the convenient half of it.
NODE_VERSION="$(node --version)"
NODE_MAJOR="$(printf '%s' "$NODE_VERSION" | sed 's/^v//' | cut -d. -f1)"
NODE_MINOR="$(printf '%s' "$NODE_VERSION" | sed 's/^v//' | cut -d. -f2)"
NODE_OK=0
if [ "$NODE_MAJOR" -eq 20 ] && [ "$NODE_MINOR" -ge 19 ]; then
    NODE_OK=1
elif [ "$NODE_MAJOR" -eq 22 ] && [ "$NODE_MINOR" -ge 12 ]; then
    NODE_OK=1
elif [ "$NODE_MAJOR" -gt 22 ]; then
    NODE_OK=1
fi
if [ "$NODE_OK" -eq 0 ]; then
    fail "node $NODE_VERSION is not supported by this build. vite 8 / rolldown declare '^20.19.0 || >=22.12.0'. Below that, 'npm ci && npm run build' fails with errors that never mention Node - see docs/deployment.md §2. Install Node 20.19+ or 22.12+ and run again."
fi

say "Deploying in $APP_DIR (node $NODE_VERSION, $(php --version | head -n1))"

# --- 1. Get the code --------------------------------------------------------

if [ "$PULL" -eq 1 ]; then
    BRANCH="$(git rev-parse --abbrev-ref HEAD)"
    say "Pulling $BRANCH"
    git pull --ff-only origin "$BRANCH"
else
    say "Skipping git pull (--no-pull)"
fi
say "Deploying commit $(git rev-parse --short HEAD)"

# --- 2. PHP dependencies ----------------------------------------------------
#
# --no-dev drops Pest, Pint, Boost and Faker. composer's post-autoload-dump
# hook runs `php artisan filament:upgrade`, which republishes Filament's own
# CSS/JS into public/ — that is why .env must already exist at this point.

say "composer install"
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist

# --- 3. Front-end assets ----------------------------------------------------
#
# /public/build is gitignored and the admin panel reads public/build/manifest.json
# on EVERY page (AdminPanelProvider calls ->viteTheme(...)). Skipping this step
# gives a 500 on every admin page, not a missing stylesheet.
#
# `npm ci` needs the npm registry; nothing else here reaches the internet.
# The Instrument Sans web font used to be downloaded from fonts.bunny.net
# during the build. It is checked into public/fonts/instrument-sans now, so
# a build host with no outbound access past the registry is fine.

say "npm ci && npm run build"
npm ci
npm run build
[ -f public/build/manifest.json ] || fail "public/build/manifest.json was not produced. The panel will 500 on every page. Do not continue."

# --- 4. Database ------------------------------------------------------------
#
# --force skips the interactive "are you sure" in production. Running this
# with nothing to migrate prints "Nothing to migrate." and changes nothing.

say "php artisan migrate --force"
php artisan migrate --force

# --- 5. Caches --------------------------------------------------------------
#
# optimize:clear first, so a stale config cache from the previous release
# cannot be read while the new one is being written. `optimize` then runs
# config:cache, route:cache, view:cache and event:cache.
#
# NOTE: once config is cached, env() outside config/ returns null. Never add
# an env() call to application code.

say "php artisan optimize"
php artisan optimize:clear
php artisan optimize

# --- 6. Storage symlink -----------------------------------------------------
#
# Harmless and idempotent. Document scans are NOT served this way — they live
# on the private `documents` disk and leave only through DocumentFileController.

say "php artisan storage:link"
php artisan storage:link --quiet || true

# --- 7. Restart the workers -------------------------------------------------
#
# queue:restart asks running workers to finish their current job and exit;
# systemd's Restart=always then starts them on the new code. The systemd
# restart afterwards covers a worker that was not running at all.

say "Restarting the queue worker"
php artisan queue:restart
if command -v systemctl >/dev/null 2>&1; then
    sudo systemctl restart crm-queue.service
    sudo systemctl is-active --quiet crm-queue.service || fail "crm-queue.service did not come back up. Check: sudo journalctl -u crm-queue -n 50"
else
    printf 'systemctl not found — restart the queue worker yourself.\n'
fi

# --- 8. Reload PHP-FPM ------------------------------------------------------
#
# Drops the old opcache so the new code is actually served.

if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 'php8.3-fpm.service' --no-legend | grep -q php8.3-fpm; then
    say "Reloading php8.3-fpm"
    sudo systemctl reload php8.3-fpm.service
fi

# --- 9. Say what was deployed ----------------------------------------------

say "Deployed"
php artisan about --only=environment
printf '\nNow run the post-deploy checks in docs/deployment.md §10.\n'
