default:
    @just --list

# XAMXAM Justfile

# ============================================================================
# Development
# ============================================================================

[group('dev')]
setup:
    @bash scripts/setup-dev.sh

[group('dev')]
serve: migrate
    @xdg-open http://127.0.0.1:8000 &
    @xdg-open http://127.0.0.1:8000/admin/ &
    @php \
        -d upload_max_filesize=512M \
        -d post_max_size=520M \
        -d memory_limit=256M \
        -d max_execution_time=300 \
        -d max_input_time=300 \
        -S 127.0.0.1:8000 -t app/public/ app/router.php 2>&1 \
        | stdbuf -oL grep -Ev '(Accepted|Closing|live-reload\.php|assets/|favicon)'

[group('dev')]
stop:
    @pkill -f "php -S 127.0.0.1:8000" 2>/dev/null && echo "stopped" || echo "no server running"

[group('dev')]
logs:
    @tail -n 20 error.log 2>/dev/null || echo "no error log"

# ============================================================================
# Deploy
# ============================================================================

[group('deploy')]
deploy:
    # Main deploy (code + assets) then run any pending DB migrations
    rsync -vur --progress --delete \
        --chown="www-data:xamxam" \
        --exclude 'vendor' \
        --exclude 'tests' \
        --exclude '*.md' \
        --exclude '.git*' \
        --exclude '.jj' \
        --exclude '.claude' \
        --exclude '.pi' \
        --exclude '.DS_Store' \
        --exclude '.env' \
        --exclude 'storage/xamxam.db' \
        --exclude 'storage/theses' \
        --exclude 'storage/covers' \
        --exclude 'storage/backup_*' \
        --exclude 'storage/cache/*' \
        --exclude 'storage/maintenance.flag' \
        --exclude 'storage/fixtures' \
        --exclude 'storage/docs' \
        --exclude 'var/' \
        app/ xamxam:/var/www/xamxam/
    # Upload deploy-server.sh for post-deploy permission fix
    rsync -v scripts/deploy-server.sh xamxam:/tmp/deploy-server.sh
    ssh -t xamxam "sudo bash /tmp/deploy-server.sh"
    ssh xamxam "rm -f /tmp/deploy-server.sh"
    ssh xamxam "mkdir -p /var/www/xamxam/var/{cache,logs,tmp}"
    ssh xamxam "cd /var/www/xamxam && php -r 'if (!file_exists(\"/var/www/xamxam/storage/xamxam.db\")) { \$db = new PDO(\"sqlite:/var/www/xamxam/storage/xamxam.db\"); \$db->exec(file_get_contents(\"/var/www/xamxam/storage/schema.sql\")); echo \"Database created from schema.\\n\"; } else { echo \"Database already exists.\\n\"; }'"
    # Run pending migrations
    ssh xamxam "cd /var/www/xamxam && bash scripts/migrate.sh"
    # Deploy nginx configuration
    @just deploy-nginx
    # Sync .env separately (excluded above to avoid accidental overwrite on subsequent deploys)
    @just deploy-env
    @just deploy-verify-permissions
    @echo ""
    @echo "ℹ️  First deploy? Also run: just deploy-backup"
    @echo ""

[group('deploy')]
deploy-env:
    #!/usr/bin/env bash
    set -euo pipefail
    # Upload app/.env only if it exists locally; never overwrites a remote .env that already has APP_KEY.
    if [ ! -f app/.env ]; then
    echo "WARNING: app/.env not found locally — skipping."
    exit 0
    fi
    if ssh xamxam '[ -f /var/www/xamxam/.env ]'; then
    echo "Remote .env already exists — skipping to avoid overwriting key."
    echo "Run 'just reencrypt-password' if you rotated APP_KEY."
    else
    rsync -v --progress app/.env xamxam:/var/www/xamxam/.env
    ssh xamxam "chmod 640 /var/www/xamxam/.env && chown www-data:xamxam /var/www/xamxam/.env"
    echo ".env uploaded."
    fi

