name: 🔒 Security & Quality

on:
  push:
    branches: [ master, main ]
  pull_request:
    branches: [ master, main ]
  schedule:
    # Run weekly security scans
    - cron: '0 6 * * 1'

# Least privilege by default; jobs opt into more only where they need it.
permissions:
  contents: read

# Supersede in-flight runs for the same ref — these are checks, not releases.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  security-scan:
    name: 🔍 Security Scan
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read
      security-events: write

    steps:
      - name: 📥 Checkout
        uses: actions/checkout@v7
        with:
          persist-credentials: false

      - name: 🔒 Run Trivy vulnerability scanner
        # Pinned to the v0.36.0 tag commit — @master is a mutable ref.
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          trivyignores: '.trivyignore'

      - name: 📤 Upload Trivy scan results to GitHub Security
        uses: github/codeql-action/upload-sarif@v4
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'
          category: 'trivy-filesystem'

      - name: 🐳 Build Docker image for security scan
        if: github.event_name != 'schedule'
        run: |
          docker build -t security-scan-image .

      - name: 🔒 Run Trivy on Docker image
        if: github.event_name != 'schedule'
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          image-ref: 'security-scan-image'
          format: 'sarif'
          output: 'trivy-docker-results.sarif'
          trivyignores: '.trivyignore'

      - name: 📤 Upload Docker scan results
        uses: github/codeql-action/upload-sarif@v4
        if: always() && github.event_name != 'schedule'
        with:
          sarif_file: 'trivy-docker-results.sarif'
          category: 'trivy-docker'

  dependency-check:
    name: 📦 Dependency Check
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      contents: read

    steps:
      - name: 📥 Checkout
        uses: actions/checkout@v7
        with:
          persist-credentials: false

      - name: 📦 Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '22'
          cache: 'npm'

      - name: 📥 Install dependencies
        run: npm ci

      - name: 🔍 Audit dependencies
        # Using audit-ci for allowlisting, npm audit for basic check (non-blocking)
        run: npm audit --audit-level=moderate || true

      - name: 📊 Check for outdated packages
        run: npm outdated || true

      - name: 🔒 Check for known vulnerabilities
        # audit-ci respects the allowlist in audit-ci.json — advisories with no
        # reachable fix (bundled/unpatched transitive dev deps). Each entry carries
        # a rationale + re-check trigger in $allowlist-rationale.
        # Version-pinned so the gate itself cannot shift under us.
        run: npx --yes audit-ci@7.1.0 --config audit-ci.json

  dockerfile-lint:
    name: 🐳 Dockerfile Lint
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: read
      security-events: write

    steps:
      - name: 📥 Checkout
        uses: actions/checkout@v7
        with:
          persist-credentials: false

      - name: 🔍 Lint Dockerfile
        uses: hadolint/hadolint-action@v3.1.0
        with:
          dockerfile: Dockerfile
          format: sarif
          output-file: hadolint-results.sarif
          no-fail: true

      - name: 📤 Upload Dockerfile lint results
        uses: github/codeql-action/upload-sarif@v4
        if: always()
        with:
          sarif_file: hadolint-results.sarif
          category: 'hadolint'

  test:
    name: 🧪 Test Suite
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read

    steps:
      - name: 📥 Checkout
        uses: actions/checkout@v7
        with:
          persist-credentials: false

      - name: 📦 Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '22'
          cache: 'npm'

      - name: 📥 Install dependencies
        run: npm ci

      - name: 🔍 Lint
        run: npm run lint

      - name: 🏗️ Build
        run: npm run build

      - name: 🔬 Type check
        run: npm run typecheck

      - name: 🧪 Test
        run: npm test

  quality-gates:
    name: 🚀 Quality Gates
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: [security-scan, dependency-check, dockerfile-lint, test]
    if: always()
    permissions:
      contents: read

    steps:
      - name: 📊 Quality Gate Summary
        run: |
          echo "## 🔒 Security & Quality Report" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          if [ "${{ needs.security-scan.result }}" == "success" ]; then
            echo "✅ **Security Scan:** Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "❌ **Security Scan:** Failed" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ needs.dependency-check.result }}" == "success" ]; then
            echo "✅ **Dependency Check:** Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "❌ **Dependency Check:** Failed" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ needs.dockerfile-lint.result }}" == "success" ]; then
            echo "✅ **Dockerfile Lint:** Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "❌ **Dockerfile Lint:** Failed" >> $GITHUB_STEP_SUMMARY
          fi

          if [ "${{ needs.test.result }}" == "success" ]; then
            echo "✅ **Test Suite:** Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "❌ **Test Suite:** Failed" >> $GITHUB_STEP_SUMMARY
          fi

          echo "" >> $GITHUB_STEP_SUMMARY
          echo "📋 **View detailed results in the Security tab**" >> $GITHUB_STEP_SUMMARY

      - name: 🚦 Enforce quality gates
        # Without this the job is summary-only and always green, so a red
        # dependency check or test suite would not fail this check.
        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
        run: |
          echo "::error::One or more quality gates failed — see the job summary above"
          exit 1
