name: CI

on:
  push:
    branches:
      - "**"
  pull_request:
    branches:
      - main

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-D warnings"
  # sccache caches compilation artifacts within a job so that compiling
  # both --features server and --features web shares common crate work.
  RUSTC_WRAPPER: /usr/local/bin/sccache
  SCCACHE_DIR: /tmp/sccache

# Cancel in-progress runs for the same branch/PR
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ---------------------------------------------------------------------------
  # Stage 1: Code quality checks (run in parallel)
  # ---------------------------------------------------------------------------
  fmt:
    name: Format
    runs-on: docker
    container:
      image: rust:1.94-bookworm
    steps:
      - name: Checkout
        run: |
          git init
          git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
          git fetch --depth=1 origin "${GITHUB_SHA}"
          git checkout FETCH_HEAD
      - run: rustup component add rustfmt
      # Format check does not compile, so sccache is not needed here.
      - run: cargo fmt --all --check
        env:
          RUSTC_WRAPPER: ""

  clippy:
    name: Clippy
    runs-on: docker
    container:
      image: rust:1.94-bookworm
    steps:
      - name: Checkout
        run: |
          git init
          git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
          git fetch --depth=1 origin "${GITHUB_SHA}"
          git checkout FETCH_HEAD
      - name: Install sccache
        run: |
          curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \
            | tar xz --strip-components=1 -C /usr/local/bin/ sccache-v0.9.1-x86_64-unknown-linux-musl/sccache
          chmod +x /usr/local/bin/sccache
      - run: rustup component add clippy
      # Lint the agent (native only).
      - name: Clippy (agent)
        run: cargo clippy -p compliance-agent -- -D warnings
      # Lint the dashboard for both feature sets independently.
      # sccache deduplicates shared crates between the two compilations.
      - name: Clippy (dashboard server)
        run: cargo clippy -p compliance-dashboard --features server --no-default-features -- -D warnings
      - name: Clippy (dashboard web)
        run: cargo clippy -p compliance-dashboard --features web --no-default-features -- -D warnings
      - name: Clippy (mcp)
        run: cargo clippy -p compliance-mcp -- -D warnings
      - name: Show sccache stats
        run: sccache --show-stats
        if: always()

  audit:
    name: Security Audit
    runs-on: docker
    if: github.ref == 'refs/heads/main'
    container:
      image: rust:1.94-bookworm
    steps:
      - name: Checkout
        run: |
          git init
          git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
          git fetch --depth=1 origin "${GITHUB_SHA}"
          git checkout FETCH_HEAD
      - run: cargo install cargo-audit
        env:
          RUSTC_WRAPPER: ""
      - run: cargo audit
        env:
          RUSTC_WRAPPER: ""

  # ---------------------------------------------------------------------------
  # Stage 2: Tests (only after all quality checks pass)
  # ---------------------------------------------------------------------------
  test:
    name: Tests
    runs-on: docker
    needs: [fmt, clippy, audit]
    container:
      image: rust:1.94-bookworm
    steps:
      - name: Checkout
        run: |
          git init
          git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
          git fetch --depth=1 origin "${GITHUB_SHA}"
          git checkout FETCH_HEAD
      - name: Install sccache
        run: |
          curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.9.1/sccache-v0.9.1-x86_64-unknown-linux-musl.tar.gz \
            | tar xz --strip-components=1 -C /usr/local/bin/ sccache-v0.9.1-x86_64-unknown-linux-musl/sccache
          chmod +x /usr/local/bin/sccache
      - name: Run tests (core + agent)
        run: cargo test -p compliance-core -p compliance-agent
      - name: Run tests (dashboard server)
        run: cargo test -p compliance-dashboard --features server --no-default-features
      - name: Run tests (dashboard web)
        run: cargo test -p compliance-dashboard --features web --no-default-features
      - name: Show sccache stats
        run: sccache --show-stats
        if: always()

  # ---------------------------------------------------------------------------
  # Stage 3: Deploy (only on main, after tests pass)
  # Each service only deploys when its relevant files changed.
  # ---------------------------------------------------------------------------
  detect-changes:
    name: Detect Changes
    runs-on: docker
    if: github.ref == 'refs/heads/main'
    needs: [test]
    container:
      image: alpine:latest
    outputs:
      agent: ${{ steps.changes.outputs.agent }}
      dashboard: ${{ steps.changes.outputs.dashboard }}
      docs: ${{ steps.changes.outputs.docs }}
      mcp: ${{ steps.changes.outputs.mcp }}
    steps:
      - name: Install git
        run: apk add --no-cache git
      - name: Checkout
        run: |
          git init
          git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
          git fetch --depth=2 origin "${GITHUB_SHA}"
          git checkout FETCH_HEAD
      - name: Detect changed paths
        id: changes
        run: |
          CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
          echo "Changed files:"
          echo "$CHANGED"

          # Agent: core libs, agent code, agent Dockerfile
          if echo "$CHANGED" | grep -qE '^(compliance-core/|compliance-agent/|compliance-graph/|compliance-dast/|Dockerfile\.agent|Cargo\.(toml|lock))'; then
            echo "agent=true" >> "$GITHUB_OUTPUT"
          else
            echo "agent=false" >> "$GITHUB_OUTPUT"
          fi

          # Dashboard: core libs, dashboard code, dashboard Dockerfile, assets
          if echo "$CHANGED" | grep -qE '^(compliance-core/|compliance-dashboard/|Dockerfile\.dashboard|Dioxus\.toml|assets/|bin/|Cargo\.(toml|lock))'; then
            echo "dashboard=true" >> "$GITHUB_OUTPUT"
          else
            echo "dashboard=false" >> "$GITHUB_OUTPUT"
          fi

          # Docs: docs folder, docs Dockerfile
          if echo "$CHANGED" | grep -qE '^(docs/|Dockerfile\.docs)'; then
            echo "docs=true" >> "$GITHUB_OUTPUT"
          else
            echo "docs=false" >> "$GITHUB_OUTPUT"
          fi

          # MCP: core libs, mcp code, mcp Dockerfile
          if echo "$CHANGED" | grep -qE '^(compliance-core/|compliance-mcp/|Dockerfile\.mcp|Cargo\.(toml|lock))'; then
            echo "mcp=true" >> "$GITHUB_OUTPUT"
          else
            echo "mcp=false" >> "$GITHUB_OUTPUT"
          fi

  deploy-agent:
    name: Deploy Agent
    runs-on: docker
    needs: [detect-changes]
    if: needs.detect-changes.outputs.agent == 'true'
    container:
      image: alpine:latest
    steps:
      - name: Trigger Coolify deploy
        run: |
          apk add --no-cache curl
          curl -sf "${{ secrets.COOLIFY_WEBHOOK_AGENT }}" \
            -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"

  deploy-dashboard:
    name: Deploy Dashboard
    runs-on: docker
    needs: [detect-changes]
    if: needs.detect-changes.outputs.dashboard == 'true'
    container:
      image: alpine:latest
    steps:
      - name: Trigger Coolify deploy
        run: |
          apk add --no-cache curl
          curl -sf "${{ secrets.COOLIFY_WEBHOOK_DASHBOARD }}" \
            -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"

  deploy-docs:
    name: Deploy Docs
    runs-on: docker
    needs: [detect-changes]
    if: needs.detect-changes.outputs.docs == 'true'
    container:
      image: alpine:latest
    steps:
      - name: Trigger Coolify deploy
        run: |
          apk add --no-cache curl
          curl -sf "${{ secrets.COOLIFY_WEBHOOK_DOCS }}" \
            -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"

  deploy-mcp:
    name: Deploy MCP
    runs-on: docker
    needs: [detect-changes]
    if: needs.detect-changes.outputs.mcp == 'true'
    container:
      image: alpine:latest
    steps:
      - name: Trigger Coolify deploy
        run: |
          apk add --no-cache curl
          curl -sf "${{ secrets.COOLIFY_WEBHOOK_MCP }}" \
            -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"