[group('deploy')]
reencrypt-password new_key_b64="":
    #!/usr/bin/env bash
    set -euo pipefail
    # Re-encrypt the SMTP password in the remote DB after rotating APP_KEY.
    # Usage:
    #   1. Generate a new key:  php -r "echo base64_encode(random_bytes(32));"
    #   2. Run:                 just reencrypt-password <new_base64_key>
    #   3. Update app/.env locally with the new key, then run: just deploy-env
    if [ -z "{{new_key_b64}}" ]; then
    echo "Usage: just reencrypt-password <new_base64_key>"
    echo "Generate a key: php -r \"echo base64_encode(random_bytes(32));\""
    exit 1
    fi
    # Run the re-encryption script on the server using the current key (from remote .env)
    # and the supplied new key.
    ssh xamxam "php /var/www/xamxam/scripts/reencrypt-smtp-password.php '{{new_key_b64}}' /var/www/xamxam/storage/xamxam.db"

[group('deploy')]
deploy-db:
    @ssh xamxam '[ ! -f /var/www/xamxam/storage/xamxam.db ]' || (echo "ERROR: remote database already exists. Remove it manually if you intend to overwrite." && exit 1)
    rsync -v --progress app/storage/xamxam.db xamxam:/var/www/xamxam/storage/xamxam.db
    ssh xamxam "chown www-data:xamxam /var/www/xamxam/storage/xamxam.db && chmod 660 /var/www/xamxam/storage/xamxam.db"

