# E2E Testing Pipeline using Native Woodpecker Services
# No Docker-in-Docker - uses pre-built images for fast startup

kind: pipeline
type: docker
name: e2e

when:
  event: pull_request
  path:
    exclude:
      - "tools/**"
      - "onchain/test/FitnessEvaluator*"
      - "docs/**"
      - "formulas/**"
      - "evidence/**"
      - ".codeberg/**"
      - "*.md"

clone:
  git:
    image: woodpeckerci/plugin-git
    settings:
      depth: 50
      reference: /git-mirrors/harb.git
      netrc_machine: codeberg.org
      netrc_username: johba
      netrc_password:
        from_secret: codeberg_token

# All background services - services get proper DNS resolution in Woodpecker
# Note: Services can't depend on steps, so they wait internally for contracts.env
services:
  # PostgreSQL for Ponder
  - name: postgres
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ponder
      POSTGRES_PASSWORD: ponder_local
      POSTGRES_DB: ponder_local

  # Anvil blockchain fork
  - name: anvil
    image: ghcr.io/foundry-rs/foundry:latest
    entrypoint:
      - anvil
      - --host=0.0.0.0
      - --port=8545
      - --fork-url=https://sepolia.base.org
      - --fork-block-number=20000000
      - --chain-id=31337
      - --accounts=10
      - --balance=10000

  # Ponder indexer - waits for contracts.env from bootstrap
  - name: ponder
    image: registry.niovi.voyage/harb/ponder-ci:latest
    commands:
      - |
        set -eu

        # Wait for contracts.env (bootstrap writes it after deploying)
        echo "=== Waiting for contracts.env ==="
        for i in $(seq 1 120); do
          if [ -f /woodpecker/src/contracts.env ]; then
            echo "Found contracts.env after $i attempts"
            break
          fi
          echo "Waiting for contracts.env... ($i/120)"
          sleep 3
        done

        if [ ! -f /woodpecker/src/contracts.env ]; then
          echo "ERROR: contracts.env not found after 6 minutes"
          exit 1
        fi

        # Source contract addresses from bootstrap
        . /woodpecker/src/contracts.env
        echo "=== Contract addresses ==="
        echo "KRAIKEN=$KRAIKEN"
        echo "STAKE=$STAKE"
        echo "START_BLOCK=$START_BLOCK"

        # Export env vars required by ponder
        export DATABASE_URL="$DATABASE_URL"
        export DATABASE_SCHEMA="ponder_ci_$START_BLOCK"
        export START_BLOCK="$START_BLOCK"
        export KRAIKEN_ADDRESS="$KRAIKEN"
        export STAKE_ADDRESS="$STAKE"
        export LM_ADDRESS="${LIQUIDITY_MANAGER:-0x0000000000000000000000000000000000000000}"
        export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
        export PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK="$PONDER_RPC_URL_1"
        export PONDER_RPC_URL_1="$PONDER_RPC_URL_1"

        # Overlay kraiken-lib and ponder source from workspace
        # CI_WORKSPACE points to the repo checkout directory
        WS="${CI_WORKSPACE:-$(pwd)}"
        echo "=== Workspace: $WS ==="

        echo "=== Overlaying kraiken-lib from workspace ==="
        if [ -d "$WS/kraiken-lib/dist" ]; then
          cp -r "$WS/kraiken-lib/dist/." /app/kraiken-lib/dist/
          cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
          echo "kraiken-lib updated from workspace (src + dist)"
        elif [ -d "$WS/kraiken-lib/src" ]; then
          cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
          echo "kraiken-lib/src updated (dist not available — may need rebuild)"
        else
          echo "WARNING: kraiken-lib not found at $WS/kraiken-lib"
        fi

        echo "=== Overlaying ponder source from workspace ==="
        # Copy individual source files (not the directory itself) to avoid nested src/src/
        if [ -d "$WS/services/ponder/src" ]; then
          cp -r "$WS/services/ponder/src/." /app/services/ponder/src/
          echo "ponder/src files updated from workspace"
        fi
        for f in ponder.schema.ts ponder.config.ts; do
          if [ -f "$WS/services/ponder/$f" ]; then
            cp "$WS/services/ponder/$f" /app/services/ponder/"$f"
            echo "ponder/$f updated from workspace"
          fi
        done

        echo "=== Starting Ponder (pre-built image + workspace overlay) ==="
        cd /app/services/ponder
        {
          echo "DATABASE_URL=${DATABASE_URL}"
          echo "PONDER_RPC_URL_1=${PONDER_RPC_URL_1}"
          echo "DATABASE_SCHEMA=${DATABASE_SCHEMA}"
          echo "START_BLOCK=${START_BLOCK}"
        } > .env.local
        # Use 'start' mode in CI — 'dev' mode watches for file changes and causes
        # a hot-restart loop when workspace overlay modifies source files
        exec npm run start

  # Webapp - waits for contracts.env from bootstrap
  - name: webapp
    image: registry.niovi.voyage/harb/webapp-ci:latest
    environment:
      CI: "true"
    commands:
      - |
        set -eu

        # Wait for contracts.env (bootstrap writes it after deploying)
        echo "=== Waiting for contracts.env ==="
        for i in $(seq 1 120); do
          if [ -f /woodpecker/src/contracts.env ]; then
            echo "Found contracts.env after $i attempts"
            break
          fi
          echo "Waiting for contracts.env... ($i/120)"
          sleep 3
        done

        if [ ! -f /woodpecker/src/contracts.env ]; then
          echo "ERROR: contracts.env not found after 6 minutes"
          exit 1
        fi

        # Source contract addresses from bootstrap
        . /woodpecker/src/contracts.env

        # Export environment variables for Vite
        export VITE_KRAIKEN_ADDRESS="$KRAIKEN"
        export VITE_STAKE_ADDRESS="$STAKE"
        export VITE_DEFAULT_CHAIN_ID=31337
        export VITE_LOCAL_RPC_PROXY_TARGET=http://anvil:8545
        export VITE_LOCAL_GRAPHQL_PROXY_TARGET=http://ponder:42069
        # Default is the Sepolia SwapRouter; override via VITE_SWAP_ROUTER env var for other networks.
        export VITE_SWAP_ROUTER="${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4}"
        export VITE_ENABLE_LOCAL_SWAP=true
        export VITE_BASE_PATH=/app/

        # Overlay kraiken-lib from workspace (may be newer than baked-in image)
        WS="${CI_WORKSPACE:-$(pwd)}"
        echo "=== Overlaying kraiken-lib from workspace ==="
        if [ -d "$WS/kraiken-lib/dist" ]; then
          cp -r "$WS/kraiken-lib/dist/." /app/kraiken-lib/dist/
          cp -r "$WS/kraiken-lib/src/." /app/kraiken-lib/src/
          echo "kraiken-lib updated from workspace (src + dist)"
        elif [ -d /app/kraiken-lib/src ]; then
          echo "kraiken-lib/src found in image (using baked-in version)"
        else
          echo "ERROR: kraiken-lib/src not found!"
          exit 1
        fi

        # Overlay webapp source from workspace (ensures CI tests current branch)
        echo "=== Overlaying webapp source from workspace ==="
        if [ -d "$WS/web-app/src" ]; then
          cp -r "$WS/web-app/src/." /app/web-app/src/
          echo "webapp/src updated from workspace"
        fi
        for f in vite.config.ts vite.config.js; do
          if [ -f "$WS/web-app/$f" ]; then
            cp "$WS/web-app/$f" /app/web-app/"$f"
            echo "webapp/$f updated from workspace"
          fi
        done

        # Overlay @harb/web3 shared package from workspace
        if [ -d "$WS/packages/web3" ]; then
          mkdir -p /app/packages/web3
          cp -r "$WS/packages/web3/." /app/packages/web3/
          # Link @harb/web3 into web-app node_modules
          mkdir -p /app/web-app/node_modules/@harb
          ln -sf /app/packages/web3 /app/web-app/node_modules/@harb/web3
          # Symlink wagmi/viem into packages dir so @harb/web3 can resolve them
          mkdir -p /app/packages/web3/node_modules
          ln -sf /app/web-app/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi
          ln -sf /app/web-app/node_modules/viem /app/packages/web3/node_modules/viem
          echo "@harb/web3 linked with wagmi/viem deps"
        fi

        # Overlay @harb/utils shared package from workspace
        if [ -d "$WS/packages/utils" ]; then
          mkdir -p /app/packages/utils
          cp -r "$WS/packages/utils/." /app/packages/utils/
          # Link @harb/utils into web-app node_modules
          mkdir -p /app/web-app/node_modules/@harb
          ln -sf /app/packages/utils /app/web-app/node_modules/@harb/utils
          # Symlink viem into packages dir so @harb/utils can resolve it
          mkdir -p /app/packages/utils/node_modules
          ln -sf /app/web-app/node_modules/viem /app/packages/utils/node_modules/viem
          echo "@harb/utils linked with viem dep"
        fi

        # Overlay @harb/analytics shared package from workspace
        if [ -d "$WS/packages/analytics" ]; then
          mkdir -p /app/packages/analytics
          cp -r "$WS/packages/analytics/." /app/packages/analytics/
          mkdir -p /app/web-app/node_modules/@harb
          ln -sf /app/packages/analytics /app/web-app/node_modules/@harb/analytics
          echo "@harb/analytics linked for webapp"
        fi

        echo "=== Starting webapp (pre-built image + source overlay) ==="
        cd /app/web-app
        # Explicitly set CI=true to disable Vue DevTools in vite.config.ts
        # (prevents 500 errors from devtools path resolution in CI environment)
        export CI=true
        echo "CI=$CI (should be 'true' to disable Vue DevTools)"
        exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/

  # Landing page - no contracts needed, starts immediately
  - name: landing
    image: registry.niovi.voyage/harb/landing-ci:latest
    commands:
      - |
        set -eu
        # Overlay landing source from workspace
        WS="${CI_WORKSPACE:-$(pwd)}"
        if [ -d "$WS/landing/src" ]; then
          cp -r "$WS/landing/src/." /app/landing/src/
          echo "landing/src updated from workspace"
        fi
        for f in vite.config.ts vite.config.js; do
          if [ -f "$WS/landing/$f" ]; then
            cp "$WS/landing/$f" /app/landing/"$f"
            echo "landing/$f updated from workspace"
          fi
        done

        # Overlay @harb/web3 shared package
        if [ -d "$WS/packages/web3" ]; then
          mkdir -p /app/packages/web3
          cp -r "$WS/packages/web3/." /app/packages/web3/
          # Landing CI image doesn't have wagmi — install it
          cd /app/landing
          npm install --no-audit --no-fund @wagmi/vue viem 2>/dev/null || true
          # Link @harb/web3
          mkdir -p /app/landing/node_modules/@harb
          ln -sf /app/packages/web3 /app/landing/node_modules/@harb/web3
          # Symlink wagmi/viem into packages dir for resolution
          mkdir -p /app/packages/web3/node_modules
          ln -sf /app/landing/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi 2>/dev/null || true
          ln -sf /app/landing/node_modules/viem /app/packages/web3/node_modules/viem 2>/dev/null || true
          echo "@harb/web3 linked for landing"
        fi

        # Overlay @harb/ui-shared shared package from workspace
        if [ -d "$WS/packages/ui-shared" ]; then
          mkdir -p /app/packages/ui-shared
          cp -r "$WS/packages/ui-shared/." /app/packages/ui-shared/
          # Link @harb/ui-shared into landing node_modules
          mkdir -p /app/landing/node_modules/@harb
          ln -sf /app/packages/ui-shared /app/landing/node_modules/@harb/ui-shared
          # Symlink vue into packages dir so @harb/ui-shared can resolve it
          mkdir -p /app/packages/ui-shared/node_modules
          ln -sf /app/landing/node_modules/vue /app/packages/ui-shared/node_modules/vue 2>/dev/null || true
          echo "@harb/ui-shared linked for landing"
        fi

        # Overlay @harb/analytics shared package from workspace
        if [ -d "$WS/packages/analytics" ]; then
          mkdir -p /app/packages/analytics
          cp -r "$WS/packages/analytics/." /app/packages/analytics/
          mkdir -p /app/landing/node_modules/@harb
          ln -sf /app/packages/analytics /app/landing/node_modules/@harb/analytics
          echo "@harb/analytics linked for landing"
        fi

        echo "=== Starting landing (pre-built image + source overlay) ==="
        cd /app/landing
        exec npm run dev -- --host 0.0.0.0 --port 5174

  # Caddy proxy - waits for contracts.env to ensure other services are starting
  - name: caddy
    image: caddy:2.8-alpine
    commands:
      - |
        # Wait briefly for other services to start
        echo "=== Waiting for contracts.env before starting Caddy ==="
        for i in $(seq 1 120); do
          if [ -f /woodpecker/src/contracts.env ]; then
            echo "Found contracts.env, starting Caddy..."
            break
          fi
          echo "Waiting for contracts.env... ($i/120)"
          sleep 3
        done

        printf '%s\n' ':8081 {' \
          '  route /app* {' \
          '    reverse_proxy webapp:5173' \
          '  }' \
          '  route /api/graphql* {' \
          '    uri strip_prefix /api' \
          '    reverse_proxy ponder:42069' \
          '  }' \
          '  route /api/rpc* {' \
          '    uri strip_prefix /api/rpc' \
          '    reverse_proxy anvil:8545' \
          '  }' \
          '  reverse_proxy landing:5174' \
          '}' > /etc/caddy/Caddyfile
        exec caddy run --config /etc/caddy/Caddyfile

