#!/usr/bin/env bash
#
# Deploy this repo's committed tree to the Launceston Bin Hire dev subdomain
# on the Clever Dog Design cPanel box. Idempotent — safe to run twice. No
# interactive prompts.
#
# Refuses to target anything but dev.launcestonbinhire.com.au (see
# DEV_DOMAIN below) — never launcestonbinhire.com.au or lbhire.com.au.
#
# Usage:
#   ./deploy.sh --db-name=NAME --db-user=USER --db-password=PASS --owner-password=PASS [options]
#
# Required:
#   --db-name=NAME            cPanel MySQL database name
#   --db-user=USER            cPanel MySQL database user
#   --db-password=PASS        cPanel MySQL database password
#   --owner-password=PASS     New password for owner@launcestonbinhire.com.au
#
# Optional:
#   --stripe-webhook-secret=SECRET   Reuse an existing Stripe webhook endpoint's
#                                    signing secret instead of creating a new one.
#   --ssh-host=HOST            Override the SSH host (default: server.cleverdogdesign.com)
#   --ssh-user=USER            Override the SSH/cPanel user (default: lbhire)
#   --app-dir=DIR              Remote app directory, relative to $HOME (default: public_html/binhire-platform —
#                              must match the dev subdomain's configured document root minus /public)
#   --force                    Deploy even with uncommitted local changes
#
# Every one of the above can also be supplied as an environment variable
# instead (DB_NAME, DB_USER, DB_PASSWORD, OWNER_PASSWORD,
# STRIPE_WEBHOOK_SECRET, SSH_HOST, SSH_USER, APP_DIR) — flags win if both are
# given. Prefer environment variables for the passwords: a flag value is
# visible to anyone on this Mac who can run `ps`; an env var generally isn't.
#
# Secrets are never printed by this script.

set -euo pipefail

DEV_DOMAIN="dev.launcestonbinhire.com.au"
SSH_HOST="${SSH_HOST:-server.cleverdogdesign.com}"
SSH_USER="${SSH_USER:-lbhire}"
APP_DIR="${APP_DIR:-public_html/binhire-platform}"
DB_NAME="${DB_NAME:-}"
DB_USER="${DB_USER:-}"
DB_PASSWORD="${DB_PASSWORD:-}"
OWNER_PASSWORD="${OWNER_PASSWORD:-}"
STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET:-}"
FORCE=0

for arg in "$@"; do
    case "$arg" in
        --db-name=*) DB_NAME="${arg#*=}" ;;
        --db-user=*) DB_USER="${arg#*=}" ;;
        --db-password=*) DB_PASSWORD="${arg#*=}" ;;
        --owner-password=*) OWNER_PASSWORD="${arg#*=}" ;;
        --stripe-webhook-secret=*) STRIPE_WEBHOOK_SECRET="${arg#*=}" ;;
        --ssh-host=*) SSH_HOST="${arg#*=}" ;;
        --ssh-user=*) SSH_USER="${arg#*=}" ;;
        --app-dir=*) APP_DIR="${arg#*=}" ;;
        --force) FORCE=1 ;;
        -h|--help) sed -n '2,34p' "$0"; exit 0 ;;
        *) echo "Unknown argument: $arg" >&2; exit 1 ;;
    esac
done

if [[ "$DEV_DOMAIN" == "launcestonbinhire.com.au" || "$DEV_DOMAIN" == *"lbhire.com.au" ]]; then
    echo "Refusing to deploy to a live domain." >&2
    exit 1
fi

