name: CI Pipeline

on:
  push:
    branches: [main, develop]
    tags: ['v*', '[0-9]+.*']
  pull_request:
    branches: [main]

permissions:
  contents: read
  packages: write

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  IMAGE_NAME: christianlouis/docuelevate

jobs:
  # ══════════════════════════════════════════════════════════════════════════
  # Stage 1: Static Analysis (Fast Fail Gates)
  # ══════════════════════════════════════════════════════════════════════════
  lint:
    name: Ruff Lint & Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'
      - name: Install Ruff
        run: pip install ruff
      - name: Check for merge conflict markers
        run: |
          if git grep -rn -E '^(<{7} |>{7} |={7}$)' -- '.'; then
            echo "ERROR: Merge conflict markers found."
            exit 1
          fi
      - run: ruff check app/ tests/
      - run: ruff format --check app/ tests/

  html-lint:
    name: HTML Accessibility Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'
      - run: pip install djlint>=1.36.0
      - run: djlint frontend/templates/ --lint

  # ══════════════════════════════════════════════════════════════════════════
  # Stage 2: Parallel Heavy Lifters (Consolidated for Efficiency)
  # ══════════════════════════════════════════════════════════════════════════
  
  mypy:
    name: Mypy Type Check
    runs-on: ubuntu-latest
    needs: [lint]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'
      - name: Install Dependencies
        run: pip install -r requirements-dev.txt
      - run: mypy app/

  dependency-scan:
    name: Dependency Scan
    runs-on: ubuntu-latest
    needs: [lint]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'
      - run: pip install pip-audit>=2.7.0
      - run: pip-audit -r requirements.txt --desc on

  run-tests:
    name: Execute All Tests (Quick + Integration)
    runs-on: ubuntu-latest
    needs: [lint]
    services:
      redis:
        image: redis:7
        ports: ["6379:6379"]
        options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
      rabbitmq:
        image: rabbitmq:3-management
        ports: ["5672:5672", "15672:15672"]
        options: --health-cmd "rabbitmq-diagnostics -q ping" --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: 'pip'

      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements-dev.txt

      - name: Run Quick Tests
        run: >
          pytest tests/ -v --timeout=120 
          --cov=app --cov-report=xml:coverage.xml 
          --junitxml=junit-quick.xml -o junit_family=legacy
          -m "not e2e and not requires_docker and not requires_external and not slow"

      - name: Run Integration Tests
        if: success() || failure()
        run: >
          pytest tests/ -v --timeout=300 
          --cov=app --cov-append --cov-report=xml:coverage.xml 
          --junitxml=junit-integration.xml -o junit_family=legacy
          -m "(requires_docker or requires_external or slow) and not e2e"

      - name: Upload Unified Coverage to Codecov
        if: always()
        uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage.xml
          fail_ci_if_error: true

  # ══════════════════════════════════════════════════════════════════════════
  # Stage 3: Build & Push (Quality Gate)
  # ══════════════════════════════════════════════════════════════════════════
  build:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    needs: [run-tests, mypy, dependency-scan, html-lint]
    if: github.event_name == 'push'
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Generate Build Metadata
        run: |
          chmod +x scripts/generate_build_metadata.sh
          ./scripts/generate_build_metadata.sh
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Extract metadata for tags
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ env.IMAGE_NAME }}
            ghcr.io/${{ github.repository_owner }}/docuelevate
          tags: |
            type=ref,event=branch
            type=sha,prefix={{branch}}-
            type=semver,pattern={{version}}
            type=raw,value=latest,enable={{is_default_branch}}
      - name: Build and Push Docker Image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          sbom: true
          provenance: mode=max

  # ══════════════════════════════════════════════════════════════════════════
  # Stage 4: GitOps Update
  # ══════════════════════════════════════════════════════════════════════════
  update-k8s-manifest:
    name: Update Preprod K8s Manifest
    runs-on: ubuntu-latest
    needs: [build]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - name: Compute image tag
        id: tag
        run: |
          SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
          echo "tag=main-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
          echo "image=ghcr.io/${{ github.repository_owner }}/docuelevate:main-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
      - name: Checkout k8s-cluster-state
        uses: actions/checkout@v4
        with:
          repository: christianlouis/k8s-cluster-state
          token: ${{ secrets.GH_PAT }}
          path: k8s-cluster-state
      - name: Update image tag in preprod manifest
        uses: mikefarah/yq@v4.44.6
        env:
          IMAGE: ${{ steps.tag.outputs.image }}
        with:
          cmd: |
            yq -i '(.. | select(tag == "!!str") | select(test("^(ghcr\\.io/christianlouis/docuelevate|christianlouis/docuelevate):"))) = strenv(IMAGE)' \
              k8s-cluster-state/apps/docuelevate/preprod/docuelevate-stack.yaml
      - name: Commit and push
        run: |
          cd k8s-cluster-state
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add apps/docuelevate/preprod/docuelevate-stack.yaml
          if git diff --staged --quiet; then
            echo "No changes to commit"
          else
            git commit -m "chore(preprod): update docuelevate image to ${{ steps.tag.outputs.tag }}"
            git push
          fi
