#!/usr/bin/env bash
# Local CI runner. Runs lint / static analysis / tests for both subprojects,
# builds the ui asset bundle, and verifies docker compose images build.
#
# Designed for a host with Docker installed but no PHP/Composer/Node toolchain:
# every PHP and Node command runs inside an ephemeral container.
#
# Usage:
#     ./scripts/ci.sh
#
# Env vars:
#     DB_DRIVERS   space-separated list, default "sqlite mysql".
#                  api test stage runs once per driver. mysql is skipped
#                  with a warning if no MySQL is reachable on the host.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

DB_DRIVERS="${DB_DRIVERS:-sqlite mysql}"
PHP_IMAGE="composer:2"
NODE_IMAGE="node:20-alpine"

UID_GID="$(id -u):$(id -g)"

banner() {
    printf '\n\033[1;36m==> %s\033[0m\n' "$*"
}

note() {
    printf '\033[1;33m[note]\033[0m %s\n' "$*"
}

ok() {
    printf '\033[1;32m%s\033[0m\n' "$*"
}

fail() {
    printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2
    exit 1
}

# Run a command inside a one-off PHP/Composer container, mounted at /app.
# Args: <subproject-dir> <env-args> <command...>
run_php() {
    local dir="$1"; shift
    docker run --rm \
        -u "$UID_GID" \
        -v "$REPO_ROOT/$dir":/app \
        -v "$HOME/.composer-cache":/tmp \
        -w /app \
        -e COMPOSER_HOME=/tmp/composer \
        -e COMPOSER_CACHE_DIR=/tmp/composer-cache \
        -e XDG_CONFIG_HOME=/tmp \
        "$@"
}

# Run a command inside a one-off Node container.
run_node() {
    local dir="$1"; shift
    docker run --rm \
        -u "$UID_GID" \
        -v "$REPO_ROOT/$dir":/app \
        -w /app \
        -e HOME=/tmp \
        "$NODE_IMAGE" "$@"
}

mkdir -p "$HOME/.composer-cache"

# ---------- 1. api ----------
banner "api: composer install"
run_php api "$PHP_IMAGE" composer install --no-interaction --prefer-dist

banner "api: phpstan"
run_php api "$PHP_IMAGE" composer stan

banner "api: php-cs-fixer (dry-run)"
run_php api "$PHP_IMAGE" composer cs

# Dependency vulnerability scan (SPEC §M14.9). Fails on any advisory.
# Policy: when this fails an admin reviews and either patches or
# accepts with a documented exception (see doc/security.md).
banner "api: composer audit"
run_php api "$PHP_IMAGE" composer audit --no-dev

for driver in $DB_DRIVERS; do
    if [ "$driver" = "mysql" ]; then
        # No MySQL reachable from the host in M01; skip gracefully. Once
        # later milestones add real DB-touching tests, replace this with a
        # proper reachability probe.
        note "api: skipping phpunit for driver=mysql (no MySQL configured for local CI in M01)"
        continue
    fi
    banner "api: phpunit (DB_DRIVER=$driver)"
    run_php api -e "DB_DRIVER=$driver" "$PHP_IMAGE" composer test
done

# ---------- 2. ui ----------
banner "ui: composer install"
run_php ui "$PHP_IMAGE" composer install --no-interaction --prefer-dist

banner "ui: phpstan"
run_php ui "$PHP_IMAGE" composer stan

banner "ui: php-cs-fixer (dry-run)"
run_php ui "$PHP_IMAGE" composer cs

banner "ui: composer audit"
run_php ui "$PHP_IMAGE" composer audit --no-dev

banner "ui: phpunit"
run_php ui "$PHP_IMAGE" composer test

# ---------- 3. ui frontend build ----------
banner "ui: npm ci"
if [ -f ui/package-lock.json ]; then
    run_node ui npm ci --no-audit --no-fund
else
    note "ui/package-lock.json not present — using npm install to bootstrap"
    run_node ui npm install --no-audit --no-fund
fi

banner "ui: npm run build"
run_node ui npm run build

# Production-deps vuln scan (SPEC §M14.9). `--audit-level=high` fails
# only on high/critical so noisy moderate advisories don't block CI;
# they're expected to be reviewed during a routine bump pass.
banner "ui: npm audit (production deps, high+critical)"
run_node ui npm audit --omit=dev --audit-level=high

if [ ! -f ui/public/assets/app.css ]; then
    fail "ui/public/assets/app.css was not produced by the tailwind build"
fi

# ---------- 4. docker compose build ----------
banner "docker compose build"
docker compose build

ok "CI OK"
