name: Fuzz

on:
  schedule:
    # Run fuzzing daily at 2 AM UTC
    - cron: '0 2 * * *'
  workflow_dispatch:
    inputs:
      duration:
        description: 'Fuzzing duration in seconds per target'
        required: false
        default: '300'
        type: string
      targets:
        description: 'Comma-separated list of fuzz targets to run (empty for all)'
        required: false
        default: ''
        type: string

env:
  CARGO_TERM_COLOR: always

jobs:
  fuzz:
    name: Fuzz Targets
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        target: [constant_time, edge_cases, crypto_ops]
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly  # Fuzzing typically requires nightly

      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz

      - name: Cache dependencies
        uses: Swatinem/rust-cache@v2

      - name: Build fuzz targets
        run: cargo fuzz build ${{ matrix.target }}

      - name: Run fuzzing
        run: |
          # Determine fuzzing duration
          DURATION="${{ github.event.inputs.duration || '300' }}"

          # Determine if this target should run
          TARGETS="${{ github.event.inputs.targets || '' }}"
          if [ -n "$TARGETS" ]; then
            # Check if this target is in the requested list
            if ! echo "$TARGETS" | grep -q "${{ matrix.target }}"; then
              echo "Skipping ${{ matrix.target }} (not in requested targets: $TARGETS)"
              exit 0
            fi
          fi

          echo "Running ${{ matrix.target }} for ${DURATION} seconds"

          # Run fuzzing with timeout
          timeout ${DURATION} cargo fuzz run ${{ matrix.target }} -- -max_total_time=${DURATION} || true

      - name: Minimize corpus (on schedule only)
        if: github.event_name == 'schedule'
        run: |
          echo "Minimizing fuzz corpus for ${{ matrix.target }}"
          cargo fuzz cmin ${{ matrix.target }} || true

      - name: Upload crash artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: fuzz-crash-${{ matrix.target }}
          path: |
            fuzz/artifacts/${{ matrix.target }}/
            fuzz/corpus/${{ matrix.target }}/
          retention-days: 30

  regression-test:
    name: Regression Test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly

      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz

      - name: Cache dependencies
        uses: Swatinem/rust-cache@v2

      - name: Build all fuzz targets
        run: |
          for target in constant_time edge_cases crypto_ops; do
            echo "Building $target"
            cargo fuzz build $target
          done

      - name: Run regression tests
        run: |
          # Run each fuzz target for a short time to catch immediate crashes
          for target in constant_time edge_cases crypto_ops; do
            echo "Testing $target for regressions"
            timeout 30 cargo fuzz run $target -- -runs=10000 || {
              echo "Regression detected in $target"
              exit 1
            }
          done

  coverage:
    name: Fuzz Coverage
    runs-on: ubuntu-latest
    if: github.event_name == 'schedule'
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: nightly
          components: llvm-tools-preview

      - name: Install cargo-fuzz
        run: cargo install cargo-fuzz

      - name: Install cargo-llvm-cov
        run: cargo install cargo-llvm-cov --version 0.6.0

      - name: Cache dependencies
        uses: Swatinem/rust-cache@v2

      - name: Build fuzz targets with coverage
        run: |
          export RUSTFLAGS="-Cinstrument-coverage"
          for target in constant_time edge_cases crypto_ops; do
            echo "Building $target with coverage"
            cargo fuzz build $target
          done

      - name: Generate coverage report
        run: |
          # Run short fuzzing sessions to generate coverage
          export LLVM_PROFILE_FILE="fuzz-%p-%m.profraw"
          for target in constant_time edge_cases crypto_ops; do
            echo "Generating coverage for $target"
            timeout 60 cargo fuzz run $target -- -runs=5000 || true
          done

          # Generate coverage report
          cargo llvm-cov report --lcov --output-path fuzz-coverage.lcov

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: fuzz-coverage.lcov
          flags: fuzz
          name: fuzz-coverage
          fail_ci_if_error: false