# Build and publish Docker images to GitHub Container Registry
#
# Triggers:
# - On release: Build with version tags (X.Y.Z, X.Y, latest)
# - On schedule: Rebuild all tags with fresh base image (OS patches)
# - Manual: For testing, optional push
#
# Security:
# - All actions pinned by SHA
# - Vulnerability scanning with Trivy
# - SBOM and provenance attestation

name: Docker Build and Publish

on:
  release:
    types: [published]

  schedule:
    # Daily at 4 AM UTC - rebuild with fresh base image
    - cron: "0 4 * * *"

  pull_request:
    paths:
      - Dockerfile
      - .dockerignore
      - docker/**
      - pyproject.toml
      - uv.lock
      - src/**
      - scripts/**
      - .github/workflows/docker-publish.yml

  workflow_dispatch:
    inputs:
      push:
        description: "Push image to registry"
        required: false
        default: false
        type: boolean

permissions:
  contents: read
  packages: write
  id-token: write
  attestations: write
  artifact-metadata: write

concurrency:
  group: docker-${{ github.ref }}
  cancel-in-progress: true

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      # For nightly builds, get the latest release version
      - name: Get latest release version
        id: get-version
        if: github.event_name == 'schedule'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Get latest release tag
          LATEST_TAG=$(gh release view --json tagName -q '.tagName' 2>/dev/null || echo "")
          if [ -z "$LATEST_TAG" ]; then
            echo "No releases found, skipping nightly build"
            echo "skip=true" >> $GITHUB_OUTPUT
            exit 0
          fi
          # Strip 'v' prefix if present
          VERSION=$(echo "$LATEST_TAG" | sed 's/^v//')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "skip=false" >> $GITHUB_OUTPUT

      - name: Skip if no releases
        if: github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true'
        run: |
          echo "No releases found, skipping nightly build"
          exit 0

      - name: Set up QEMU
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0

      - name: Set up Docker Buildx
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Log in to Container Registry
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Generate tags based on event type
      - name: Extract metadata (release)
        id: meta-release
        if: github.event_name == 'release'
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            # X.Y.Z
            type=semver,pattern={{version}}
            # X.Y
            type=semver,pattern={{major}}.{{minor}}
            # latest
            type=raw,value=latest

      - name: Extract metadata (nightly)
        id: meta-nightly
        if: github.event_name == 'schedule' && steps.get-version.outputs.skip != 'true'
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            # Rebuild version tags with OS patches
            type=raw,value=${{ steps.get-version.outputs.version }}
            # Nightly tags
            type=raw,value=nightly
            type=raw,value=nightly-{{date 'YYYYMMDD'}}
            # Also update latest with security patches
            type=raw,value=latest

      - name: Extract metadata (manual)
        id: meta-manual
        if: github.event_name == 'workflow_dispatch'
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=sha-

      # Build image (release - with cache)
      - name: Build and push (release)
        id: build-release
        if: github.event_name == 'release'
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta-release.outputs.tags }}
          labels: ${{ steps.meta-release.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: true
          sbom: true

      # Build image (nightly - no cache, fresh base)
      - name: Build and push (nightly)
        id: build-nightly
        if: github.event_name == 'schedule' && steps.get-version.outputs.skip != 'true'
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta-nightly.outputs.tags }}
          labels: ${{ steps.meta-nightly.outputs.labels }}
          pull: true
          no-cache: true
          provenance: true
          sbom: true

      # Build image (manual)
      - name: Build and push (manual)
        id: build-manual
        if: github.event_name == 'workflow_dispatch'
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ inputs.push }}
          tags: ${{ steps.meta-manual.outputs.tags }}
          labels: ${{ steps.meta-manual.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: true
          sbom: true

      # Determine image tag for scanning and testing
      - name: Determine image tag
        id: image-tag
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            # Strip 'v' prefix to match semver tag format from metadata-action
            echo "tag=$(echo '${{ github.event.release.tag_name }}' | sed 's/^v//')" >> $GITHUB_OUTPUT
          elif [ "${{ github.event_name }}" = "schedule" ]; then
            echo "tag=nightly" >> $GITHUB_OUTPUT
          else
            echo "tag=sha-${{ github.sha }}" >> $GITHUB_OUTPUT
          fi

      # Vulnerability scanning
      - name: Run Trivy vulnerability scanner
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }}
          format: "sarif"
          output: "trivy-results.sarif"
          severity: "CRITICAL,HIGH"
        continue-on-error: true

      - name: Upload Trivy scan results
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true')"
        uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
        with:
          sarif_file: "trivy-results.sarif"
        continue-on-error: true

      # Smoke test - verify image runs correctly
      # Skip for manual runs when push is disabled (image not available to pull)
      - name: Smoke test
        if: "!(github.event_name == 'schedule' && steps.get-version.outputs.skip == 'true') && !(github.event_name == 'workflow_dispatch' && inputs.push == false)"
        run: |
          IMAGE_TAG="${{ steps.image-tag.outputs.tag }}"

          docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}

          # Test that Python and key modules are available
          docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG} \
            python -c "from meshmon.db import init_db; from meshmon.env import get_config; print('Smoke test passed')"

      # Attestation (releases only)
      - name: Generate attestation
        if: github.event_name == 'release'
        uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
        with:
          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          subject-digest: ${{ steps.build-release.outputs.digest }}
          push-to-registry: true

  build-pr:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Build image (PR)
        id: build-pr
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          platforms: linux/amd64
          load: true
          push: false
          tags: meshcore-stats:pr-${{ github.event.pull_request.number }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Smoke test (PR)
        run: |
          docker run --rm meshcore-stats:pr-${{ github.event.pull_request.number }} \
            python -c "from meshmon.db import init_db; from meshmon.env import get_config; print('Smoke test passed')"
