name: CI

on:
  pull_request:
    branches:
      - develop
  push:
    branches:
      - develop
    tags:
      - "v*"

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}

permissions:
  contents: read

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: niklasrosenstein/rise
  BUILDER_IMAGE_NAME: niklasrosenstein/rise-builder
  CHART_REGISTRY: ghcr.io/niklasrosenstein/rise
  # renovate: datasource=github-tags depName=rust packageName=rust-lang/rust extractVersion=^(?<version>\d+\.\d+\.\d+)$
  RUST_VERSION: "1.95.0"

jobs:
  prepare:
    name: Prepare CI metadata
    runs-on: ubuntu-latest
    outputs:
      ci_version: ${{ steps.meta.outputs.ci_version }}
      image_tag: ${{ steps.meta.outputs.image_tag }}
      short_sha: ${{ steps.meta.outputs.short_sha }}
      is_trusted_pr: ${{ steps.meta.outputs.is_trusted_pr }}
      should_publish: ${{ steps.meta.outputs.should_publish }}
    steps:
      - name: Compute metadata
        id: meta
        shell: bash
        run: |
          set -euo pipefail

          short_sha="${GITHUB_SHA::7}"
          if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
            pr_number="${{ github.event.pull_request.number }}"
            ci_version="0.0.0-pr.${pr_number}+${short_sha}"
            image_tag="pr-${pr_number}-${short_sha}"
            is_trusted_pr="false"
            if [[ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then
              is_trusted_pr="true"
            fi
          elif [[ "${GITHUB_REF}" == refs/heads/develop ]]; then
            ci_version="0.0.0-develop.${GITHUB_RUN_NUMBER}+${short_sha}"
            image_tag="develop-${short_sha}"
            is_trusted_pr="false"
          elif [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
            version="${GITHUB_REF#refs/tags/v}"
            ci_version="${version}"
            image_tag="${version}"
            is_trusted_pr="false"
          else
            ci_version="0.0.0-dev.${GITHUB_RUN_NUMBER}+${short_sha}"
            image_tag="dev-${short_sha}"
            is_trusted_pr="false"
          fi

          should_publish="false"
          if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
            should_publish="true"
          elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" && "${is_trusted_pr}" == "true" ]]; then
            should_publish="true"
          fi

          echo "ci_version=${ci_version}" >> "$GITHUB_OUTPUT"
          echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
          echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT"
          echo "is_trusted_pr=${is_trusted_pr}" >> "$GITHUB_OUTPUT"
          echo "should_publish=${should_publish}" >> "$GITHUB_OUTPUT"

          echo "CI Version: ${ci_version}"
          echo "Image Tag: ${image_tag}"
          echo "Short SHA: ${short_sha}"
          echo "Is Trusted PR: ${is_trusted_pr}"
          echo "Should Publish: ${should_publish}"

  rust-quality:
    name: Rust quality checks
    runs-on: ubuntu-latest
    needs: prepare
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.RUST_VERSION }}
          components: rustfmt, clippy

      - name: Rust cache
        uses: Swatinem/rust-cache@v2

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g cargo:sqlx-cli cargo:cargo-all-features

      - name: Verify backend config schema is up to date
        run: mise config:schema:check

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

      - name: Run Clippy (all feature combinations)
        run: cargo all-features clippy --all-targets -- -D warnings
        env:
          SQLX_OFFLINE: true

      - name: Verify SQLX offline query data
        run: mise run sqlx:check

  unit-tests-linux:
    name: Linux tests
    runs-on: ubuntu-latest
    needs: prepare
    services:
      postgres:
        image: postgres:18
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: rise_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.RUST_VERSION }}

      - name: Rust cache
        uses: Swatinem/rust-cache@v2

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g cargo:sqlx-cli

      - name: Run database migrations
        run: |
          sqlx database create
          sqlx migrate run
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/rise_test

      - name: Run tests
        run: cargo test --all-features
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/rise_test

  helm-lint:
    name: Helm lint
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g helm

      - name: Set up chart-testing
        uses: helm/chart-testing-action@v2.8.0

      - name: Run chart-testing (lint)
        run: ct lint --target-branch ${{ github.event.repository.default_branch }} --charts helm/rise --validate-maintainers=false

      - name: Install kubeconform
        run: |
          set -euo pipefail
          curl -sSLo /tmp/kubeconform.tar.gz https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-linux-amd64.tar.gz
          tar -xzf /tmp/kubeconform.tar.gz -C /tmp kubeconform
          sudo install -m 0755 /tmp/kubeconform /usr/local/bin/kubeconform

      - name: Validate Helm templates with kubeconform
        run: |
          helm template rise helm/rise | kubeconform -strict -summary
          helm template rise helm/rise -f helm/rise/values-ci.yaml | kubeconform -strict -summary

  package-image:
    name: Build and publish image
    runs-on: ubuntu-latest
    needs:
      - prepare
      - rust-quality
      - unit-tests-linux
      - helm-lint
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        if: needs.prepare.outputs.should_publish == 'true'
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and optionally push image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ./Dockerfile
          target: rise
          push: ${{ needs.prepare.outputs.should_publish == 'true' }}
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.image_tag }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ needs.prepare.outputs.short_sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64
          provenance: false

      - name: Build and optionally push builder image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: ./Dockerfile
          target: rise-builder
          push: ${{ needs.prepare.outputs.should_publish == 'true' }}
          tags: |
            ${{ env.REGISTRY }}/${{ env.BUILDER_IMAGE_NAME }}:${{ needs.prepare.outputs.image_tag }}
            ${{ env.REGISTRY }}/${{ env.BUILDER_IMAGE_NAME }}:sha-${{ needs.prepare.outputs.short_sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64
          provenance: false

  package-helm:
    name: Package and publish Helm chart
    runs-on: ubuntu-latest
    needs:
      - prepare
      - package-image
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g helm

      - name: Patch chart version and appVersion for CI package
        shell: bash
        run: |
          set -euo pipefail
          ci_version="${{ needs.prepare.outputs.ci_version }}"
          image_tag="${{ needs.prepare.outputs.image_tag }}"
          sed -i "s/^version: .*/version: ${ci_version}/" helm/rise/Chart.yaml
          sed -i "s/^appVersion: .*/appVersion: \"${image_tag}\"/" helm/rise/Chart.yaml
          cat helm/rise/Chart.yaml

      - name: Package chart
        run: |
          mkdir -p .helm-packages
          helm package helm/rise -d .helm-packages

      - name: Upload chart artifact
        uses: actions/upload-artifact@v5
        with:
          name: helm-chart-${{ needs.prepare.outputs.image_tag }}
          path: .helm-packages/*.tgz

      - name: Login to GHCR for Helm OCI push
        if: needs.prepare.outputs.should_publish == 'true'
        run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ${{ env.REGISTRY }} -u "${{ github.actor }}" --password-stdin

      - name: Push chart to OCI registry
        if: needs.prepare.outputs.should_publish == 'true'
        run: |
          chart_file=$(ls .helm-packages/*.tgz)
          helm push "$chart_file" "oci://${{ env.CHART_REGISTRY }}"

  publish-crates:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    needs:
      - rust-quality
      - unit-tests-linux
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.RUST_VERSION }}

      - name: Verify version matches tag
        run: |
          TAG_VERSION=${GITHUB_REF#refs/tags/v}
          CARGO_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[0].version')
          echo "Tag version: $TAG_VERSION"
          echo "Cargo version: $CARGO_VERSION"
          if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
            echo "::error::Tag version ($TAG_VERSION) doesn't match Cargo version ($CARGO_VERSION)"
            exit 1
          fi

      - name: Authenticate with crates.io
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Publish to crates.io
        run: cargo publish --features cli
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

  e2e-build:
    name: E2E build tests
    runs-on: ubuntu-latest
    needs: prepare
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.RUST_VERSION }}

      - name: Rust cache
        uses: Swatinem/rust-cache@v2

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g pack railpack buildkit

      - name: Build Rise CLI
        run: cargo build --release --features cli

      - name: Run e2e build tests (no proxy)
        run: RISE_BIN=./target/release/rise bash tests/e2e-build/run.sh --no-proxy

      - name: Run e2e build tests (with proxy)
        run: RISE_BIN=./target/release/rise bash tests/e2e-build/run.sh --only-proxy

  e2e-minikube:
    name: Minikube end-to-end smoke tests
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
    needs:
      - prepare
      - package-image
      - package-helm
    permissions:
      contents: read
      packages: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup mise
        uses: jdx/mise-action@v4
        with:
          cache: true
          experimental: true

      - name: Install mise tools
        run: mise use -g minikube kubectl helm

      - name: Start Minikube and run smoke tests
        env:
          RISE_IMAGE_REPOSITORY: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          RISE_IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
        run: bash scripts/ci/e2e-minikube.sh
