# CI checks for PRs
name: 🚦 CI

on:
  pull_request:
  workflow_dispatch:

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

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  # Detect which files changed, to determine which checks to run
  changes:
    name: 🔍 Detect changes
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      rust: ${{ steps.filter.outputs.rust }}
      docker: ${{ steps.filter.outputs.docker }}
      workflows: ${{ steps.filter.outputs.workflows }}
      deps: ${{ steps.filter.outputs.deps }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Filter changed paths
        id: filter
        uses: dorny/paths-filter@v4
        with:
          filters: |
            rust:
              - 'src/**'
              - 'build.rs'
              - 'Cargo.toml'
              - 'Cargo.lock'
            docker:
              - 'Dockerfile*'
              - 'src/**'
              - 'build.rs'
              - 'Cargo.toml'
              - 'Cargo.lock'
            workflows:
              - '.github/workflows/**'
            deps:
              - 'Cargo.toml'
              - 'Cargo.lock'

  # Run format check, to ensure code nice and pretty
  fmt:
    name: 🎨 Format
    needs: changes
    if: needs.changes.outputs.rust == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt

      - name: Check formatting
        run: cargo fmt --all --check

  # Run clippy, to ensure no rust warnings
  clippy:
    name: 📎 Clippy
    needs: changes
    if: needs.changes.outputs.rust == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache cargo build
        uses: Swatinem/rust-cache@v2

      - name: Run clippy
        run: cargo clippy --all-targets --all-features --locked -- -D warnings

  # Execute the tests
  test:
    name: 🧪 Test
    needs: changes
    if: needs.changes.outputs.rust == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo build
        uses: Swatinem/rust-cache@v2

      - name: Run tests
        run: cargo test --all-features --locked

  # Do build check, ensure everything builds
  build:
    name: 🔨 Build
    needs: changes
    if: needs.changes.outputs.rust == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo build
        uses: Swatinem/rust-cache@v2

      - name: Build release
        run: cargo build --release --locked

  # Check the docs build with no warnings or broken intra-doc links
  docs:
    name: 📚 Docs
    needs: changes
    if: needs.changes.outputs.rust == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      RUSTDOCFLAGS: -D warnings
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo build
        uses: Swatinem/rust-cache@v2

      - name: Build docs
        run: cargo doc --no-deps --all-features --locked

  # Dependencies audit for known vulnerabilities
  # audit:
  #   name: 🛡️ Audit
  #   runs-on: ubuntu-latest
  #   timeout-minutes: 15
  #   steps:
  #     - name: Checkout
  #       uses: actions/checkout@v6
  #       with:
  #         persist-credentials: false

  #     - name: Install cargo-audit
  #       uses: taiki-e/install-action@v2
  #       with:
  #         tool: cargo-audit

  #     - name: Run cargo audit
  #       run: cargo audit

  # Run the Docker image build and smoke test
  docker:
    name: 🐳 Docker build
    needs: changes
    if: needs.changes.outputs.docker == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Build image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          load: true
          tags: adguardian:ci
          cache-from: type=gha,scope=ci-docker
          cache-to: type=gha,mode=max,scope=ci-docker

      - name: Smoke test (image starts)
        env:
          IMAGE: adguardian:ci
        run: |
          set -euo pipefail
          output=$(timeout 30 docker run --rm "$IMAGE" </dev/null 2>&1 || true)
          echo "$output"
          echo "$output" | grep -q "AdGuardian Terminal Edition" \
            || { echo "::error::Image built but did not start as expected"; exit 1; }

  # Review the workflow files are valid and free of security issues
  workflow-lint:
    name: ⚙️ Workflow lint
    needs: changes
    if: needs.changes.outputs.workflows == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Run actionlint
        uses: raven-actions/actionlint@v2

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.x'

      - name: Run zizmor
        run: |
          python -m pip install --quiet zizmor
          zizmor --offline .github/workflows/

  # Check no super secret things committed
  secrets:
    name: 🔑 Secret scan
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Scan for secrets with TruffleHog
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

  # Dependency changes have no known vulnerabilities
  dependency-review:
    name: ⛓️‍💥 Dependency review
    needs: changes
    if: github.event_name == 'pull_request' && needs.changes.outputs.deps == 'true'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Review dependencies
        uses: actions/dependency-review-action@v5
        with:
          fail-on-severity: moderate
          show-openssf-scorecard: false
