name: E2E (packaged app, Windows)

# Fast-feedback packaged-app gate, Windows-only. Split into parallel jobs so the
# push wall-clock stays well under ~10 min:
#   gate        - cheap pure-node selftests (no build): mutation gate + preflight
#                 Layers 1-5. Fails the "tests that test the tests" fast.
#   verify      - build the UNPACKED app (electron-builder --dir, which skips the
#                 slow ~2min NSIS LZMA compression) and run the deterministic
#                 verify-all gate against win-unpacked\OpenSwarm.exe.
#   playwright  - build the unpacked app and run the renderer-level Playwright e2e.
#   installer   - full NSIS build + destructive install->verify->uninstall. The
#                 heaviest leg, so it runs only on PR-to-main / dispatch (NOT on
#                 routine pushes); release-windows.yml covers it on v* tags.
# verify + playwright run concurrently; both reuse cached heavy build inputs
# (bundled Python env, uv, MCP bundles, npm) so warm builds are fast - the build
# script skips any input already on disk.
#
# macOS legs were removed (runner starvation + untriageable mac-only failures);
# re-add when a Mac maintainer can own them. Real Win10 coverage still needs the
# SELF-HOSTED e2e-win10 job (repo var WIN10_SELF_HOSTED=true + a
# [self-hosted, windows, win10] runner).

on:
  push:
    branches: [eric/lock]
    paths: &build-paths
      - 'electron/**'
      - 'frontend/**'
      - 'backend/**'
      - 'e2e/**'
      - 'scripts/build-app**'
      - 'scripts/build-python-env**'
      - 'scripts/fetch-router**'
      - 'scripts/ci/**'
      - '.github/workflows/e2e.yml'
  pull_request:
    branches: [main]
    paths: *build-paths
  workflow_dispatch:

permissions:
  contents: read

env:
  CSC_IDENTITY_AUTO_DISCOVERY: 'false'
  GOOGLE_OAUTH_CLIENT_ID: 'e2e-placeholder.apps.googleusercontent.com'
  GOOGLE_OAUTH_CLIENT_SECRET: 'e2e-placeholder-secret'

