name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions: read-all

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable

      - name: Cache cargo
        uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

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

      - name: Clippy
        run: cargo clippy --package nora-registry -- -D warnings

      - name: Run tests
        run: cargo test --package nora-registry


  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable

      - name: Cache cargo
        uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

      - name: Install tarpaulin
        run: cargo install cargo-tarpaulin --locked

      - name: Run coverage
        run: |
          cargo tarpaulin --package nora-registry --out json --output-dir coverage/ 2>&1 | tee /tmp/tarpaulin.log
          COVERAGE=$(python3 -c "import json; d=json.load(open('coverage/tarpaulin-report.json')); print(f\"{d['coverage']:.1f}\")")
          echo "COVERAGE=$COVERAGE" >> $GITHUB_ENV
          echo "Coverage: $COVERAGE%"

      - name: Update coverage badge
        uses: schneegans/dynamic-badges-action@e9a478b16159b4d31420099ba146cdc50f134483 # v1.7.0
        with:
          auth: ${{ secrets.GIST_TOKEN }}
          gistID: ${{ vars.COVERAGE_GIST_ID }}
          filename: nora-coverage.json
          label: coverage
          message: ${{ env.COVERAGE }}%
          valColorRange: ${{ env.COVERAGE }}
          minColorRange: 0
          maxColorRange: 100

  security:
    name: Security
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          fetch-depth: 0

      - name: Install Rust
        uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable

      - name: Cache cargo
        uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

      # ── Secrets ────────────────────────────────────────────────────────────
      - name: Gitleaks — scan for hardcoded secrets
        run: |
          curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \
            | tar xz -C /usr/local/bin gitleaks
          gitleaks detect --source . --config .gitleaks.toml --exit-code 1 --report-format sarif --report-path gitleaks.sarif

      # ── CVE in Rust dependencies ────────────────────────────────────────────
      - name: Install cargo-audit
        run: cargo install cargo-audit --locked

      - name: cargo audit — RustSec advisory database
        run: |
          cargo audit --ignore RUSTSEC-2025-0119
          cargo audit --ignore RUSTSEC-2025-0119 --json > /tmp/audit.json || true

      - name: Upload cargo-audit results as SARIF
        if: always()
        run: |
          pip install --quiet cargo-audit-sarif 2>/dev/null || true
          python3 -c "
          import json, sys
          sarif = {
            'version': '2.1.0',
            '\$schema': 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json',
            'runs': [{'tool': {'driver': {'name': 'cargo-audit', 'version': '0.21', 'informationUri': 'https://github.com/rustsec/rustsec'}}, 'results': []}]
          }
          with open('cargo-audit.sarif', 'w') as f:
            json.dump(sarif, f)
          "

      - name: Upload SAST results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
        if: always()
        with:
          sarif_file: cargo-audit.sarif
          category: cargo-audit

      # ── Licenses, banned crates, supply chain policy ────────────────────────
      - name: cargo deny — licenses and banned crates
        uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2
        with:
          command: check
          arguments: --all-features

      # ── CVE scan of source tree and Cargo.lock ──────────────────────────────
      - name: Trivy — filesystem scan (Cargo.lock + source)
        if: always()
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          scan-type: fs
          scan-ref: .
          format: sarif
          output: trivy-fs.sarif
          severity: HIGH,CRITICAL
          exit-code: 1

      - name: Upload Trivy fs results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
        if: always()
        with:
          sarif_file: trivy-fs.sarif
          category: trivy-fs

  integration:
    name: Integration
    runs-on: ubuntu-latest
    needs: test

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable

      - name: Cache cargo
        uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

      - name: Build NORA
        run: cargo build --release --package nora-registry

      - name: Start NORA
        run: |
          NORA_STORAGE_PATH=/tmp/nora-data ./target/release/nora &
          for i in $(seq 1 15); do
            curl -sf http://localhost:4000/health && break || sleep 2
          done
          curl -sf http://localhost:4000/health | jq .

      - name: Configure Docker for insecure registry
        run: |
          echo '{"insecure-registries": ["localhost:4000"]}' | sudo tee /etc/docker/daemon.json
          sudo systemctl restart docker
          sleep 2

      - name: Docker — push and pull image
        run: |
          docker pull alpine:3.20
          docker tag alpine:3.20 localhost:4000/test/alpine:integration
          docker push localhost:4000/test/alpine:integration
          docker rmi localhost:4000/test/alpine:integration
          docker pull localhost:4000/test/alpine:integration
          echo "Docker push/pull OK"

      - name: Docker — verify catalog and tags
        run: |
          curl -sf http://localhost:4000/v2/_catalog | jq .
          curl -sf http://localhost:4000/v2/test/alpine/tags/list | jq .

      - name: npm — verify registry endpoint
        run: |
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/npm/lodash)
          echo "npm endpoint returned: $STATUS"
          [ "$STATUS" != "000" ] && echo "npm endpoint OK" || (echo "npm endpoint unreachable" && exit 1)

      - name: Maven — deploy and download artifact
        run: |
          echo "test-artifact-content-$(date +%s)" > /tmp/test-artifact.jar
          CHECKSUM=$(sha256sum /tmp/test-artifact.jar | cut -d' ' -f1)
          curl -sf -X PUT --data-binary @/tmp/test-artifact.jar \
            http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar
          curl -sf -o /tmp/downloaded.jar \
            http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar
          DOWNLOAD_CHECKSUM=$(sha256sum /tmp/downloaded.jar | cut -d' ' -f1)
          [ "$CHECKSUM" = "$DOWNLOAD_CHECKSUM" ] && echo "Maven deploy/download OK" || (echo "Checksum mismatch!" && exit 1)

      - name: PyPI — verify simple index
        run: |
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/simple/)
          echo "PyPI simple index returned: $STATUS"
          [ "$STATUS" = "200" ] && echo "PyPI endpoint OK" || (echo "Expected 200, got $STATUS" && exit 1)

      - name: Cargo — verify registry API responds
        run: |
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/cargo/api/v1/crates/serde)
          echo "Cargo API returned: $STATUS"
          [ "$STATUS" != "000" ] && echo "Cargo endpoint OK" || (echo "Cargo endpoint unreachable" && exit 1)

      - name: API — health, ready, metrics
        run: |
          curl -sf http://localhost:4000/health | jq .status
          curl -sf http://localhost:4000/ready
          curl -sf http://localhost:4000/metrics | head -5
          echo "API checks OK"

      - name: Stop NORA
        if: always()
        run: pkill nora || true
