name: CI

on:
  pull_request:
  push:
    branches:
      - main
      - master

env:
  CARGO_TERM_COLOR: always
  RUST_MIN_STACK: 67108864
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  RUSTUP_MAX_RETRIES: 10
  # Gitea Actions runner tool cache. Actions like setup-node/setup-python can reuse this.
  RUNNER_TOOL_CACHE: /toolcache

jobs:
  rust-fmt:
    name: Rustfmt
    runs-on: build-amd64
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cache Rust toolchain
        uses: actions/cache@v4
        with:
          path: |
            ~/.rustup/toolchains
            ~/.rustup/update-hashes
          key: rustup-rustfmt-${{ runner.os }}-stable-v1
          restore-keys: |
            rustup-${{ runner.os }}-stable-v1
            rustup-

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

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

  rust-clippy:
    name: Clippy
    runs-on: build-amd64
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cache Rust toolchain
        uses: actions/cache@v4
        with:
          path: |
            ~/.rustup/toolchains
            ~/.rustup/update-hashes
          key: rustup-clippy-${{ runner.os }}-stable-v1
          restore-keys: |
            rustup-${{ runner.os }}-stable-v1
            rustup-

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

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

      - name: Cache Cargo build artifacts
        uses: actions/cache@v4
        with:
          path: target
          key: cargo-clippy-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
          restore-keys: |
            cargo-clippy-${{ hashFiles('**/Cargo.lock') }}-
            cargo-clippy-

      - name: Clippy
        run: cargo clippy --workspace --all-targets --all-features -- -D warnings

  rust-test:
    name: Tests
    runs-on: build-amd64
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cache Rust toolchain
        uses: actions/cache@v4
        with:
          path: |
            ~/.rustup/toolchains
            ~/.rustup/update-hashes
          key: rustup-test-${{ runner.os }}-stable-v1
          restore-keys: |
            rustup-${{ runner.os }}-stable-v1
            rustup-

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

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

      - name: Cache Cargo build artifacts
        uses: actions/cache@v4
        with:
          path: target
          key: cargo-test-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
          restore-keys: |
            cargo-test-${{ hashFiles('**/Cargo.lock') }}-
            cargo-test-

      - name: Tests
        run: cargo test --workspace --all-features

  rust-audit:
    name: Cargo Audit & Deny
    runs-on: build-amd64
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cache Rust toolchain
        uses: actions/cache@v4
        with:
          path: |
            ~/.rustup/toolchains
            ~/.rustup/update-hashes
          key: rustup-audit-${{ runner.os }}-stable-v1
          restore-keys: |
            rustup-${{ runner.os }}-stable-v1
            rustup-

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

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

      - name: Cache cargo-binstall and installed binaries
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/cargo-binstall
            ~/.cargo/bin/cargo-deny
          key: cargo-security-tools-v2

      - name: Install cargo-binstall
        run: |
          if ! command -v cargo-binstall &> /dev/null; then
            curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
          fi

      - name: Install security tools (pre-built binaries)
        run: |
          command -v cargo-deny   &> /dev/null || cargo binstall --no-confirm --locked cargo-deny

      - name: Cargo Deny
        run: cargo deny check

  web-blocking:
    name: Web Blocking Checks
    runs-on: build-amd64
    defaults:
      run:
        working-directory: web
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
          cache-dependency-path: web/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: ESLint
        run: npm run lint

      - name: TypeScript
        run: npm run typecheck

      - name: Build
        run: npm run build

  security-blocking:
    name: Security Blocking Checks
    runs-on: build-amd64
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Gitleaks
        run: |
          mkdir -p "$HOME/bin"
          GITLEAKS_VERSION="8.24.2"
          ARCH="$(uname -m)"
          case "$ARCH" in
            x86_64) ARCH="x64" ;;
            aarch64|arm64) ARCH="arm64" ;;
            *)
              echo "Unsupported architecture: $ARCH"
              exit 1
              ;;
          esac
          curl -sSfL \
            -o /tmp/gitleaks.tar.gz \
            "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${ARCH}.tar.gz"
          tar -xzf /tmp/gitleaks.tar.gz -C "$HOME/bin" gitleaks
          chmod +x "$HOME/bin/gitleaks"

      - name: Gitleaks
        run: |
          "$HOME/bin/gitleaks" git \
            --report-format sarif \
            --report-path gitleaks.sarif \
            --config .gitleaks.toml

  web-advisory:
    name: Web Advisory Checks
    runs-on: build-amd64
    continue-on-error: true
    defaults:
      run:
        working-directory: web
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
          cache-dependency-path: web/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Knip
        run: npm run knip
        continue-on-error: true

      - name: NPM Audit (prod deps)
        run: npm audit --omit=dev
        continue-on-error: true

  security-advisory:
    name: Security Advisory Checks
    runs-on: build-amd64
    continue-on-error: true
    steps:
      - name: Checkout
        uses: actions/checkout@v4

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

      - name: Install Semgrep
        run: pip install semgrep

      - name: Semgrep
        run: semgrep scan --config p/default --error
        continue-on-error: true