missing=()
[[ -z "$DB_NAME" ]] && missing+=(--db-name)
[[ -z "$DB_USER" ]] && missing+=(--db-user)
[[ -z "$DB_PASSWORD" ]] && missing+=(--db-password)
[[ -z "$OWNER_PASSWORD" ]] && missing+=(--owner-password)
if [[ ${#missing[@]} -gt 0 ]]; then
    echo "Missing required arguments: ${missing[*]}" >&2
    echo "Run with --help for usage." >&2
    exit 1
fi

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

echo "==> Pre-flight checks"

if [[ $FORCE -eq 0 && -n "$(git status --porcelain)" ]]; then
    echo "Working tree has uncommitted changes. Commit/stash first, or pass --force to deploy HEAD anyway." >&2
    exit 1
fi

if [[ -z "$(git ls-files public/build)" ]]; then
    echo "public/build isn't committed — run 'npm run build' and commit the assets before deploying." >&2
    exit 1
fi

php -r '
$lock = json_decode(file_get_contents("composer.lock"), true);
$bad = [];
foreach ($lock["packages"] ?? [] as $p) {
    if (isset($p["require"]["php"]) && version_compare(str_replace(["^", ">=", "~"], "", explode(" ", $p["require"]["php"])[0]), "8.4", ">=")) {
        $bad[] = $p["name"]." requires php ".$p["require"]["php"];
    }
}
if ($bad) {
    fwrite(STDERR, "WARNING: composer.lock has packages requiring PHP 8.4+:\n");
    foreach (array_slice($bad, 0, 5) as $b) { fwrite(STDERR, "  - $b\n"); }
    fwrite(STDERR, "composer install on a PHP 8.3 server will fail unless the cPanel domain is switched to PHP 8.4+ (MultiPHP Manager).\n");
}
'

echo "==> Reading local .env for values to carry over (not printed)"

read_local_env() {
    local key="$1"
    local line
    line="$(grep -E "^${key}=" .env 2>/dev/null | tail -n1 | cut -d'=' -f2- || true)"
    line="${line%\"}"
    line="${line#\"}"
    printf '%s' "$line"
}

GOOGLE_MAPS_API_KEY="$(read_local_env GOOGLE_MAPS_API_KEY)"
GOOGLE_MAPS_BROWSER_KEY="$(read_local_env GOOGLE_MAPS_BROWSER_KEY)"
STRIPE_KEY="$(read_local_env STRIPE_KEY)"
STRIPE_SECRET="$(read_local_env STRIPE_SECRET)"
STRIPE_PLATFORM_ACCOUNT="$(read_local_env STRIPE_PLATFORM_ACCOUNT)"
CASHIER_CURRENCY="$(read_local_env CASHIER_CURRENCY)"
MAIL_MAILER="$(read_local_env MAIL_MAILER)"
MAIL_SCHEME="$(read_local_env MAIL_SCHEME)"
MAIL_HOST="$(read_local_env MAIL_HOST)"
MAIL_PORT="$(read_local_env MAIL_PORT)"
MAIL_USERNAME="$(read_local_env MAIL_USERNAME)"
MAIL_PASSWORD="$(read_local_env MAIL_PASSWORD)"
MAIL_FROM_ADDRESS="$(read_local_env MAIL_FROM_ADDRESS)"
MAIL_FROM_NAME="$(read_local_env MAIL_FROM_NAME)"

if [[ -z "$STRIPE_SECRET" ]]; then
    echo "Local .env has no STRIPE_SECRET — can't create/verify the Stripe webhook endpoint." >&2
    exit 1
fi

REMOTE_APP_PATH="\$HOME/${APP_DIR}"

echo "==> Checking the existing remote .env (for APP_KEY / webhook secret to preserve)"

EXISTING_ENV="$(ssh "${SSH_USER}@${SSH_HOST}" "cat ${REMOTE_APP_PATH}/.env 2>/dev/null || true")"
EXISTING_APP_KEY="$(printf '%s\n' "$EXISTING_ENV" | grep -E '^APP_KEY=' | tail -n1 | cut -d'=' -f2- || true)"
EXISTING_WEBHOOK_SECRET="$(printf '%s\n' "$EXISTING_ENV" | grep -E '^STRIPE_WEBHOOK_SECRET=' | tail -n1 | cut -d'=' -f2- || true)"

echo "==> Resolving the Stripe webhook endpoint"

WEBHOOK_URL="https://${DEV_DOMAIN}/stripe/webhook"

if [[ -n "$STRIPE_WEBHOOK_SECRET" ]]; then
    : # explicitly supplied — trust it
elif [[ -n "$EXISTING_WEBHOOK_SECRET" ]]; then
    STRIPE_WEBHOOK_SECRET="$EXISTING_WEBHOOK_SECRET"
    echo "    Reusing the webhook secret already on the server."
else
    EXISTING_ENDPOINT_ID="$(curl -fsS -G https://api.stripe.com/v1/webhook_endpoints \
        -u "${STRIPE_SECRET}:" \
        -d "limit=100" \
        | python3 -c "
import json, sys
data = json.load(sys.stdin)
for ep in data.get('data', []):
    if ep.get('url') == '${WEBHOOK_URL}':
        print(ep['id'])
        break
")"

    if [[ -n "$EXISTING_ENDPOINT_ID" ]]; then
        echo "A Stripe webhook endpoint for ${WEBHOOK_URL} already exists (${EXISTING_ENDPOINT_ID})." >&2
        echo "Its signing secret can only be read once, at creation. Pass it with --stripe-webhook-secret, or delete the endpoint in the Stripe Dashboard and re-run." >&2
        exit 1
    fi

    echo "    Creating a new Stripe webhook endpoint for ${WEBHOOK_URL}"
    CREATE_RESPONSE="$(curl -fsS https://api.stripe.com/v1/webhook_endpoints \
        -u "${STRIPE_SECRET}:" \
        -d "url=${WEBHOOK_URL}" \
        -d "enabled_events[]=payment_intent.succeeded" \
        -d "enabled_events[]=payment_intent.payment_failed")"

    STRIPE_WEBHOOK_SECRET="$(printf '%s' "$CREATE_RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['secret'])")"
fi

echo "==> Building the remote .env"

ENV_FILE="$(mktemp)"
trap 'rm -f "$ENV_FILE"' EXIT

{
    echo "APP_NAME=\"Bin Hire Platform\""
    echo "APP_ENV=production"
    echo "APP_KEY=${EXISTING_APP_KEY}"
    echo "APP_DEBUG=false"
    echo "APP_URL=https://${DEV_DOMAIN}"
    echo
    echo "APP_LOCALE=en"
    echo "APP_FALLBACK_LOCALE=en"
    echo "APP_FAKER_LOCALE=en_US"
    echo
    echo "APP_MAINTENANCE_DRIVER=file"
    echo "BCRYPT_ROUNDS=12"
    echo
    echo "LOG_CHANNEL=stack"
    echo "LOG_STACK=single"
    echo "LOG_DEPRECATIONS_CHANNEL=null"
    echo "LOG_LEVEL=warning"
    echo
    echo "DB_CONNECTION=mysql"
    echo "DB_HOST=127.0.0.1"
    echo "DB_PORT=3306"
    echo "DB_DATABASE=${DB_NAME}"
    echo "DB_USERNAME=${DB_USER}"
    echo "DB_PASSWORD=${DB_PASSWORD}"
    echo
    echo "SESSION_DRIVER=database"
    echo "SESSION_LIFETIME=120"
    echo "SESSION_ENCRYPT=false"
    echo "SESSION_PATH=/"
    echo "SESSION_DOMAIN=null"
    echo
    echo "BROADCAST_CONNECTION=log"
    echo "FILESYSTEM_DISK=local"
    echo "QUEUE_CONNECTION=database"
    echo
    echo "CACHE_STORE=database"
    echo
    echo "MAIL_MAILER=${MAIL_MAILER}"
    echo "MAIL_SCHEME=${MAIL_SCHEME}"
    echo "MAIL_HOST=${MAIL_HOST}"
    echo "MAIL_PORT=${MAIL_PORT}"
    echo "MAIL_USERNAME=${MAIL_USERNAME}"
    echo "MAIL_PASSWORD=${MAIL_PASSWORD}"
    echo "MAIL_FROM_ADDRESS=\"${MAIL_FROM_ADDRESS}\""
    echo "MAIL_FROM_NAME=\"${MAIL_FROM_NAME}\""
    echo
    echo "VITE_APP_NAME=\"Bin Hire Platform\""
    echo
    echo "GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}"
    echo "GOOGLE_MAPS_BROWSER_KEY=${GOOGLE_MAPS_BROWSER_KEY}"
    echo
    echo "STRIPE_KEY=${STRIPE_KEY}"
    echo "STRIPE_SECRET=${STRIPE_SECRET}"
    echo "STRIPE_PLATFORM_ACCOUNT=${STRIPE_PLATFORM_ACCOUNT}"
    echo "STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}"
    echo "CASHIER_CURRENCY=${CASHIER_CURRENCY}"
} > "$ENV_FILE"

echo "==> Syncing the committed tree to ${SSH_USER}@${SSH_HOST}:${APP_DIR}"

ssh "${SSH_USER}@${SSH_HOST}" "mkdir -p ${REMOTE_APP_PATH}"
git archive HEAD | ssh "${SSH_USER}@${SSH_HOST}" "tar -xf - -C ${REMOTE_APP_PATH}"

echo "==> Uploading .env (not printed)"

ssh "${SSH_USER}@${SSH_HOST}" "cat > ${REMOTE_APP_PATH}/.env" < "$ENV_FILE"
rm -f "$ENV_FILE"
trap - EXIT

echo "==> Running the remote build steps"

# shellcheck disable=SC2087
# ssh joins everything after the host into one string for the remote shell to
# re-parse, so an OWNER_PASSWORD containing spaces or shell metacharacters
# would corrupt that command line if passed through raw. Base64 is the only
# thing crossing that boundary; the real value is decoded back inside the
# remote script, which — unlike the ssh command line — is parsed once, by
# bash, from the heredoc text exactly as quoted below.
OWNER_PASSWORD_B64="$(printf '%s' "$OWNER_PASSWORD" | base64 | tr -d '\n')"

ssh "${SSH_USER}@${SSH_HOST}" OWNER_PASSWORD_B64="$OWNER_PASSWORD_B64" bash -s -- "$APP_DIR" <<'REMOTE'
set -euo pipefail
APP_DIR="$1"
cd "$HOME/$APP_DIR"
OWNER_PASSWORD="$(printf '%s' "$OWNER_PASSWORD_B64" | base64 -d)"

PHP_BIN=""
# This app's composer.lock pulls in Symfony packages that require PHP
# >=8.4.1, so an ea-php83 binary is tried last, not first — picking it would
# get us all the way to `composer install` before failing.
for candidate in /opt/cpanel/ea-php84/root/usr/bin/php php84 /opt/cpanel/ea-php83/root/usr/bin/php php83 php; do
    if command -v "$candidate" >/dev/null 2>&1; then
        PHP_BIN="$candidate"
        break
    fi
done
if [[ -z "$PHP_BIN" ]]; then
    echo "No PHP binary found on the server." >&2
    exit 1
fi
echo "Using PHP: $("$PHP_BIN" -v | head -n1)"

COMPOSER_BIN=""
for candidate in /opt/cpanel/composer/bin/composer composer; do
    if command -v "$candidate" >/dev/null 2>&1; then
        COMPOSER_BIN="$candidate"
        break
    fi
done
if [[ -z "$COMPOSER_BIN" ]]; then
    if [[ ! -f "$HOME/bin/composer.phar" ]]; then
        mkdir -p "$HOME/bin"
        "$PHP_BIN" -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
        "$PHP_BIN" composer-setup.php --install-dir="$HOME/bin" --filename=composer.phar
        rm -f composer-setup.php
    fi
    COMPOSER_BIN="$PHP_BIN $HOME/bin/composer.phar"
fi

mkdir -p storage/app/public storage/framework/cache/data storage/framework/sessions storage/framework/views storage/logs bootstrap/cache
chmod -R 775 storage bootstrap/cache

export COMPOSER_MEMORY_LIMIT=-1

# Fails in one clear line here instead of partway through `composer install`
# if the resolved PHP binary is still older than composer.lock requires.
$COMPOSER_BIN check-platform-reqs --no-dev

$COMPOSER_BIN install --no-dev --optimize-autoloader --no-interaction

# Clears any config cache left over from a previous run before anything
# below reads config — otherwise a stale cached config.php (not part of the
# git archive, so a prior run's copy survives on disk) could shadow the .env
# we just uploaded. Not optimize:clear: that also runs cache:clear, which on
# CACHE_STORE=database issues a DELETE against a table migrate hasn't
# created yet on a brand new database.
"$PHP_BIN" artisan config:clear

if ! grep -qE '^APP_KEY=.+' .env; then
    "$PHP_BIN" artisan key:generate --force
fi

"$PHP_BIN" artisan migrate --force

# LaunchestonBinHireSeeder is safe to run every time: every raw ::create()
# in it (User, Depot, Truck, Hero) is already gated on the tenant not having
# bin types yet, and Bin::create() is gated per bin type on that type having
# no bins yet — everything else is updateOrCreate. A coarser "skip if the
# tenant row exists" guard here was actively wrong: a deploy that dies after
# Tenant::firstOrCreate() but before the seeder finishes (as the pre-fix
# fake()-in-production crash did) leaves the tenant row present with the
# owner user and everything after it still unseeded, and that guard would
# then skip seeding forever, leaving user:set-password with no user to find.
"$PHP_BIN" artisan db:seed --force

"$PHP_BIN" artisan tenant:add-domain launceston-bin-hire dev.launcestonbinhire.com.au
OWNER_PASSWORD="$OWNER_PASSWORD" "$PHP_BIN" artisan user:set-password owner@launcestonbinhire.com.au

[[ -L public/storage ]] || "$PHP_BIN" artisan storage:link

"$PHP_BIN" artisan optimize
"$PHP_BIN" artisan queue:restart

echo "Cashier webhook secret configured: $("$PHP_BIN" artisan tinker --execute 'echo blank(config("cashier.webhook.secret")) ? "NO" : "YES";')"
echo
echo "Crontab lines — use this exact PHP binary, not a guess:"
echo "* * * * * cd \$HOME/${APP_DIR} && $(command -v "$PHP_BIN" || echo "$PHP_BIN") artisan queue:work database --stop-when-empty --max-time=55 --tries=3 >> /dev/null 2>&1"
echo "* * * * * cd \$HOME/${APP_DIR} && $(command -v "$PHP_BIN" || echo "$PHP_BIN") artisan schedule:run >> /dev/null 2>&1"
REMOTE

echo "==> Verifying (dev subdomain must already point its docroot at ${APP_DIR}/public in cPanel)"

status() {
    curl -sS -o /dev/null -w '%{http_code}' "$1" || echo "curl-failed"
}

HOME_STATUS="$(status "https://${DEV_DOMAIN}/")"
BOOK_STATUS="$(status "https://${DEV_DOMAIN}/book")"
LOGIN_STATUS="$(status "https://${DEV_DOMAIN}/dashboard/login")"

echo "Home page:        ${HOME_STATUS}"
echo "Booking step:     ${BOOK_STATUS}"
echo "Dashboard login:  ${LOGIN_STATUS}"
echo
echo "Done."
