name: CI Pipeline
run-name: "${{ gitea.actor }} triggered CI pipeline"

on:
  workflow_dispatch: {}  # Manual trigger only - run tests locally before merging
  # Auto-runs disabled for solo development
  # Re-enable when ready for collaborators:
  # pull_request:
  #   branches: [main]
  #   types: [opened, reopened, synchronize]

env:
  NODE_VERSION: "20"
  NEXT_TELEMETRY_DISABLED: "1"

jobs:
  test:
    runs-on: [self-hosted, macos-latest]
    env:
      NODE_OPTIONS: "--max_old_space_size=8192 --max_semi_space_size=128"
      CI: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false
      - run: npm test -- --reporter=dot --maxConcurrency=1
      # If the Codecov Action fails on Gitea, replace this with the bash uploader below
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          token: "${{ secrets.CODECOV_TOKEN }}"
          files: ./coverage/lcov.info
          flags: unittests

      # Bash uploader alternative (uncomment if the action above has issues)
      # - name: Upload coverage to Codecov (bash)
      #   run: |
      #     curl -s https://codecov.io/bash > codecov.sh
      #     bash codecov.sh -t "${{ secrets.CODECOV_TOKEN }}" -f coverage/lcov.info -F unittests

  e2e:
    runs-on: [self-hosted, macos-latest]
    strategy:
      matrix:
        browser: [chromium, firefox, webkit]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false
      - name: Install Playwright
        run: "npx playwright install --with-deps ${{ matrix.browser }}"
      - run: npm run build

      - name: E2E (start + test + teardown)
        run: |
          set -euo pipefail

          export PORT="${PORT:-3010}"
          export HOST="127.0.0.1"
          mkdir -p .next

          # ensure build exists
          test -d .next || { echo "❌ Missing .next build output"; exit 1; }

          echo "🚀 Starting Next.js server for E2E testing..."

          # Start Next directly with node so $! is the real node PID
          node node_modules/next/dist/bin/next start -p "$PORT" -H "$HOST" > .next/runner.log 2>&1 &
          SVPID=$!
          echo "$SVPID" > .next/runner.pid
          echo "🌐 Server PID: $SVPID"

          # Wait for readiness
          echo "⏳ Waiting for server to be ready..."
          npx wait-on -t 120000 "tcp:$HOST:$PORT"
          curl -fsS "http://$HOST:$PORT" >/dev/null
          echo "✅ App is responding at http://$HOST:$PORT"

          # Run tests
          echo "🧪 Running E2E tests for ${{ matrix.browser }}..."
          BASE_URL="http://$HOST:$PORT" npx playwright test --project=${{ matrix.browser }} --reporter=list || TEST_EXIT_CODE=$?

          # Teardown
          echo "🧹 Cleaning up server..."
          kill "$SVPID" 2>/dev/null || true
          echo "✅ Server cleanup complete"
        env:
          NEXT_TELEMETRY_DISABLED: "1"
          NODE_ENV: production
          NODE_OPTIONS: "--max-old-space-size=8192"

      # package artifacts (keeps file count small)
      - name: Package E2E artifacts
        if: failure()
        run: |
          tar -czf playwright-${{ matrix.browser }}.tgz playwright-report test-results || true

      - name: Upload E2E artifacts
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-results-${{ matrix.browser }}
          path: playwright-${{ matrix.browser }}.tgz
          retention-days: 30

  visual-regression:
    runs-on: [self-hosted, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false
      - name: Install Playwright
        run: npx playwright install --with-deps
      - run: npm run build
      # 1) Sanity check that the build exists
      - name: Verify Next build output
        run: |
          set -euo pipefail
          ls -la .next || true
          test -f .next/BUILD_ID || (echo "No Next build output (.next) – did build fail?" && exit 1)

      - name: Visual Regression (start + test + teardown)
        run: |
          set -euo pipefail

          export PORT="${PORT:-3010}"
          export HOST="127.0.0.1"
          mkdir -p .next

          # ensure build exists
          test -d .next || { echo "❌ Missing .next build output"; exit 1; }

          echo "🚀 Starting Next.js server for visual regression testing..."

          # Ensure port is free before starting
          echo "🔍 Checking if port $PORT is available..."
          if lsof -ti:$PORT >/dev/null 2>&1; then
            echo "⚠️  Port $PORT is in use, killing existing processes..."
            lsof -ti:$PORT | xargs kill -9 2>/dev/null || true
            sleep 2
          fi

          # Start Next with explicit memory settings for CI stability
          echo "🚀 Starting Next.js server on $HOST:$PORT..."

          # Set environment variable and start server
          export NODE_OPTIONS="--max-old-space-size=4096"
          nohup node node_modules/next/dist/bin/next start -p "$PORT" -H "$HOST" > .next/runner.log 2>&1 &
          SVPID=$!
          echo "$SVPID" > .next/runner.pid
          echo "🌐 Server PID: $SVPID"

          # Give the server a moment to start
          sleep 5

          # Check if the server process is still running
          if ! kill -0 "$SVPID" 2>/dev/null; then
            echo "❌ Server process died immediately after starting"
            echo "📋 Server logs:"
            cat .next/runner.log || true
            exit 1
          fi
          echo "✅ Server process is running (PID: $SVPID)"

          # Wait for readiness with better error handling
          echo "⏳ Waiting for server to be ready..."
          npx wait-on -t 120000 "tcp:$HOST:$PORT"

          # Verify server is actually responding to all test routes
          echo "🔍 Verifying server readiness for all test routes..."
          for i in {1..15}; do
            # Check all routes that will be tested in visual regression
            if curl -fsS "http://$HOST:$PORT" >/dev/null 2>&1 && \
               curl -fsS "http://$HOST:$PORT/blog" >/dev/null 2>&1 && \
               curl -fsS "http://$HOST:$PORT/blog/resolving-active-conflicts" >/dev/null 2>&1; then
              echo "✅ App is responding to all test routes at http://$HOST:$PORT"
              break
            else
              echo "⏳ Attempt $i/15: Server not ready for all routes yet, waiting..."
              sleep 3
              if [ $i -eq 15 ]; then
                echo "❌ Server failed to respond to all routes after 15 attempts"
                echo "📋 Server logs:"
                cat .next/runner.log || true
                echo "🔍 Testing individual routes:"
                curl -I "http://$HOST:$PORT" || echo "❌ Homepage failed"
                curl -I "http://$HOST:$PORT/blog" || echo "❌ Blog failed"
                curl -I "http://$HOST:$PORT/blog/resolving-active-conflicts" || echo "❌ Blog post failed"
                exit 1
              fi
            fi
          done

          # Give server a moment to fully settle after all routes are ready
          echo "⏳ Allowing server to fully settle..."
          sleep 10

          # Final verification that server is still responding
          echo "🔍 Final server health check..."
          if ! curl -fsS "http://$HOST:$PORT" >/dev/null 2>&1; then
            echo "❌ Server health check failed after settlement period"
            echo "📋 Server logs:"
            cat .next/runner.log || true
            exit 1
          fi
          echo "✅ Server is healthy and ready for tests"

          # Run visual regression tests with server monitoring
          echo "🧪 Running visual regression tests..."

          # Start comprehensive server monitoring in background
          (
            while true; do
              # Check if server process is still running
              if ! kill -0 "$SVPID" 2>/dev/null; then
                echo "❌ Server process died during test execution"
                echo "📋 Server logs:"
                cat .next/runner.log || true
                break
              fi
              
              # Check if server is responding
              if ! curl -fsS "http://$HOST:$PORT" >/dev/null 2>&1; then
                echo "⚠️  Server health check failed - server may have crashed"
                echo "📋 Current server logs:"
                tail -20 .next/runner.log || true
                break
              fi
              sleep 5
            done
          ) &
          HEALTH_PID=$!

          # Run tests with increased timeout and conservative settings for CI stability
          BASE_URL="http://$HOST:$PORT" npx playwright test tests/e2e/visual-regression.spec.ts --timeout=120000 --workers=1 --retries=1

          # Stop health monitoring
          kill $HEALTH_PID 2>/dev/null || true

          # Teardown with better error handling
          echo "🧹 Cleaning up server..."
          kill "$SVPID" 2>/dev/null || true

          # Wait for server to actually stop
          for i in {1..10}; do
            if ! kill -0 "$SVPID" 2>/dev/null; then
              echo "✅ Server process stopped"
              break
            else
              echo "⏳ Waiting for server to stop... ($i/10)"
              sleep 2
              if [ $i -eq 10 ]; then
                echo "⚠️  Force killing server process"
                kill -9 "$SVPID" 2>/dev/null || true
              fi
            fi
          done

          echo "✅ Server cleanup complete"
        env:
          NEXT_TELEMETRY_DISABLED: "1"
          NODE_ENV: production
          NODE_OPTIONS: "--max-old-space-size=8192"

      - name: Package visual artifacts
        if: failure()
        run: |
          # Include server logs for debugging
          echo "📋 Server logs for debugging:"
          cat .next/runner.log || echo "No server logs found"

          # Package test results and logs
          tar -czf visual-regression.tgz test-results tests/e2e/visual-regression.spec.ts-snapshots .next/runner.log || true

      - name: Upload visual artifacts
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: visual-regression-results
          path: visual-regression.tgz
          retention-days: 30

      - name: Stop app
        if: always()
        run: |
          if [ -f .next/runner.pid ]; then
            kill $(cat .next/runner.pid) 2>/dev/null || true
          fi

  performance:
    runs-on: [self-hosted, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false

      - name: Build application
        run: npm run build

      # 1) Sanity check that the build exists
      - name: Verify Next build output
        run: |
          set -euo pipefail
          ls -la .next || true
          test -f .next/BUILD_ID || (echo "No Next build output (.next) – did build fail?" && exit 1)

      - name: Install Chrome via Puppeteer (mac_arm)
        run: |
          # Install Chrome (arm64) into a local cache
          set -euo pipefail
          mkdir -p .cache/puppeteer

          # 1) Install and capture the build id that was actually installed
          INSTALL_OUT="$(npx @puppeteer/browsers install chrome@stable --platform=mac_arm --path .cache/puppeteer)"
          echo "$INSTALL_OUT"

          # INSTALL_OUT looks like: "chrome@140.0.7339.80 /abs/path/to/.../Google Chrome for Testing"
          BUILD_ID="$(printf '%s\n' "$INSTALL_OUT" | awk '{print $1}' | cut -d@ -f2)"
          echo "Detected Chrome build: $BUILD_ID"

          # 2) Ask for the executable path using the explicit build id
          CHROME_PATH="$(npx @puppeteer/browsers executable-path chrome@"$BUILD_ID" --platform=mac_arm --path .cache/puppeteer || true)"
          echo "Chrome executable path (via CLI): ${CHROME_PATH:-<empty>}"

          # 3) Fallback: resolve the binary directly from the cache if the CLI returned empty
          if [ -z "$CHROME_PATH" ]; then
            CHROME_PATH="$(/usr/bin/find ".cache/puppeteer/chrome" -type f -path "*/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" -print -quit || true)"
            echo "Chrome executable path (via find): ${CHROME_PATH:-<empty>}"
          fi

          # 4) Hard fail if still empty
          if [ -z "$CHROME_PATH" ] || [ ! -x "$CHROME_PATH" ]; then
            echo "❌ Chrome path is empty or not executable"
            ls -la .cache/puppeteer || true
            exit 1
          fi

          # 5) Export for subsequent steps in this job and later ones
          echo "CHROME_PATH=$CHROME_PATH" >> "$GITHUB_ENV"
          "$CHROME_PATH" --version || true

      - name: Ensure arm64 Node for Lighthouse
        run: |
          set -euo pipefail
          echo "node before: $(node -v) arch=$(node -p 'process.arch')"
          if [ "$(node -p 'process.arch')" != "arm64" ]; then
            NODE_VER=20.17.0
            curl -fsSLO "https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-darwin-arm64.tar.xz"
            tar -xJf "node-v${NODE_VER}-darwin-arm64.tar.xz"
            # Make arm64 node take effect in THIS step:
            export PATH="$PWD/node-v${NODE_VER}-darwin-arm64/bin:$PATH"
            # And persist for subsequent steps:
            echo "$PWD/node-v${NODE_VER}-darwin-arm64/bin" >> "$GITHUB_PATH"
          fi
          echo "node after: $(node -v) arch=$(node -p 'process.arch')"
          echo "uname -m: $(uname -m)"
          # Get Chrome path for this step
          CHROME_PATH=$(npx @puppeteer/browsers executable-path chrome@stable --platform=mac_arm --path .cache/puppeteer)
          echo "Chrome path: $CHROME_PATH"
          "$CHROME_PATH" --version || true

      - name: Performance (start + test + teardown)
        run: |
          set -euo pipefail

          export PORT=3010 HOST=127.0.0.1
          mkdir -p .next
          test -d .next || { echo "❌ Missing .next build output"; exit 1; }

          echo "🚀 Starting Next.js server for performance testing..."
          node node_modules/next/dist/bin/next start -p "$PORT" -H "$HOST" > .next/runner.log 2>&1 &
          SVPID=$!
          npx wait-on -t 120000 "tcp:$HOST:$PORT"
          curl -fsS "http://$HOST:$PORT" >/dev/null
          echo "✅ App is responding at http://$HOST:$PORT"

          # Ensure we're using arm64 Node for Lighthouse
          echo "Node arch: $(node -p "process.arch")"

          # Get Chrome path directly in this step (same logic as installation step)
          INSTALL_OUT="$(npx @puppeteer/browsers install chrome@stable --platform=mac_arm --path .cache/puppeteer 2>/dev/null || true)"
          BUILD_ID="$(printf '%s\n' "$INSTALL_OUT" | awk '{print $1}' | cut -d@ -f2)"
          echo "Using Chrome build: $BUILD_ID"

          # Try CLI first, then fallback to find
          CHROME_PATH="$(npx @puppeteer/browsers executable-path chrome@"$BUILD_ID" --platform=mac_arm --path .cache/puppeteer 2>/dev/null || true)"
          if [ -z "$CHROME_PATH" ]; then
            CHROME_PATH="$(/usr/bin/find ".cache/puppeteer/chrome" -type f -path "*/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" -print -quit 2>/dev/null || true)"
          fi

          echo "Chrome path: $CHROME_PATH"

          # Verify Chrome path is not empty
          if [ -z "$CHROME_PATH" ]; then
            echo "❌ Chrome path is empty - Chrome installation may have failed"
            exit 1
          fi

          # Verify Chrome executable exists and is executable
          if [ ! -x "$CHROME_PATH" ]; then
            echo "❌ Chrome executable not found or not executable: $CHROME_PATH"
            ls -la .cache/puppeteer/ || true
            exit 1
          fi

          "$CHROME_PATH" --version

          # Run LHCI with arm64 Node + arm64 Chrome
          # Test homepage and blog pages using config file
          npx lhci autorun --chrome-path="$CHROME_PATH"

          kill "$SVPID" 2>/dev/null || true
        env:
          NEXT_TELEMETRY_DISABLED: "1"
          NODE_ENV: production
          NODE_OPTIONS: "--max-old-space-size=8192"

      - name: Upload Performance Artifacts
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: performance-results
          path: |
            lhci-results
            .next/analyze
            .next/monitoring
            .next/web-vitals
            .next/test-results
          retention-days: 30

  lint:
    runs-on: [self-hosted, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false
      - run: npm run lint
      - run: npm exec prettier -- --check "**/*.{js,jsx,ts,tsx,json,css,md}"

  build:
    runs-on: [self-hosted, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "${{ env.NODE_VERSION }}"
          cache: npm
      - run: npm ci --no-audit --fund=false
      - run: npm run build
      - run: npm run storybook:build:github
