name: CI

on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Format + Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2
      - run: cargo fmt --all -- --check
        working-directory: crates/sfu
      - run: cargo clippy --workspace -- -D warnings
        working-directory: crates/sfu
      # Same featureless blind spot as the `test` job: clippy --workspace never
      # lints the required-features=["test-utils"] test files either.
      - run: cargo clippy -p oxpulse-sfu --features test-utils,vfm --all-targets -- -D warnings
        working-directory: crates/sfu

  # ADR-S10 — single-str0m fitness. Both oxpulse-sfu-kit and this crate MUST
  # resolve to ONE str0m version; two mismatched 0.x minors are a hard compile
  # break at every interop boundary (SfuRid/Rid, MediaData). Fails if the
  # workspace resolves more than one str0m version.
  single-str0m:
    name: Single str0m version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Assert exactly one str0m version
        run: |
          set -euo pipefail
          count=$(cargo tree --workspace --prefix none -i str0m 2>/dev/null | grep -c '^str0m v' || true)
          echo "resolved str0m versions: $count"
          cargo tree --workspace -i str0m || true
          if [ "$count" != "1" ]; then
            echo "::error::expected exactly 1 str0m version, found $count (two minors are a hard interop break)"
            exit 1
          fi

  # ADR-S8 — release-train guardrail. A validation-only [patch.crates-io]
  # override that points oxpulse-sfu-kit at a git/path source MUST NEVER reach a
  # deployable branch. Runs only for main/dev (push or PR target); a scratch
  # validation branch is intentionally exempt.
  no-kit-source-override:
    name: No oxpulse-sfu-kit source override
    runs-on: ubuntu-latest
    if: >-
      github.ref == 'refs/heads/main' ||
      github.ref == 'refs/heads/dev' ||
      github.base_ref == 'main' ||
      github.base_ref == 'dev'
    steps:
      - uses: actions/checkout@v4
      - name: Reject git=/path= override on oxpulse-sfu-kit
        run: |
          set -euo pipefail
          # Match a [patch...] entry pinning oxpulse-sfu-kit to a git or path source.
          if grep -Eiq 'oxpulse-sfu-kit[[:space:]]*=.*(\bgit\b|\bpath\b)[[:space:]]*=' Cargo.toml; then
            echo "::error::Cargo.toml carries a git=/path= override on oxpulse-sfu-kit."
            echo "This validation-only [patch.crates-io] must never land on main/dev."
            grep -niE 'oxpulse-sfu-kit[[:space:]]*=.*(git|path)[[:space:]]*=' Cargo.toml || true
            exit 1
          fi
          echo "OK: no oxpulse-sfu-kit source override present."

  test:
    name: Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo test --workspace
        working-directory: crates/sfu
      # `cargo test --workspace` above is featureless, so every test with
      # `required-features = ["test-utils"]` (most of crates/sfu/tests/*.rs —
      # see each [[test]] entry in Cargo.toml) is silently skipped, not run.
      # That let several of them bit-rot uncompiled (fixed in the sfu test
      # hygiene pass). Compile + run them explicitly so this can't recur.
      - run: cargo test -p oxpulse-sfu --features test-utils,vfm
        working-directory: crates/sfu

  fips-build:
    name: FIPS build check
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - name: Install FIPS build deps
        run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build nasm
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Cargo check (FIPS)
        run: cargo check --workspace --features fips
        working-directory: crates/sfu

  installer-bash-tests:
    name: Installer bash tests
    runs-on: ubuntu-22.04
    timeout-minutes: 15
    env:
      # Tests use REPO_ROOT to locate install.sh / upgrade.sh / templates.
      # Without this, tests fall back to the developer's local path and fail.
      REPO_ROOT: ${{ github.workspace }}
    steps:
      - uses: actions/checkout@v4

      - name: Install bats
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y -qq bats

      - name: Run plain-bash tests
        # Only test_update_sh.sh is excluded — it needs a local HTTP fallback
        # server not available in CI. Every other plain-bash test either passes
        # or self-SKIPs (exit 0) when a live prerequisite is absent: a <domain>
        # arg / running edge (test_headers, test_e2e_a_prime,
        # test_installer_production_readiness), a locally-built Docker image or a
        # reachable daemon (test_caddy_image, test_cover_page, caddyfile_template,
        # caddyfile_golden, canary_endpoints), or the opec binary
        # (test_install_ships_cover's dry-run block).
        run: |
          set +e
          PASS=0; FAIL=0
          SKIP_LIST="test_update_sh.sh"
          # Guard: every SKIP_LIST entry must exist; stale entries fail the job.
          for entry in $SKIP_LIST; do
            if [ ! -f "tests/$entry" ]; then
              echo "ERROR: SKIP_LIST entry 'tests/$entry' does not exist — remove stale exclusion" >&2
              exit 1
            fi
          done
          for f in tests/test_*.sh; do
            name=$(basename "$f")
            # Skip bats-format files (handled in next step)
            if head -1 "$f" | grep -qE '^#!.*\bbats\b'; then continue; fi
            # Skip excluded tests
            if echo "$SKIP_LIST" | grep -qw "$name"; then
              echo "SKIP (excluded): $name"; continue
            fi
            echo "--- $name ---"
            if bash "$f"; then
              PASS=$((PASS+1))
            else
              echo "FAILED: $name" >&2
              FAIL=$((FAIL+1))
            fi
          done
          echo ""
          echo "Plain-bash: PASS=$PASS FAIL=$FAIL"
          [ "$FAIL" -eq 0 ]

      - name: Run bats tests
        # Bats files run as whole units; if even one @test inside fails, the
        # file is marked FAIL. BATS_SKIP excludes whole files with stale
        # assertions (pre-existing drift after refactors). See followup
        # tracking issue for each entry's root cause.
        run: |
          set +e
          PASS=0; FAIL=0
          # Whole-file skip list (pre-existing drift on origin/main):
          #   test_hydrate_render_identical.sh — Caddyfile upstream refactored from
          #     `10.9.0.2:8907 host.docker.internal:18443` to `xray-client:3080`;
          #     fixture not updated.
          #   test_install_opec_parity.sh — install.sh `render_with_opec xray`
          #     call-site removed during OPEC Phase 3 absorb; assertion stale.
          #   test_release_assets.sh — release.yml uninstall.sh staging path
          #     changed (Bug15 closed differently); test asserts old pattern.
          # test_install_lib_checksum.sh — REMOVED from skip list: checksums drift fixed
          #     (lib/lib-checksums.txt regenerated to match release pipeline, PR #285).
          BATS_SKIP="test_hydrate_render_identical.sh \
            test_install_opec_parity.sh test_release_assets.sh"
          for entry in $BATS_SKIP; do
            if [ ! -f "tests/$entry" ]; then
              echo "ERROR: BATS_SKIP entry 'tests/$entry' does not exist — remove stale exclusion" >&2
              exit 1
            fi
          done
          for f in tests/test_*.sh; do
            head -1 "$f" | grep -qE '^#!.*\bbats\b' || continue
            name=$(basename "$f")
            if echo "$BATS_SKIP" | grep -qw "$name"; then
              echo "SKIP (excluded): $name"; continue
            fi
            echo "--- $name ---"
            if bats "$f"; then
              PASS=$((PASS+1))
            else
              FAIL=$((FAIL+1))
            fi
          done
          echo ""
          echo "Bats: PASS=$PASS FAIL=$FAIL"
          [ "$FAIL" -eq 0 ]