jobs:
  # Pure-node, no build: cheap enough to always run and fail fast.
  gate:
    runs-on: windows-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.18.1'
      - name: Gate selftest (mutation)
        shell: bash
        run: node scripts/ci/selftest-gate.js
      - name: Preflight selftest (Layers 1+2)
        shell: bash
        run: node scripts/ci/selftest-preflight.js
      - name: Preflight failure rigs (Layer 3)
        shell: bash
        run: node scripts/ci/verify-preflight-rigs.js
      - name: Preflight race / cache (Layer 4)
        shell: bash
        run: node scripts/ci/verify-preflight-race.js
      - name: Pairwise generator selftest (covering-array math)
        shell: bash
        run: node scripts/ci/selftest-pairwise.js
      - name: Preflight matrix (normal + hostile-env scenarios)
        shell: bash
        run: |
          node scripts/ci/verify-preflight.js
          OPENSWARM_TEST_NETWORK=blocked node scripts/ci/verify-preflight.js
          OPENSWARM_TEST_APPDATA=readonly node scripts/ci/verify-preflight.js
          OPENSWARM_TEST_LANG=de-DE node scripts/ci/verify-preflight.js

  # Build the unpacked app + run the deterministic gate.
  verify:
    runs-on: windows-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.18.1'
          cache: npm
          cache-dependency-path: |
            electron/package-lock.json
            frontend/package-lock.json
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Cache bundled Python env
        uses: actions/cache@v4
        with:
          path: electron/python-env
          key: pyenv-win-${{ hashFiles('scripts/build-python-env-win.ps1', 'backend/requirements.txt') }}
      - name: Cache uv binaries
        uses: actions/cache@v4
        with:
          path: backend/uv-bin
          key: uvbin-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Cache MCP bundles
        uses: actions/cache@v4
        with:
          path: backend/mcp-bundles
          key: mcpbundles-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Build packaged app (unpacked, no installer)
        shell: pwsh
        run: pwsh -NoProfile -File scripts/build-app-win.ps1 -DirOnly
      - name: Deterministic gate (verify-all)
        shell: bash
        run: node scripts/ci/verify-all.js

  # Build the unpacked app + run the Playwright renderer suite, concurrently with verify.
  playwright:
    runs-on: windows-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.18.1'
          cache: npm
          cache-dependency-path: |
            electron/package-lock.json
            frontend/package-lock.json
            e2e/package-lock.json
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Cache bundled Python env
        uses: actions/cache@v4
        with:
          path: electron/python-env
          key: pyenv-win-${{ hashFiles('scripts/build-python-env-win.ps1', 'backend/requirements.txt') }}
      - name: Cache uv binaries
        uses: actions/cache@v4
        with:
          path: backend/uv-bin
          key: uvbin-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Cache MCP bundles
        uses: actions/cache@v4
        with:
          path: backend/mcp-bundles
          key: mcpbundles-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Build packaged app (unpacked, no installer)
        shell: pwsh
        run: pwsh -NoProfile -File scripts/build-app-win.ps1 -DirOnly
      - name: Install e2e deps
        shell: bash
        working-directory: e2e
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
        run: npm ci
      - name: GUI hand selftest (MCP)
        shell: bash
        working-directory: e2e
        run: node mcp/selftest.js
      - name: Run E2E (Playwright)
        shell: bash
        working-directory: e2e
        run: npm test
      - name: Upload E2E results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: e2e-results
          path: e2e/results.json
          if-no-files-found: ignore
          retention-days: 14
      # Per-test traces: playwright-trace.zip, events.jsonl, mousepath.jsonl,
      # backend.log.tail - always uploaded so a failed run is debuggable.
      - name: Upload E2E visibility traces
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: e2e-traces
          path: e2e/traces/
          if-no-files-found: ignore
          retention-days: 14

  # Heaviest leg: full NSIS installer + destructive install->verify->uninstall on
  # a clean runner. Skip on routine pushes; run on PRs into main + manual dispatch.
  installer:
    if: github.event_name != 'push'
    runs-on: windows-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.18.1'
          cache: npm
          cache-dependency-path: |
            electron/package-lock.json
            frontend/package-lock.json
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Cache bundled Python env
        uses: actions/cache@v4
        with:
          path: electron/python-env
          key: pyenv-win-${{ hashFiles('scripts/build-python-env-win.ps1', 'backend/requirements.txt') }}
      - name: Cache uv binaries
        uses: actions/cache@v4
        with:
          path: backend/uv-bin
          key: uvbin-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Cache MCP bundles
        uses: actions/cache@v4
        with:
          path: backend/mcp-bundles
          key: mcpbundles-win-${{ hashFiles('scripts/build-app-win.ps1') }}
      - name: Build packaged app (full NSIS installer)
        shell: pwsh
        run: pwsh -NoProfile -File scripts/build-app-win.ps1
      - name: Installer cycle (clean runner)
        shell: pwsh
        run: node scripts/ci/verify-installer.js --destructive

  # Real Windows 10 coverage. Skipped unless a self-hosted Win10 runner exists
  # and WIN10_SELF_HOSTED=true (repo variable). Mirrors the windows steps above.
  e2e-win10:
    if: ${{ vars.WIN10_SELF_HOSTED == 'true' }}
    runs-on: [self-hosted, windows, win10]
    timeout-minutes: 90
    env:
      CSC_IDENTITY_AUTO_DISCOVERY: 'false'
      GOOGLE_OAUTH_CLIENT_ID: 'e2e-placeholder.apps.googleusercontent.com'
      GOOGLE_OAUTH_CLIENT_SECRET: 'e2e-placeholder-secret'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.18.1'
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Gate selftest (mutation)
        shell: bash
        run: node scripts/ci/selftest-gate.js
      - name: Preflight selftest + rigs + race + matrix (Layers 1-5)
        shell: bash
        run: |
          node scripts/ci/selftest-preflight.js
          node scripts/ci/verify-preflight-rigs.js
          node scripts/ci/verify-preflight-race.js
          node scripts/ci/verify-preflight.js
          OPENSWARM_TEST_NETWORK=blocked node scripts/ci/verify-preflight.js
          OPENSWARM_TEST_APPDATA=readonly node scripts/ci/verify-preflight.js
      - name: Build packaged app (Windows)
        shell: pwsh
        run: pwsh -NoProfile -File scripts/build-app-win.ps1
      - name: Deterministic gate (verify-all)
        shell: bash
        run: node scripts/ci/verify-all.js
      - name: Install e2e deps
        shell: bash
        working-directory: e2e
        env:
          PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
        run: npm ci
      - name: GUI hand selftest (MCP)
        shell: bash
        working-directory: e2e
        run: node mcp/selftest.js
      - name: Run E2E (Playwright)
        shell: bash
        working-directory: e2e
        run: npm test
      - name: Installer cycle (clean runner)
        shell: pwsh
        run: node scripts/ci/verify-installer.js --destructive
