#!/bin/bash
#
# E2E Test Runner with Live Progress Reporting
#
# Runs each test file sequentially with real-time progress display.
# Shows sub-steps as they happen (building, deploying, configuring).
# Captures full output per test in e2e/results/<timestamp>/.
#
# Shared infrastructure (DNS, Pebble, apt-cache) is started once
# before the first test and torn down after the last test. Per-test
# containers connect to the shared networks.
#
# Usage:
#   ./e2e/bin/e2e-run              # run all tests
#   ./e2e/bin/e2e-run smoke caddy  # run tests matching these patterns
#

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Package root (where docker/, config/ live)
PKG_DIR="$(dirname "$SCRIPT_DIR")"
# Test directory — can be overridden via E2E_TEST_DIR env var.
# Default: look for tests/ relative to CWD, or fall back to package dir.
E2E_TEST_DIR="${E2E_TEST_DIR:-$(pwd)}"
cd "$PKG_DIR"

SHARED_PROJECT="celilo-e2e-shared"
SHARED_COMPOSE="docker-compose.shared.yml"

# Handle Ctrl+C: kill running test, clean up, and exit immediately.
INTERRUPTED=false
cleanup_and_exit() {
  INTERRUPTED=true
  echo ""
  echo -e "${RED}Interrupted. Cleaning up...${NC}"
  # Kill any running test process
  [ -n "$TEST_PID" ] && kill $TEST_PID 2>/dev/null
  # Remove named pipe
  [ -n "$PIPE" ] && rm -f "$PIPE"
  # Tear down shared infra
  echo -e "${DIM}  Stopping shared infrastructure...${NC}"
  docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
  # Clean up Docker
  docker network prune -f > /dev/null 2>&1 || true
  echo -e "${RED}Aborted.${NC}"
  exit 130
}
trap cleanup_and_exit INT TERM

# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
DIM='\033[2m'
BOLD='\033[1m'
CYAN='\033[0;36m'
NC='\033[0m'

# Create results directory (in the test project, not the package)
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RESULTS_DIR="$E2E_TEST_DIR/results/$TIMESTAMP"
mkdir -p "$RESULTS_DIR"

# Discover test files from E2E_TEST_DIR
TESTS_PATH="$E2E_TEST_DIR/tests"
if [ ! -d "$TESTS_PATH" ]; then
  echo -e "${RED}No tests/ directory found at $TESTS_PATH${NC}"
  echo "Set E2E_TEST_DIR to the directory containing your tests/ folder."
  exit 1
fi

