name: CI

on:
  pull_request:
  push:
    branches:
      - master

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

jobs:
  # ──────────────────────────────────────────────
  # Fast checks: format, clippy, deny (runs first)
  # ──────────────────────────────────────────────
  lint:
    name: Lint
    runs-on: ubuntu-latest
    env:
      RUSTFLAGS: --deny warnings
    steps:
      - uses: actions/checkout@v4

      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: lint-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
          restore-keys: lint-

      - name: Install Rust
        run: |
          rustup component add rustfmt
          rustup component add clippy

      - name: Install cargo-deny
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-deny@0.19.0

      - name: Format
        run: cargo fmt --all -- --check

      - name: Clippy (all features)
        run: cargo clippy --all --all-targets --all-features -- -Dwarnings

      - name: Clippy (no features)
        run: cargo clippy --all --all-targets --no-default-features -- -Dwarnings

      - name: License check
        run: cargo deny --workspace --all-features check licenses

      - name: Advisory check
        run: cargo deny --workspace check advisories

      - name: Ban check
        run: cargo deny --workspace check bans

      - name: Source check
        run: cargo deny --workspace check sources

  # ──────────────────────────────────────────────
  # Compile checks across feature combinations
  # ──────────────────────────────────────────────
  compile:
    name: Compile (${{ matrix.name }})
    runs-on: ubuntu-latest
    env:
      RUSTFLAGS: --deny warnings
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: default
            args: "--all --all-targets"
          - name: all-features
            args: "--all --all-targets --all-features"
          - name: no-features
            args: "--all --all-targets --no-default-features"
          - name: wasm32
            args: ""
            wasm: true
    steps:
      - uses: actions/checkout@v4

      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: compile-${{ matrix.name }}-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
          restore-keys: compile-${{ matrix.name }}-

      - name: Compile
        if: ${{ !matrix.wasm }}
        run: cargo check ${{ matrix.args }}

      - name: Compile (wasm32-unknown-unknown)
        if: ${{ matrix.wasm }}
        run: |
          rustup target add wasm32-unknown-unknown
          # Ensure lockfile exists (cargo pkgid requires it)
          test -f Cargo.lock || cargo generate-lockfile
          command="cargo check -p $(cargo pkgid) -p shodh-redb-derive --target wasm32-unknown-unknown"
          $command
          # bf_tree requires native targets (threads, filesystem) — skip it for wasm32
          $command --features std,logging,cache_metrics,compression_lz4,compression_zstd
          $command --no-default-features

  # ──────────────────────────────────────────────
  # Test matrix: multi-platform
  # ──────────────────────────────────────────────
  test:
    name: Test (${{ matrix.name }})
    needs: [lint, compile]
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: Linux x86_64
            os: ubuntu-latest
            fuzz: true
            wasi: true
            python: true
          - name: Linux ARM64
            os: ubuntu-24.04-arm
            fuzz: true
            wasi: true
            python: true
          - name: macOS
            os: macos-latest
            fuzz: true
            wasi: true
            python: true
          - name: Windows
            os: windows-latest
            fuzz: false
            wasi: false
            python: true

    runs-on: ${{ matrix.os }}
    env:
      RUSTFLAGS: --deny warnings

    steps:
      - uses: actions/checkout@v4

      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
            fuzz/target/
          key: test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
          restore-keys: test-${{ runner.os }}-${{ runner.arch }}-

      - name: Non ASCII characters
        if: startsWith(matrix.os, 'ubuntu')
        run: |
            if grep -P -n "[^\x00-\x7F]" -r --include="*.rs" --include="*.toml" .; then
            echo "Non-ASCII characters detected"
            exit 1
            fi

      - name: Install Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install Rust
        run: |
          rustup component add rustfmt
          rustup component add clippy

      - name: OSX x86 rust
        if: startsWith(matrix.os, 'macos')
        run: rustup target add x86_64-apple-darwin

      - name: Setup wasmtime
        if: matrix.wasi
        uses: bytecodealliance/actions/wasmtime/setup@v1
        with:
          version: "24.0.0"

      - name: Install cargo-fuzz
        if: matrix.fuzz
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-fuzz@0.13.1

      - name: Setup fuzz toolchain
        if: matrix.fuzz
        run: |
          rustup install nightly
          HOST_TARGET=$(rustc -vV | awk '/^host:/ {print $2}')
          rustup target add --toolchain nightly "$HOST_TARGET"

      - name: Install just
        uses: taiki-e/install-action@v2
        with:
          tool: just@1.36.0

      - name: Build all
        run: cargo build --all --all-targets --all-features

      - name: Run tests
        run: cargo test --all --all-features
        env:
          RUST_BACKTRACE: "1"

      - name: Fuzzer (60s)
        if: matrix.fuzz
        run: |
          HOST_TARGET=$(rustc -vV | awk '/^host:/ {print $2}')
          cargo +nightly fuzz run --sanitizer=none --target "$HOST_TARGET" fuzz_redb -- -max_len=10000 -max_total_time=60
        env:
          RUSTFLAGS: ""

      # ── CLI smoke tests ──
      - name: CLI smoke test (Unix)
        if: runner.os != 'Windows'
        run: |
          cargo run --example create_test_db
          cargo run -p shodh-redb-cli -- info test.redb
          cargo run -p shodh-redb-cli -- tables test.redb
          cargo run -p shodh-redb-cli -- stats test.redb
          cargo run -p shodh-redb-cli -- header test.redb
          cargo run -p shodh-redb-cli -- dump test.redb -t users -l 5
          cargo run -p shodh-redb-cli -- verify test.redb
          cargo run -p shodh-redb-cli -- compact test.redb
          rm test.redb

      - name: CLI smoke test (Windows)
        if: runner.os == 'Windows'
        shell: bash
        run: |
          cargo run --example create_test_db
          cargo run -p shodh-redb-cli -- info test.redb
          cargo run -p shodh-redb-cli -- tables test.redb
          cargo run -p shodh-redb-cli -- stats test.redb
          cargo run -p shodh-redb-cli -- header test.redb
          cargo run -p shodh-redb-cli -- dump test.redb -t users -l 5
          cargo run -p shodh-redb-cli -- verify test.redb
          cargo run -p shodh-redb-cli -- compact test.redb
          rm test.redb

      # ── Python binding tests ──
      - name: Python tests (Unix)
        if: matrix.python && runner.os != 'Windows'
        run: |
          python3 -m venv venv
          source venv/bin/activate
          pip3 install --upgrade pip
          pip3 install maturin
          maturin develop --manifest-path=./crates/redb-python/Cargo.toml
          python3 -m unittest discover --start-directory=./crates/redb-python

      - name: Python tests (Windows)
        if: matrix.python && runner.os == 'Windows'
        run: |
          python3 -m venv venv
          venv\Scripts\activate
          pip3 install --upgrade pip
          pip3 install maturin
          maturin develop --manifest-path=./crates/redb-python/Cargo.toml
          python3 -m unittest discover --start-directory=./crates/redb-python

      # ── WASI tests ──
      - name: WASI tests
        if: matrix.wasi
        run: RUSTFLAGS="" just test_wasi

  # ──────────────────────────────────────────────
  # Feature-isolated test runs (ubuntu-latest only)
  # ──────────────────────────────────────────────
  test-features:
    name: Test Features (${{ matrix.name }})
    needs: [lint, compile]
    runs-on: ubuntu-latest
    env:
      RUSTFLAGS: --deny warnings
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: no-default-features
            args: "--no-default-features"
          - name: default
            args: ""
          - name: compression_lz4
            args: "--features compression_lz4"
          - name: compression_zstd
            args: "--features compression_zstd"
    steps:
      - uses: actions/checkout@v4

      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: test-features-${{ matrix.name }}-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
          restore-keys: test-features-${{ matrix.name }}-

      - name: Run tests (${{ matrix.name }})
        run: cargo test --all ${{ matrix.args }}
        env:
          RUST_BACKTRACE: "1"

  # ──────────────────────────────────────────────
  # MSRV check: ensure we compile and pass tests on minimum supported Rust
  # ──────────────────────────────────────────────
  msrv:
    name: MSRV (1.89)
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install MSRV toolchain
        run: |
          rustup toolchain install 1.89.0 --profile minimal
          rustup override set 1.89.0

      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: msrv-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
          restore-keys: msrv-

      - name: Check MSRV
        run: cargo check --all --all-features

      - name: Test MSRV
        run: cargo test --all --all-features
        env:
          RUST_BACKTRACE: "1"

  # ──────────────────────────────────────────────
  # Summary gate for branch protection
  # ──────────────────────────────────────────────
  ci-pass:
    name: CI Pass
    if: always()
    needs: [lint, compile, test, test-features, msrv]
    runs-on: ubuntu-latest
    steps:
      - name: Check all jobs
        run: |
          if [[ "${{ needs.lint.result }}" != "success" ]]; then
            echo "Lint failed"
            exit 1
          fi
          if [[ "${{ needs.compile.result }}" != "success" ]]; then
            echo "Compile failed"
            exit 1
          fi
          if [[ "${{ needs.test.result }}" != "success" ]]; then
            echo "Test failed"
            exit 1
          fi
          if [[ "${{ needs.test-features.result }}" != "success" ]]; then
            echo "Test Features failed"
            exit 1
          fi
          if [[ "${{ needs.msrv.result }}" != "success" ]]; then
            echo "MSRV failed"
            exit 1
          fi
          echo "All checks passed"
