#!/usr/bin/env bash
# deploy-canary.sh — zero-downtime canary deployment for Docker services.
#
# Starts the new version alongside the old one, runs health checks,
# then cuts traffic over. If health checks fail, the new container
# is removed and the old one keeps serving. No load balancer required —
# uses Docker's port mapping to swap atomically.
#
# Usage: ./deploy-canary.sh myapp:v2.1

set -euo pipefail

APP_NAME="${1%%:*}"
NEW_IMAGE="$1"
PORT="${PORT:-3000}"
HEALTH_ENDPOINT="${HEALTH_ENDPOINT:-/api/health}"
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-30}"
CANARY_PORT=0  # Docker assigns random available port

log() { printf "\033[1;34m[canary]\033[0m %s\n" "$*"; }
err() { printf "\033[1;31m[canary]\033[0m %s\n" "$*" >&2; }

# ── Preflight ───────────────────────────────────────────────
OLD_CONTAINER=$(docker ps -q --filter "name=^${APP_NAME}$")
if [ -z "$OLD_CONTAINER" ]; then
    err "No running container named '$APP_NAME'. Use regular deploy."
    exit 1
fi

OLD_IMAGE=$(docker inspect --format='{{.Config.Image}}' "$OLD_CONTAINER")
log "Current: $OLD_IMAGE"
log "Canary:  $NEW_IMAGE"

# ── Pull new image ──────────────────────────────────────────
log "Pulling $NEW_IMAGE..."
docker pull "$NEW_IMAGE" --quiet

# ── Start canary on random port ─────────────────────────────
CANARY_NAME="${APP_NAME}-canary-$$"
log "Starting canary container: $CANARY_NAME"

docker run -d \
    --name "$CANARY_NAME" \
    -p "${CANARY_PORT}:${PORT}" \
    --env-file "${ENV_FILE:-.env}" \
    "$NEW_IMAGE" > /dev/null

CANARY_MAPPED=$(docker port "$CANARY_NAME" "$PORT" | head -1 | cut -d: -f2)
log "Canary running on port $CANARY_MAPPED"

# ── Health check loop ───────────────────────────────────────
log "Running health checks (${HEALTH_TIMEOUT}s timeout)..."
SECONDS=0
HEALTHY=false

while (( SECONDS < HEALTH_TIMEOUT )); do
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
        "http://localhost:${CANARY_MAPPED}${HEALTH_ENDPOINT}" 2>/dev/null || echo "000")

    if [ "$STATUS" = "200" ]; then
        HEALTHY=true
        break
    fi

    sleep 1
done

if [ "$HEALTHY" = false ]; then
    err "Health checks failed after ${HEALTH_TIMEOUT}s. Rolling back."
    docker logs "$CANARY_NAME" --tail 20
    docker stop "$CANARY_NAME" > /dev/null && docker rm "$CANARY_NAME" > /dev/null
    exit 1
fi

log "Health checks passed. Swapping traffic..."

# ── Atomic swap ─────────────────────────────────────────────
docker stop "$APP_NAME" > /dev/null
docker rm "$APP_NAME" > /dev/null

# Stop canary, restart with production name and port
docker stop "$CANARY_NAME" > /dev/null
docker rm "$CANARY_NAME" > /dev/null

docker run -d \
    --name "$APP_NAME" \
    -p "${PORT}:${PORT}" \
    --env-file "${ENV_FILE:-.env}" \
    --restart unless-stopped \
    "$NEW_IMAGE" > /dev/null

log "Deployed $NEW_IMAGE as $APP_NAME on port $PORT"
log "Old image $OLD_IMAGE is still available for manual rollback."