if [ $# -gt 0 ]; then
  TEST_FILES=()
  for pattern in "$@"; do
    for f in "$TESTS_PATH"/*${pattern}*.test.ts; do
      [ -f "$f" ] && TEST_FILES+=("$f")
    done
  done
else
  TEST_FILES=("$TESTS_PATH"/*.test.ts)
fi

TOTAL=${#TEST_FILES[@]}
PASSED=0
FAILED=0
RESULTS=()

echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║  Celilo E2E Tests                                          ║${NC}"
printf  "${BOLD}║  %d test(s) queued — %s                              ║${NC}\n" "$TOTAL" "$(date +%H:%M:%S)"
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""

SUITE_START=$(date +%s)

# ─── Shared Infrastructure Startup ────────────────────────────────────
# The first test will call ensureSharedInfra() which starts the shared
# project. But we also pre-check here so the runner can show progress.
echo -e "${BOLD}Shared Infrastructure${NC}"

# Check if shared infra is already running
SHARED_RUNNING=false
SHARED_CONTAINERS=$(docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" ps -q 2>/dev/null || true)
if [ -n "$SHARED_CONTAINERS" ]; then
  # Verify DNS is healthy
  DNS_CHECK=$(docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" exec -T comcast-resolver dig @127.0.0.1 iamtheinternet.org NS +short +timeout=2 2>/dev/null || true)
  if [ -n "$DNS_CHECK" ]; then
    SHARED_RUNNING=true
    echo -e "  ${GREEN}✔ already running (DNS healthy)${NC}"
  else
    echo -e "  ${DIM}DNS unhealthy, restarting...${NC}"
    docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
  fi
fi

if [ "$SHARED_RUNNING" = false ]; then
  echo -e "  ${DIM}Starting DNS hierarchy, Pebble ACME, apt-cache...${NC}"
  # The actual startup happens inside the first test's ensureSharedInfra()
  # call. We just report it here for visibility.
fi

SHARED_END=$(date +%s)
echo ""

# Track step count per test for percentage
STEP_COUNT=0
ESTIMATED_STEPS=15

# Parse a line of test output and display as a progress step.
show_progress() {
  local line="$1"
  local test_start="$2"
  local step=""

  # Shared infra progress (from ensureSharedInfra)
  if [[ "$line" == *"[e2e] Shared infrastructure ready"* ]]; then
    step="✔ shared infrastructure ready"
  elif [[ "$line" == *"[e2e] Building shared"* ]]; then
    step="building shared infrastructure images"
  elif [[ "$line" == *"[e2e] Starting shared"* ]]; then
    step="starting shared infrastructure"
  elif [[ "$line" == *"[e2e] Cleaning up stale shared"* ]]; then
    step="cleaning up stale shared infra"

  # Per-test container manager progress
  elif [[ "$line" == *"[e2e] Building images"* ]]; then
    step="building test images"
  elif [[ "$line" == *"[e2e] Starting containers"* ]]; then
    step="starting test containers"
  elif [[ "$line" == *"[e2e] Cleaning up"* ]]; then
    step="cleaning up stale test resources"
  elif [[ "$line" == *"[e2e] Waiting for DNS"* ]]; then
    step="waiting for DNS convergence"
  elif [[ "$line" == *"[e2e] Verifying routing"* ]]; then
    step="verifying routing"
  elif [[ "$line" == *"[e2e] Waiting for target"* ]]; then
    step="waiting for target machines"
  elif [[ "$line" == *"[e2e] Waiting for DHCP"* ]]; then
    step="waiting for DHCP"
  elif [[ "$line" == *"[e2e] Initializing celilo"* ]]; then
    step="initializing celilo"
  elif [[ "$line" == *"[e2e] Network ready"* ]]; then
    step="network ready"
  elif [[ "$line" == *"[e2e] Stopping network"* ]]; then
    step="stopping network"

  # Test-level progress (console.log from tests)
  elif [[ "$line" == *"Adding machines"* ]]; then
    step="adding machines"
  elif [[ "$line" == *"Importing modules"* ]]; then
    step="importing modules"
  elif [[ "$line" == *"Configuring modules"* ]]; then
    step="configuring modules"
  elif [[ "$line" == *"Prerequisites ready"* ]]; then
    step="prerequisites ready"

  # Celilo CLI: "Deploying module: namecheap"
  elif [[ "$line" == *"Deploying module:"* ]]; then
    local module=$(echo "$line" | sed -n 's/.*Deploying module: \([^ ]*\).*/\1/p')
    if [ -n "$module" ]; then
      step="deploying $module"
    else
      step="deploying module"
    fi
  # Celilo CLI: "Deploying software..."
  elif [[ "$line" == *"Deploying software"* ]]; then
    step="running Ansible"

  # Test console.log: "[45s] Deploying namecheap..."
  elif [[ "$line" =~ Deploying\ ([a-z_-]+) ]]; then
    step="deploying ${BASH_REMATCH[1]}"

  # Celilo CLI: "✓ Module 'namecheap' deployed successfully"
  elif [[ "$line" == *"deployed successfully"* ]]; then
    local module=$(echo "$line" | sed -n "s/.*Module '\([^']*\)' deployed.*/\1/p")
    step="✔ ${module:-module} deployed"

  # Celilo CLI: "Successfully imported module: namecheap"
  elif [[ "$line" == *"Successfully imported module:"* ]]; then
    local module=$(echo "$line" | sed -n 's/.*imported module: \([^ ]*\).*/\1/p')
    step="✔ ${module:-module} imported"

  # Celilo CLI: "Infrastructure selected: machine"
  elif [[ "$line" == *"Infrastructure selected"* ]]; then
    step="infrastructure selected"
  # Celilo CLI: "Terraform"
  elif [[ "$line" == *"Terraform skipped"* ]]; then
    step="terraform skipped (existing machine)"
  elif [[ "$line" == *"terraform init"* ]] || [[ "$line" == *"Terraform init"* ]]; then
    step="terraform init"
  elif [[ "$line" == *"terraform apply"* ]] || [[ "$line" == *"Terraform apply"* ]]; then
    step="terraform apply"

  # Ansible health retries
  elif [[ "$line" == *"RETRYING"* ]] && [[ "$line" == *"healthy"* ]]; then
    local retries=$(echo "$line" | sed -n 's/.*(\([0-9]*\) retries left).*/\1/p')
    step="waiting for health check ($retries retries left)"

  # Skip noise
  elif [[ "$line" == *"Set config for"* ]]; then return
  elif [[ "$line" == *"Set secret for"* ]]; then return
  elif [[ "$line" == *"Auto-generated"* ]]; then return
  elif [[ "$line" == *"Warning: Permanently added"* ]]; then return

  # Error detection
  elif [[ "$line" == *"Error:"* ]] && [[ "$line" != *"vitest"* ]] && [[ "$line" != *"bun test"* ]] && [[ "$line" != *"ipv4: Address already"* ]]; then
    local err=$(echo "$line" | sed 's/.*Error: //' | cut -c1-70)
    step="✗ $err"
  fi

  if [ -n "$step" ]; then
    STEP_COUNT=$((STEP_COUNT + 1))
    # Update estimate upward if we're exceeding it
    if [ $STEP_COUNT -gt $ESTIMATED_STEPS ]; then
      ESTIMATED_STEPS=$((STEP_COUNT + 5))
    fi
    local pct=$(( (STEP_COUNT * 100) / ESTIMATED_STEPS ))
    [ $pct -gt 99 ] && pct=99  # Don't show 100% until actually done
    local now=$(date +%s)
    local elapsed=$((now - test_start))
    printf "  ${DIM}%3ds${NC} ${CYAN}%2d%%${NC} %s\n" "$elapsed" "$pct" "$step"
  fi
}

