#!/usr/bin/env bash
#
# Deploy the current committed HEAD to production (teeu.ru).
#
# Workflow (ТЗ ops): commit + (this pushes) → ship to server. Only *tracked*
# files are shipped via `git archive`, so the server .env and uploaded files in
# storage/ are never touched. Файлы, удалённые из репозитория, снимаются с
# сервера отдельным шагом — сама распаковка их не убирает (см. шаг 4).
# Built assets (public/build) are committed because
# the server has no Node — rebuild + commit them before deploying if the front
# changed:  (PowerShell) $env:Path="d:\laragon\bin\nodejs\node-v22;"+$env:Path; npm run build
#           git add -A && git commit -m "..." && bash bin/deploy.sh
#
set -euo pipefail

SERVER="root@201.51.7.92"
APP="/home/teeu/web/teeu.ru/private/teeu"
PHP="/usr/bin/php8.3"
BRANCH="$(git rev-parse --abbrev-ref HEAD)"

# 1. Refuse to deploy a dirty tree — commit first.
if [ -n "$(git status --porcelain)" ]; then
    echo "‼  Есть незакоммиченные изменения — сначала commit (и пересобери public/build, если менялся фронт):"
    git status --short
    exit 1
fi

# 2. Push to GitLab (source of truth).
echo "→ push origin ${BRANCH}"
git push origin "${BRANCH}"

LOCK_BEFORE="$(ssh "${SERVER}" "md5sum ${APP}/composer.lock 2>/dev/null | cut -d' ' -f1" || true)"

# 3. Ship the committed tree (tracked files only → .env / storage untouched).
#
# Распаковываем сразу от пользователя сайта. Раньше архив приезжал от root, и
# владельца потом чинил `chown -R` по всему дереву: 250 тысяч файлов (vendor и
# логи в том числе) — на них уходило больше времени, чем на весь остальной
# деплой, до двадцати минут.
#
# Если в дереве кода вдруг оказался файл не от teeu (наследие старых выкаток или
# ручная правка под root), tar от teeu не смог бы его перезаписать — поэтому
# сначала дешёвая проверка: найти первый такой файл и, только если он есть,
# разово выправить владельца. `-print -quit` останавливает поиск на первой
# находке, поэтому в обычный день это несколько миллисекунд.
echo "→ выкатываю код на сервер"
ssh "${SERVER}" "find ${APP}/app ${APP}/bootstrap ${APP}/config ${APP}/database \
    ${APP}/lang ${APP}/public ${APP}/resources ${APP}/routes \
    -not -user teeu -print -quit 2>/dev/null | grep -q . && chown -R teeu:teeu ${APP} || true"
git archive --format=tar HEAD | ssh "${SERVER}" "sudo -u teeu tar -x -C ${APP}"

# Ставить зависимости заново, когда они не менялись, незачем: composer install
# со снятием и перезаписью autoload занимает половину деплоя. Сравниваем lock-файл
# до и после распаковки.
LOCK_AFTER="$(ssh "${SERVER}" "md5sum ${APP}/composer.lock 2>/dev/null | cut -d' ' -f1" || true)"

# 4. Убрать файлы, исчезнувшие из репозитория. `git archive | tar -x` кладёт файлы
# ПОВЕРХ существующего дерева и сам ничего не удаляет, поэтому переименованный или
# удалённый класс продолжает жить на сервере. Так /developer/partner-faqs упал в
# 500: ресурс, переименованный в Faqs, остался вторым экземпляром и лез в таблицу,
# которой уже нет. Чистим только каталоги кода — .env, storage/, vendor/ и
# bootstrap/cache/ (легитимно отсутствуют в git) не трогаем.
#
# public/build сознательно НЕ чистим: на старые бандлы ещё ссылаются страницы,
# открытые у посетителей до деплоя, а мёртвым кодом они не являются — просто
# занимают место.
echo "→ убираю файлы, удалённые из репозитория"
git ls-tree -r --name-only HEAD | ssh "${SERVER}" "
    cd ${APP} && cat > .deploy-manifest || exit 1
    # Пустой/обрезанный манифест снёс бы весь код — страхуемся порогом.
    if [ \"\$(wc -l < .deploy-manifest)\" -lt 100 ]; then
        echo '‼ манифест подозрительно короткий — чистка пропущена'
        rm -f .deploy-manifest
        exit 0
    fi
    find app config database lang resources routes -type f 2>/dev/null \
        | grep -vxF -f .deploy-manifest \
        | tee /dev/stderr \
        | xargs -r rm -f
    rm -f .deploy-manifest
"

# 5. Server-side: deps + migrations + caches + permissions.
#
# Все команды идут от пользователя сайта. Иначе `view:cache` под root создаёт
# скомпилированные шаблоны с владельцем root, и php-fpm (он работает под teeu)
# не может их перезаписать: Blade падает на tempnam(), Laravel превращает notice
# в исключение — посетитель видит 500. Так на проде накопилось 172 такие ошибки
# с 5 августа, всплесками ровно по времени выкаток.
#
# `chown -R` по всему дереву больше не нужен: архив и так распаковывается от
# teeu (шаг 3). Права правим только там, куда пишет php-fpm, — это десятки
# файлов вместо сотен тысяч.
echo "→ сборка на сервере"

if [ -n "${LOCK_AFTER}" ] && [ "${LOCK_BEFORE}" = "${LOCK_AFTER}" ]; then
    COMPOSER_STEP="[ -d vendor ] || composer install --no-dev --optimize-autoloader --no-interaction 2>&1 | tail -2"
    echo "  зависимости не менялись — composer install пропущен"
else
    COMPOSER_STEP="composer install --no-dev --optimize-autoloader --no-interaction 2>&1 | tail -2"
fi

ssh "${SERVER}" "cd ${APP} && \
    sudo -u teeu bash -lc 'cd ${APP} && \
        ${COMPOSER_STEP} && \
        ${PHP} artisan migrate --force 2>&1 | tail -3 && \
        ${PHP} artisan optimize:clear >/dev/null && \
        ${PHP} artisan config:cache >/dev/null && \
        ${PHP} artisan route:cache >/dev/null && \
        ${PHP} artisan view:cache >/dev/null && \
        ${PHP} artisan event:cache >/dev/null && \
        ${PHP} artisan filament:optimize >/dev/null 2>&1 || true; \
        ${PHP} artisan queue:restart >/dev/null 2>&1 || true' && \
    chmod -R ug+rwX ${APP}/storage/framework ${APP}/bootstrap/cache && \
    echo '✓ deploy done'"

# 6. optimize:clear выше очищает и кэш приложения, а с ним фильтры и счётчики
# каталога. Крупные разделы считаются секундами — прогреваем их сразу, в фоне,
# чтобы этот пересчёт не достался первым посетителям (docs/catalog.md). Деплой
# прогрева не ждёт: вывод и ввод отвязаны, иначе ssh держал бы сессию до конца.
echo "→ прогрев кэша каталога (в фоне)"
ssh "${SERVER}" "sudo -u teeu bash -lc 'cd ${APP} && nohup ${PHP} artisan teeu:catalog:warm >/dev/null 2>&1 </dev/null &'"

echo "✅ Готово: https://teeu.ru"