[group('deploy')]
deploy-verify-permissions:
    #!/usr/bin/env bash
    set -euo pipefail
    APP_DIR="/var/www/xamxam"
    WEB_USER="www-data"
    APP_GROUP="xamxam"
    ERRORS=0

    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    NC='\033[0m'

    ok()   { printf "${GREEN}✓${NC} %s\n" "$*"; }
    err()  { printf "${RED}✗${NC} %s\n" "$*" >&2; ERRORS=$((ERRORS + 1)); }
    warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }

    printf "🔍 Verifying permissions on %s…\n\n" "$APP_DIR"

    # ── Ownership ──────────────────────────────────────────────────────────────────
    echo "── Ownership ───────────────────────────────────"
    while IFS= read -r line; do
    owner=$(echo "$line" | awk '{print $3}')
    group=$(echo "$line" | awk '{print $4}')
    path=$(echo "$line" | awk '{print $NF}')
    if [ "$owner" != "$WEB_USER" ] || [ "$group" != "$APP_GROUP" ]; then
    err "$path → $owner:$group (expected $WEB_USER:$APP_GROUP)"
    else
    ok "$path → $owner:$group"
    fi
    done < <(ssh xamxam "stat -c '%U %G %n' $APP_DIR $APP_DIR/app $APP_DIR/storage $APP_DIR/var 2>/dev/null")

    # ── Key directories: 2775 ─────────────────────────────────────────────────────
    echo "── Directory permissions (expected 2775) ───────"
    while IFS= read -r line; do
    perms=$(echo "$line" | awk '{print $1}')
    path=$(echo "$line" | awk '{print $NF}')
    if [ "$perms" != "drwxrwsr-x" ]; then
    err "$path → $perms (expected drwxrwsr-x / 2775)"
    else
    ok "$path → $perms"
    fi
    done < <(ssh xamxam "find $APP_DIR -maxdepth 2 -type d -exec stat -c '%A %n' {} \\; 2>/dev/null | sort")

    # ── Key files: 664 ────────────────────────────────────────────────────────────
    echo "── File permissions (expected 664 / 660) ───────"
    # Spot-check a few critical files
    while IFS= read -r path; do
    perms=$(ssh xamxam "stat -c '%a %U %G' '$path' 2>/dev/null" || echo "MISSING")
    if [ "$perms" = "MISSING" ]; then
    err "$path → FILE MISSING"
    else
    perm_num=$(echo "$perms" | awk '{print $1}')
    owner=$(echo "$perms" | awk '{print $2}')
    group=$(echo "$perms" | awk '{print $3}')
    case "$path" in
    */storage/xamxam.db|*/storage/*.db)
    expected_perm="660" ;;
    *)
    expected_perm="664" ;;
    esac
    if [ "$perm_num" != "$expected_perm" ]; then
    err "$path → $perm_num ($owner:$group), expected $expected_perm $WEB_USER:$APP_GROUP"
    elif [ "$owner" != "$WEB_USER" ]; then
    err "$path → owner $owner, expected $WEB_USER (perm $perm_num OK)"
    else
    ok "$path → $perm_num $owner:$group"
    fi
    fi
    done < <(printf '%s\n' \
    "$APP_DIR/.env" \
    "$APP_DIR/app/router.php" \
    "$APP_DIR/storage/xamxam.db")

    # ── var/ subdirectories must be writable ──────────────────────────────────────
    echo "── var/ writability ────────────────────────────"
    for subdir in cache logs tmp; do
    if ssh xamxam "[ -w /var/www/xamxam/var/$subdir ]"; then
    ok "var/$subdir → writable"
    else
    err "var/$subdir → NOT WRITABLE"
    fi
    done

    # ── storage/cache/rate_limit writable ─────────────────────────────────────────
    if ssh xamxam "[ -w /var/www/xamxam/storage/cache/rate_limit ]"; then
    ok "storage/cache/rate_limit → writable"
    else
    err "storage/cache/rate_limit → NOT WRITABLE"
    fi

    # ── .env must be 640 ──────────────────────────────────────────────────────────
    env_perm=$(ssh xamxam "stat -c '%a' /var/www/xamxam/.env 2>/dev/null" || echo "")
    if [ "$env_perm" = "640" ]; then
    ok ".env → 640"
    elif [ -z "$env_perm" ]; then
    warn ".env → MISSING"
    else
    err ".env → $env_perm (expected 640)"
    fi

    # ── Summary ───────────────────────────────────────────────────────────────────
    echo ""
    if [ "$ERRORS" -eq 0 ]; then
    printf "${GREEN}✅ All permissions OK${NC}\n"
    else
    printf "${RED}❌ %d permission error(s) found${NC}\n" "$ERRORS"
    printf "${YELLOW}Fix with: sudo bash /tmp/deploy-server.sh${NC}\n"
    exit 1
    fi

[group('deploy')]
deploy-nginx:
    # Upload nginx config to the server, test it, and reload.
    # Uses the scripts/deploy-server.sh helper that handles the nginx
    # config installation and reload (steps 2-4).
    @echo "📋 Deploying nginx configuration…"
    rsync -v nginx/xamxam.conf xamxam:/tmp/xamxam.conf
    rsync -v scripts/deploy-server.sh xamxam:/tmp/deploy-server.sh
    ssh -t xamxam "sudo DEPLOY_USER=\$USER bash /tmp/deploy-server.sh"
    ssh xamxam "rm -f /tmp/deploy-server.sh /tmp/xamxam.conf"

[group('deploy')]
deploy-script script_name:
    # Generic script deployer (e.g., just deploy-script setup-server)
    rsync -v scripts/{{script_name}}.sh xamxam:/tmp/{{script_name}}.sh
    @echo ""
    @echo "Script uploaded. SSH into the server and run:"
    @echo ""
    @echo "  sudo DEPLOY_USER=\$USER bash /tmp/{{script_name}}.sh"
    @echo ""

[group('deploy')]
deploy-backup-script:
    # Upload backup-sqlite.sh to /usr/local/bin on the server (requires sudo)
    # Run once after initial deploy or when the backup script changes.
    @echo "📋 Deploying backup script…"
    rsync -v scripts/backup-sqlite.sh xamxam:/tmp/backup-sqlite.sh
    ssh -t xamxam "sudo install -o root -g root -m 755 /tmp/backup-sqlite.sh /usr/local/bin/backup-sqlite.sh && rm -f /tmp/backup-sqlite.sh"
    @echo "✅ backup-sqlite.sh installed to /usr/local/bin/"

[group('deploy')]
deploy-backup-cron:
    # Install cron jobs for hourly (30d retention) and daily (90d) backups.
    # Uses /etc/cron.d/xamxam-backup (system cron format: minute hour dom month dow user command)
    # Creates backup directory and log file on the server.
    @echo "📋 Installing backup cron jobs…"
    rsync -v deploy/xamxam-backup.cron xamxam:/tmp/xamxam-backup.cron
    ssh -t xamxam "sudo install -o root -g root -m 644 /tmp/xamxam-backup.cron /etc/cron.d/xamxam-backup && rm -f /tmp/xamxam-backup.cron"
    ssh -t xamxam "sudo mkdir -p /var/backups/xamxam && sudo chown www-data:www-data /var/backups/xamxam && sudo chmod 755 /var/backups/xamxam"
    ssh -t xamxam "sudo touch /var/log/sqlite-backup.log && sudo chown www-data:www-data /var/log/sqlite-backup.log && sudo chmod 644 /var/log/sqlite-backup.log"
    @echo "✅ Cron jobs installed."
    @echo "   Cron file: /etc/cron.d/xamxam-backup"
    @echo "   Backup dir: /var/backups/xamxam"
    @echo "   Log file:   /var/log/sqlite-backup.log"
    @echo ""
    @echo "Verify with: just deploy-check-backup-log"

[group('deploy')]
deploy-backup: deploy-backup-script deploy-backup-cron
    # One-shot: deploy backup script + install cron jobs + set up directories.

[group('deploy')]
deploy-check-backup-log:
    # Show the last 20 lines of the SQLite backup log on the server.
    ssh -t xamxam "sudo tail -20 /var/log/sqlite-backup.log 2>/dev/null || echo '(log file empty or missing — will be created on first cron run)'"

[group('deploy')]
deploy-list-backups:
    # List all existing backups on the server (most recent last).
    ssh xamxam "ls -lth /var/backups/xamxam/ 2>/dev/null || echo 'No backups yet.'"

[group('deploy')]
test-restore remote_gz_path:
    # Test-restore a production backup snapshot to a local temp DB and verify.
    # Usage: just test-restore /var/backups/xamxam/db-2026-05-11T14-00-00.db.gz
    @scp xamxam:'{{remote_gz_path}}' /tmp/xamxam-restore-test.db.gz
    @gunzip -c /tmp/xamxam-restore-test.db.gz > /tmp/xamxam-restore-test.db
    @echo "Tables in snapshot:"
    @sqlite3 /tmp/xamxam-restore-test.db ".tables"
    @echo ""
    @echo "Thesis count:"
    @sqlite3 /tmp/xamxam-restore-test.db "SELECT COUNT(*) FROM theses WHERE deleted_at IS NULL;"
    @echo ""
    @echo "✅ Snapshot is valid. Remove temp files:"
    @echo "   rm /tmp/xamxam-restore-test.db /tmp/xamxam-restore-test.db.gz"
    @rm -f /tmp/xamxam-restore-test.db.gz

[group('deploy')]
trigger-backup:
    # Manually trigger the backup script on the server now (doesn't wait for cron).
    ssh -t xamxam "sudo -u www-data /usr/local/bin/backup-sqlite.sh"

# ============================================================================
# Testing
# ============================================================================

[group('test')]
test:
    # Run all tests. To run a subset, use:
    #   php app/tests/Unit/DatabaseTest.php
    #   php app/tests/Integration/SearchTest.php
    @php app/tests/run-tests.php

[group('test')]
lint-biome:
    @biome lint app/public/assets/js/

[group('test')]
phpstan:
    @vendor/bin/phpstan analyse --memory-limit=512M

[group('test')]
cs-check:
    @vendor/bin/php-cs-fixer check --no-interaction

[group('test')]
cs-fix:
    @vendor/bin/php-cs-fixer fix --no-interaction

[group('test')]
syntax:
    @find app/ -name '*.php' -exec php -l {} \; 2>/dev/null | grep -v 'No syntax errors' || true
    @echo '✅ Syntax OK'

# ============================================================================
# Database
# ============================================================================

[group('database')]
migrate:
    @echo "Running migrations…"
    @bash scripts/migrate.sh

[group('database')]
init-db:
    @sqlite3 app/storage/xamxam.db < app/storage/schema.sql
    @sqlite3 app/storage/xamxam.db "SELECT COUNT(*) || ' tables' FROM sqlite_master WHERE type='table';"

[group('database')]
reset-db:
    @rm -f app/storage/xamxam.db
    @just init-db

[group('database')]
query:
    @sqlite3 app/storage/xamxam.db

[group('database')]
backup:
    @sqlite3 app/storage/xamxam.db .dump > app/storage/backup_$(date +%Y%m%d_%H%M%S).sql

[group('database')]
backup-snapshot:
    # Hot backup using SQLite's .backup API (WAL-safe), then gzip.
    @DB_PATH=app/storage/xamxam.db BACKUP_DIR=app/storage/backups RETENTION_DAYS=30 bash scripts/backup-sqlite.sh

# ============================================================================
# Utils
# ============================================================================

[group('utils')]
clean:
    @rm -f app/error.log
    @rm -rf app/storage/cache/rate_limit/*
    @rm -f /tmp/xamxam-*.log /tmp/xamxam-*.pid