for ((i=0; i<TOTAL; i++)); do
  FILE="${TEST_FILES[$i]}"
  NAME=$(basename "$FILE" .test.ts)
  TEST_DIR="$RESULTS_DIR/$NAME"
  mkdir -p "$TEST_DIR"

  # Percentage complete (based on test count, not time)
  PCT=$(( (i * 100) / TOTAL ))
  ELAPSED_TOTAL=$(($(date +%s) - SUITE_START))
  if [ $i -gt 0 ]; then
    AVG_PER_TEST=$((ELAPSED_TOTAL / i))
    REMAINING=$(( (TOTAL - i) * AVG_PER_TEST ))
    ETA_MIN=$((REMAINING / 60))
    ETA_SEC=$((REMAINING % 60))
    ETA_STR=" — ~${ETA_MIN}m ${ETA_SEC}s remaining"
  else
    ETA_STR=""
  fi

  echo -e "${BOLD}[${i}/${TOTAL}] ${CYAN}${PCT}%${NC}${BOLD} ${NAME}${NC}${DIM}${ETA_STR}${NC}"

  STEP_COUNT=0
  TEST_START=$(date +%s)
  PIPE=$(mktemp -u)
  mkfifo "$PIPE"

  # Background: run bun test, merge stdout+stderr, tee to log + pipe
  (bun test --timeout 300000 "$FILE" 2>&1 | tee "$TEST_DIR/output.log" > "$PIPE") &
  TEST_PID=$!

  # Foreground: read pipe line-by-line and show progress
  while IFS= read -r line; do
    show_progress "$line" "$TEST_START"
  done < "$PIPE"

  # Wait for test to finish and get exit code
  wait $TEST_PID 2>/dev/null
  EXIT_CODE=$?
  rm -f "$PIPE"

  TEST_END=$(date +%s)
  DURATION=$((TEST_END - TEST_START))

  if [ $EXIT_CODE -eq 0 ]; then
    echo -e "  ${GREEN}✔ passed (${DURATION}s)${NC}"
    PASSED=$((PASSED + 1))
    RESULTS+=("✓|$NAME|${DURATION}s")
    echo '{"status":"pass","duration":'$DURATION'}' > "$TEST_DIR/result.json"
  else
    echo -e "  ${RED}✗ failed (${DURATION}s)${NC}"
    FAILED=$((FAILED + 1))
    ERROR=$(grep -E "Error:|expected.*to be|Timeout" "$TEST_DIR/output.log" 2>/dev/null | grep -v "vitest\|bun test" | head -1 | sed 's/.*Error: //' | cut -c1-80)
    RESULTS+=("✗|$NAME|${DURATION}s|$ERROR")
    echo '{"status":"fail","duration":'$DURATION',"error":"'"${ERROR//\"/\\\"}"'"}' > "$TEST_DIR/result.json"
    echo -e "  ${RED}  └─ ${ERROR}${NC}"
  fi
  echo ""

  # Clean up per-test Docker resources between tests (not shared infra)
  docker network prune -f > /dev/null 2>&1 || true