steps:
  # Step 0: Install dependencies for onchain compilation
  - name: install-deps
    image: node:20-alpine
    commands:
      - |
        set -eu
        apk add --no-cache git
        echo "=== Installing uni-v3-lib dependencies ==="
        git submodule update --init --recursive
        cd onchain/lib/uni-v3-lib
        npm install

  # Step 1: Wait for base services and deploy contracts
  # Uses pre-built node-ci image with Foundry pre-installed (saves ~60s)
  - name: bootstrap
    image: registry.niovi.voyage/harb/node-ci:latest
    depends_on:
      - install-deps
    commands:
      - |
        # Create a bootstrap wrapper that runs under bash
        # (Woodpecker uses /bin/sh which lacks 'source' and bash-isms)
        export ANVIL_RPC=http://anvil:8545
        export CONTRACT_ENV=/woodpecker/src/contracts.env
        export LOG_FILE=/dev/null
        export ONCHAIN_DIR="$PWD/onchain"
        export TXNBOT_FUND_VALUE=10ether
        export TXNBOT_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
        export TXNBOT_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
        exec bash scripts/ci-bootstrap.sh

  # Step 2: Wait for stack to be healthy (services run in background)
  - name: wait-for-stack
    image: alpine:3.20
    depends_on:
      - bootstrap
    commands:
      - |
        set -eu
        apk add --no-cache curl bash

        echo "=== Waiting for DNS resolution (Docker embedded DNS can be slow under load) ==="
        for svc in ponder webapp landing caddy; do
          for attempt in $(seq 1 60); do
            if getent hosts "$svc" >/dev/null 2>&1; then
              echo "[dns] $svc resolved after $attempt attempts"
              break
            fi
            echo "[dns] ($attempt/60) waiting for $svc DNS..."
            sleep 5
          done
        done

        echo "=== Waiting for stack to be healthy (max 7 min) ==="
        bash scripts/wait-for-service.sh http://ponder:42069/health   420 ponder

        # Wait for ponder to finish historical indexing (not just respond)
        # /ready returns 200 only when fully synced, 503 while indexing
        echo "=== Waiting for Ponder indexing to complete ==="
        for i in $(seq 1 120); do
          HTTP_CODE=$(curl -sf -o /dev/null -w '%{http_code}' --max-time 3 http://ponder:42069/ready 2>/dev/null || echo "000")
          if [ "$HTTP_CODE" = "200" ]; then
            echo "[wait] Ponder fully indexed after $((i * 3))s"
            break
          fi
          if [ "$i" = "120" ]; then
            echo "[wait] WARNING: Ponder not fully indexed after 360s, continuing anyway"
          fi
          echo "[wait] ($i/120) Ponder indexing... (HTTP $HTTP_CODE)"
          sleep 3
        done
        bash scripts/wait-for-service.sh http://webapp:5173/app/      420 webapp
        bash scripts/wait-for-service.sh http://landing:5174/         420 landing
        bash scripts/wait-for-service.sh http://caddy:8081/app/       420 caddy
        echo "=== Stack is healthy ==="

  # Step 3: Run E2E tests — cross-browser matrix
  # Chromium runs all specs (01-07), then Firefox/WebKit/mobile run read-only specs (03,06,07).
  # The matrix is defined in playwright.config.ts via `projects`.
  - name: run-e2e-tests
    image: mcr.microsoft.com/playwright:v1.55.1-jammy
    depends_on:
      - wait-for-stack
    timeout: 1800
    environment:
      STACK_BASE_URL: http://caddy:8081
      STACK_RPC_URL: http://caddy:8081/api/rpc
      STACK_WEBAPP_URL: http://caddy:8081
      STACK_GRAPHQL_URL: http://caddy:8081/api/graphql
      CI: "true"
    commands:
      - |
        set -eux

        echo "=== Checking system resources ==="
        free -h || true
        cat /proc/meminfo | grep -E 'MemTotal|MemAvail' || true

        echo "=== Verifying Playwright browsers ==="
        npx playwright install --dry-run 2>&1 || true
        ls -la /ms-playwright/ 2>/dev/null || echo "No /ms-playwright directory"

        echo "=== Installing test dependencies ==="
        npm config set fund false
        npm config set audit false
        npm ci --no-audit --no-fund

        echo "=== Running E2E tests — cross-browser matrix (workers=1 to limit memory) ==="
        npx playwright test --reporter=list --workers=1

  # Step 4: Collect artifacts
  - name: collect-artifacts
    image: alpine:3.20
    depends_on:
      - run-e2e-tests
    when:
      status:
        - success
        - failure
    commands:
      - |
        set -eu
        apk add --no-cache tar gzip
        mkdir -p artifacts

        if [ -d playwright-report ]; then
          tar -czf artifacts/playwright-report.tgz playwright-report
          echo "Playwright report archived"
        fi

        if [ -d test-results ]; then
          tar -czf artifacts/test-results.tgz test-results
          echo "Test results archived"
        fi

        ls -lh artifacts/ 2>/dev/null || echo "No artifacts"
