---
name: "Build & Release pipeline"

on:
  pull_request:
    paths:
      - '.github/workflows/build-and-release.yml'
      - '.goreleaser.yml'
      - 'Dockerfile.multiarches'
      - 'Dockerfile.singlearch'
      - 'Makefile'
      - 'frontend/**'
      - 'integration-tests/**'
      - 'proto/**'
      - 'service/**'
      - 'var/windows/**'
  workflow_dispatch:
  push:
    tags:
      - '*'
    branches:
      - main
      - next
      - beta
    paths:
      - '.github/workflows/build-and-release.yml'
      - '.goreleaser.yml'
      - 'Dockerfile.multiarches'
      - 'Dockerfile.singlearch'
      - 'Makefile'
      - 'frontend/**'
      - 'integration-tests/**'
      - 'proto/**'
      - 'service/**'
      - 'var/windows/**'

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      new_release_published: ${{ steps.release.outputs.new_release_published }}
      new_release_git_tag: ${{ steps.release.outputs.new_release_git_tag }}
      windows_zip_artifact_id: ${{ steps.upload-windows-zip.outputs.artifact-id }}
      windows_msi_artifact_id: ${{ steps.upload-windows-msi.outputs.artifact-id }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set up QEMU
        id: qemu
        uses: docker/setup-qemu-action@v4
        with:
          image: tonistiigi/binfmt:latest
          platforms: arm64,arm

      - name: Setup node (npm cache)
        if: github.event_name != 'pull_request'
        uses: actions/setup-node@v6.4.0
        with:
          node-version: '22'
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json

      - name: Setup node
        if: github.event_name == 'pull_request'
        uses: actions/setup-node@v6.4.0
        with:
          node-version: '22'

      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version-file: 'service/go.mod'
          cache: true
          cache-dependency-path: 'service/go.mod'

      - name: Print go version
        run: go version

      - name: Login to Docker Hub
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_KEY }}

      - name: Login to ghcr
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.CONTAINER_TOKEN }}

      - name: get date
        run: |
          echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"

      - name: make webui
        run: make -w webui-dist

      - name: unit tests
        run: make -w service-unittests

      - name: frontend unit tests
        run: make -w frontend-unittests

      - name: build service
        run: make -w service

      - name: integration tests
        run: cd integration-tests && make -w

      - name: Archive integration tests
        uses: actions/upload-artifact@v7
        if: always()
        with:
          name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}"
          path: |
            integration-tests
            !integration-tests/node_modules

      - name: Install wixl and msitools
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        run: sudo apt-get update && sudo apt-get install -y wixl msitools

      - name: Install goreleaser
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        uses: goreleaser/goreleaser-action@v7
        with:
          install-only: true

      - name: Set up Docker Buildx
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        uses: docker/setup-buildx-action@v4

      - name: Verify macOS signing certificate chain
        if: github.ref_type != 'tag' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
        env:
          MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
          MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
        run: ./var/macos/verify-macos-sign-p12.sh

      - name: release
        id: release
        if: github.ref_type != 'tag' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
        uses: cycjimmy/semantic-release-action@v5
        with:
          extra_plugins: |
            @semantic-release/commit-analyzer
            @semantic-release/exec
            @semantic-release/git
        env:
          GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
          GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
          MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
          MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
          MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
          MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
          MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}

      - name: Upload unsigned Windows zip for SignPath
        id: upload-windows-zip
        if: steps.release.outputs.new_release_published == 'true'
        uses: actions/upload-artifact@v7
        with:
          # Upload as-is so SignPath receives OliveTin-windows-amd64.zip, not a wrapper zip.
          # With archive: false, the artifact name is the filename (`name` is ignored).
          path: dist/OliveTin-windows-amd64.zip
          archive: false
          if-no-files-found: error

      - name: Upload unsigned Windows MSI for SignPath
        id: upload-windows-msi
        if: steps.release.outputs.new_release_published == 'true'
        uses: actions/upload-artifact@v7
        with:
          path: dist/OliveTin-windows-amd64.msi
          archive: false
          if-no-files-found: error

      - name: Archive binaries
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
        uses: actions/upload-artifact@v7
        with:
          name: "OliveTin-snapshot-${{ env.DATE }}-${{ github.sha }}"
          path: dist/OliveTin*.*

  sign-windows:
    name: Sign Windows artifacts (SignPath)
    needs: build
    if: needs.build.outputs.new_release_published == 'true'
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: Require SignPath configuration
        env:
          SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
          SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
          SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }}
          SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
        run: |
          missing=0
          for name in SIGNPATH_API_TOKEN SIGNPATH_ORGANIZATION_ID SIGNPATH_PROJECT_SLUG SIGNPATH_SIGNING_POLICY_SLUG; do
            if [[ -z "${!name}" ]]; then
              echo "Missing required SignPath setting: ${name}" >&2
              missing=1
            fi
          done
          if [[ "${missing}" -ne 0 ]]; then
            echo "SignPath secrets/vars are required to upload signed Windows assets. Configure them (see docs/modules/dev/pages/signing.adoc)." >&2
            exit 1
          fi

      - name: Sign Windows zip
        uses: signpath/github-action-submit-signing-request@v2
        with:
          api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
          organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
          project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
          signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
          artifact-configuration-slug: windows-zip
          github-artifact-id: ${{ needs.build.outputs.windows_zip_artifact_id }}
          wait-for-completion: true
          # Preserve the signed .zip/.msi files; default decompress would unpack the zip.
          skip-decompress: true
          output-artifact-directory: signed-windows-zip

      - name: Sign Windows MSI
        uses: signpath/github-action-submit-signing-request@v2
        with:
          api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
          organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
          project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
          signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
          artifact-configuration-slug: windows-msi
          github-artifact-id: ${{ needs.build.outputs.windows_msi_artifact_id }}
          wait-for-completion: true
          skip-decompress: true
          output-artifact-directory: signed-windows-msi

      - name: Upload signed Windows assets
        env:
          GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
        run: |
          # SignPath may name the signed zip *.zip.zip (one compression level, bad Content-Disposition).
          if [[ -f signed-windows-zip/OliveTin-windows-amd64.zip.zip ]]; then
            mv signed-windows-zip/OliveTin-windows-amd64.zip.zip \
              signed-windows-zip/OliveTin-windows-amd64.zip
          fi
          zip_path="$(find signed-windows-zip -type f -name 'OliveTin-windows-amd64.zip' | head -n 1)"
          msi_path="$(find signed-windows-msi -type f -name 'OliveTin-windows-amd64.msi' | head -n 1)"
          if [[ -z "${zip_path}" || -z "${msi_path}" ]]; then
            echo "Signed Windows artifacts not found after SignPath:" >&2
            find signed-windows-zip signed-windows-msi -type f >&2 || true
            exit 1
          fi
          ./var/windows/signpath-publish-signed.sh \
            "${{ needs.build.outputs.new_release_git_tag }}" \
            "${zip_path}" \
            "${msi_path}"
