# Copyright 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.

name: Build

on:
  push:
    branches:
      - main
      - "release/**"
    tags:
      - "v*"

  # Run when there is a label change on the pull request
  # This runs only if the 'Z-Build-Workflow' is added to the pull request
  pull_request:
    types: [labeled]

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

env:
  CARGO_TERM_COLOR: always
  CARGO_NET_GIT_FETCH_WITH_CLI: "true"
  SCCACHE_GHA_ENABLED: "true"
  RUSTC_WRAPPER: "sccache"
  IMAGE: ghcr.io/element-hq/matrix-authentication-service
  BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache
  DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

jobs:
  compute-version:
    name: Compute version using git describe
    if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
    runs-on: ubuntu-24.04

    permissions:
      contents: read

    outputs:
      describe: ${{ steps.git.outputs.describe }}
      timestamp: ${{ steps.git.outputs.timestamp }}

    steps:
      - name: Checkout the code
        uses: actions/checkout@v5
        with:
          # Need a full clone so that `git describe` reports the right version
          fetch-depth: 0

      - name: Compute version and timestamp out of git history
        id: git
        run: |
          echo "describe=$(git describe --tags --match 'v*.*.*' --always)" >> $GITHUB_OUTPUT
          echo "timestamp=$(git log -1 --format=%ct)" >> $GITHUB_OUTPUT

  build-assets:
    name: Build assets
    if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
    runs-on: ubuntu-24.04

    permissions:
      contents: read

    steps:
      - name: Checkout the code
        uses: actions/checkout@v5

      - uses: ./.github/actions/build-frontend
      - uses: ./.github/actions/build-policies

      - name: Prepare assets artifact
        run: |
          mkdir -p assets-dist/share
          cp policies/policy.wasm assets-dist/share/policy.wasm
          cp frontend/dist/manifest.json assets-dist/share/manifest.json
          cp -r frontend/dist/ assets-dist/share/assets
          cp -r templates/ assets-dist/share/templates
          cp -r translations/ assets-dist/share/translations
          cp LICENSE assets-dist/LICENSE
          chmod -R u=rwX,go=rX assets-dist/

      - name: Upload assets
        uses: actions/upload-artifact@v4.6.2
        with:
          name: assets
          path: assets-dist

  build-binaries:
    name: Build binaries
    if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
    runs-on: ubuntu-24.04

    needs:
      - compute-version

    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
          - target: aarch64-unknown-linux-gnu

    env:
      VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }}
      SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }}

    permissions:
      contents: read

    steps:
      - name: Checkout the code
        uses: actions/checkout@v5

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: |
            ${{ matrix.target }}

      - name: Setup sccache
        uses: mozilla-actions/sccache-action@v0.0.9

      - name: Install zig
        uses: goto-bus-stop/setup-zig@v2
        with:
          version: 0.13.0

      - name: Install cargo-zigbuild
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-zigbuild

      - name: Build the binary
        run: |
          cargo zigbuild \
            --release \
            --target ${{ matrix.target }}.2.17 \
            --no-default-features \
            --features dist \
            -p mas-cli

      - name: Upload binary artifact
        uses: actions/upload-artifact@v4.6.2
        with:
          name: binary-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/mas-cli

  assemble-archives:
    name: Assemble release archives
    if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
    runs-on: ubuntu-24.04

    needs:
      - build-assets
      - build-binaries

    permissions:
      contents: read

    steps:
      - name: Download assets
        uses: actions/download-artifact@v5
        with:
          name: assets
          path: assets-dist

      - name: Download binary x86_64
        uses: actions/download-artifact@v5
        with:
          name: binary-x86_64-unknown-linux-gnu
          path: binary-x86_64

      - name: Download binary aarch64
        uses: actions/download-artifact@v5
        with:
          name: binary-aarch64-unknown-linux-gnu
          path: binary-aarch64

      - name: Create final archives
        run: |
          for arch in x86_64 aarch64; do
            mkdir -p dist/${arch}/share
            cp -r assets-dist/share/* dist/${arch}/share/
            cp assets-dist/LICENSE dist/${arch}/LICENSE
            cp binary-$arch/mas-cli dist/${arch}/mas-cli
            chmod -R u=rwX,go=rX dist/${arch}/
            chmod u=rwx,go=rx dist/${arch}/mas-cli
            tar -czvf mas-cli-${arch}-linux.tar.gz --owner=0 --group=0 -C dist/${arch}/ .
          done

      - name: Upload aarch64 archive
        uses: actions/upload-artifact@v4.6.2
        with:
          name: mas-cli-aarch64-linux
          path: mas-cli-aarch64-linux.tar.gz

      - name: Upload x86_64 archive
        uses: actions/upload-artifact@v4.6.2
        with:
          name: mas-cli-x86_64-linux
          path: mas-cli-x86_64-linux.tar.gz

  build-image:
    name: Build and push Docker image
    if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
    runs-on: ubuntu-24.04

    outputs:
      metadata: ${{ steps.output.outputs.metadata }}

    permissions:
      contents: read
      packages: write
      id-token: write

    needs:
      - compute-version

    env:
      VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }}
      SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }}

    steps:
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5.8.0
        with:
          images: "${{ env.IMAGE }}"
          bake-target: docker-metadata-action
          flavor: |
            latest=auto
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha

      - name: Docker meta (debug variant)
        id: meta-debug
        uses: docker/metadata-action@v5.8.0
        with:
          images: "${{ env.IMAGE }}"
          bake-target: docker-metadata-action-debug
          flavor: |
            latest=auto
            suffix=-debug,onlatest=true
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha

      - name: Setup Cosign
        uses: sigstore/cosign-installer@v4.0.0

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3.11.1
        with:
          buildkitd-config-inline: |
            [registry."docker.io"]
              mirrors = ["mirror.gcr.io"]

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3.6.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        id: bake
        uses: docker/bake-action@v6.9.0
        with:
          files: |
            ./docker-bake.hcl
            cwd://${{ steps.meta.outputs.bake-file }}
            cwd://${{ steps.meta-debug.outputs.bake-file }}
          set: |
            base.output=type=image,push=true
            base.cache-from=type=registry,ref=${{ env.BUILDCACHE }}:buildcache
            base.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache,mode=max

      - name: Transform bake output
        # This transforms the ouput to an object which looks like this:
        # { reguar: { digest: "…", tags: ["…", "…"] }, debug: { digest: "…", tags: ["…"] }, … }
        id: output
        run: |
          echo 'metadata<<EOF' >> $GITHUB_OUTPUT
          echo '${{ steps.bake.outputs.metadata }}' | jq -c 'with_entries(select(.value | (type == "object" and has("containerimage.digest")))) | map_values({ digest: .["containerimage.digest"], tags: (.["image.name"] | split(",")) })' >> $GITHUB_OUTPUT
          echo 'EOF' >> $GITHUB_OUTPUT

      - name: Sign the images with GitHub Actions provided token
        # Only sign on tags and on commits on main branch
        if: |
          github.event_name != 'pull_request'
          && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')

        env:
          REGULAR_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).regular.digest }}
          DEBUG_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).debug.digest }}

        run: |-
          cosign sign --yes \
            "$IMAGE@$REGULAR_DIGEST" \
            "$IMAGE@$DEBUG_DIGEST" \

  release:
    name: Release
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-24.04
    needs:
      - assemble-archives
      - build-image
    steps:
      - name: Download the artifacts from the previous job
        uses: actions/download-artifact@v5
        with:
          pattern: mas-cli-*
          path: artifacts
          merge-multiple: true

      - name: Prepare a release
        uses: softprops/action-gh-release@v2.4.1
        with:
          generate_release_notes: true
          body: |
            ### Docker image

            Regular image:

              - Digest:
                ```
                ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }}
                ```
              - Tags:
                ```
                ${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, '
                ') }}
                ```

            Debug variant:

              - Digest:
                ```
                ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }}
                ```
              - Tags:
                ```
                ${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, '
                ') }}
                ```

          files: |
            artifacts/mas-cli-aarch64-linux.tar.gz
            artifacts/mas-cli-x86_64-linux.tar.gz
          draft: true

  unstable:
    name: Update the unstable release
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-24.04

    needs:
      - assemble-archives
      - build-image

    permissions:
      contents: write

    steps:
      - name: Checkout the code
        uses: actions/checkout@v5
        with:
          sparse-checkout: |
            .github/scripts

      - name: Download the artifacts from the previous job
        uses: actions/download-artifact@v5
        with:
          pattern: mas-cli-*
          path: artifacts
          merge-multiple: true

      - name: Update unstable git tag
        uses: actions/github-script@v8.0.0
        with:
          script: |
            const script = require('./.github/scripts/update-unstable-tag.cjs');
            await script({ core, github, context });

      - name: Update unstable release
        uses: softprops/action-gh-release@v2.4.1
        with:
          name: "Unstable build"
          tag_name: unstable
          body: |
            This is an automatically updated unstable release containing the latest builds from the main branch.

            **⚠️ Warning: These are development builds and may be unstable.**

            Last updated: ${{ github.event.head_commit.timestamp }}
            Commit: ${{ github.sha }}

            ### Docker image

            Regular image:

              - Digest:
                ```
                ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }}
                ```
              - Tags:
                ```
                ${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, '
                ') }}
                ```

            Debug variant:

              - Digest:
                ```
                ${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }}
                ```
              - Tags:
                ```
                ${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, '
                ') }}
                ```

          files: |
            artifacts/mas-cli-aarch64-linux.tar.gz
            artifacts/mas-cli-x86_64-linux.tar.gz
          prerelease: true
          make_latest: false

  pr-cleanup:
    name: "Remove workflow build PR label and comment on it"
    runs-on: ubuntu-24.04
    if: github.event_name == 'pull_request' && github.event.label.name == 'Z-Build-Workflow'

    needs:
      - build-image

    permissions:
      contents: read
      pull-requests: write

    steps:
      - name: Checkout the code
        uses: actions/checkout@v5
        with:
          sparse-checkout: |
            .github/scripts

      - name: Remove label and comment
        uses: actions/github-script@v8.0.0
        env:
          BUILD_IMAGE_MANIFEST: ${{ needs.build-image.outputs.metadata }}
        with:
          script: |
            const script = require('./.github/scripts/cleanup-pr.cjs');
            await script({ core, github, context });