done

# ─── Shared Infrastructure Teardown ───────────────────────────────────
echo -e "${DIM}Stopping shared infrastructure...${NC}"
docker compose -f "$SHARED_COMPOSE" -p "$SHARED_PROJECT" down --volumes --remove-orphans > /dev/null 2>&1 || true
docker network prune -f > /dev/null 2>&1 || true

SUITE_END=$(date +%s)
SUITE_DURATION=$((SUITE_END - SUITE_START))
SUITE_MINUTES=$((SUITE_DURATION / 60))
SUITE_SECONDS=$((SUITE_DURATION % 60))

echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
printf  "${BOLD}║  Results: ${GREEN}%d passed${NC}${BOLD}, ${RED}%d failed${NC}${BOLD} — %dm %ds total             ║${NC}\n" "$PASSED" "$FAILED" "$SUITE_MINUTES" "$SUITE_SECONDS"
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"

for result in "${RESULTS[@]}"; do
  IFS='|' read -r icon name duration error <<< "$result"
  if [ "$icon" = "✓" ]; then
    printf "${BOLD}║${NC}  ${GREEN}%s${NC} %-28s ${DIM}%6s${NC}\n" "$icon" "$name" "$duration"
  else
    printf "${BOLD}║${NC}  ${RED}%s${NC} %-28s ${DIM}%6s${NC}\n" "$icon" "$name" "$duration"
    if [ -n "$error" ]; then
      printf "${BOLD}║${NC}    ${RED}└─ %s${NC}\n" "$error"
    fi
  fi
done

echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "Full logs: ${DIM}$RESULTS_DIR${NC}"

# Write summary
cat > "$RESULTS_DIR/summary.json" <<EOF
{
  "timestamp": "$TIMESTAMP",
  "total": $TOTAL,
  "passed": $PASSED,
  "failed": $FAILED,
  "duration": $SUITE_DURATION
}
EOF

# Add results dir to gitignore if not already there
grep -q "^results/" "$E2E_TEST_DIR/.gitignore" 2>/dev/null || echo "results/" >> "$E2E_TEST_DIR/.gitignore"

# Exit with failure if any tests failed
[ $FAILED -eq 0 ]
