name: macOS Package
on:
  push:
    branches:
      - master
      - maint
      - next
    tags:
      - "v*"
  pull_request:
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always

jobs:
  determine-versioning:
    runs-on: macos-latest
    outputs:
      macos_package_version: ${{ steps.macos_version.outputs.macos_package_version }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Derive macOS package version
        id: macos_version
        shell: bash
        run: |
          : ${GITHUB_OUTPUT:=/tmp/github_output}

          BASE_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
          if [[ "$GITHUB_REF" == refs/tags/* ]]; then
            VERSION="${GITHUB_REF_NAME#v}"
          else
            BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|[^A-Za-z0-9]|.|g; s/\.\.+/./g; s/^\.//; s/\.$//')
            HEIGHT=$(git rev-list --count HEAD)
            HASH=$(git rev-parse --short HEAD)
            if [[ -z "$BRANCH" ]]; then
              BRANCH="ref"
            fi
            VERSION="${BASE_VERSION}+${BRANCH}.${HEIGHT}.${HASH}"
          fi

          echo "macos_package_version=${VERSION}" >> "$GITHUB_OUTPUT"

  build:
    name: Build macOS package (${{ matrix.arch }})
    runs-on: ${{ matrix.os }}
    needs: determine-versioning

    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-latest
            arch: arm64
            target: aarch64-apple-darwin
          - os: macos-latest
            arch: x86_64
            target: x86_64-apple-darwin

    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set SOURCE_DATE_EPOCH from git
        run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"

      - name: Install Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          target: ${{ matrix.target }}
          cache: false
          rustflags: ''

      - name: Cache Cargo registry + build
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: macos-release-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            macos-release-${{ matrix.arch }}-

      - name: Build release binaries
        run: cargo build --release --target ${{ matrix.target }}

      - name: Build macOS package
        run: |
          packaging/macos/build-pkg.sh \
            --version "${{ needs.determine-versioning.outputs.macos_package_version }}" \
            --target ${{ matrix.target }} \
            --no-build

      - name: Resolve macOS asset path
        id: macos-assets
        shell: bash
        run: |
          : ${GITHUB_OUTPUT:=/tmp/github_output}
          set -euo pipefail

          # build-pkg.sh names the package from the build target, so each
          # matrix leg produces a distinctly named, arch-correct asset.
          # Assert that here: a regression in that naming then fails loudly
          # at the build stage instead of as a silent collision when the
          # release job merges both artifacts into one directory.
          EXPECTED="deploy/fips-${{ needs.determine-versioning.outputs.macos_package_version }}-macos-${{ matrix.arch }}.pkg"
          if [[ ! -f "$EXPECTED" ]]; then
            echo "Expected package $EXPECTED was not produced" >&2
            echo "deploy/ contains:" >&2
            ls -la deploy >&2 || true
            exit 1
          fi

          echo "pkg=$EXPECTED" >> "$GITHUB_OUTPUT"

      - name: Verify .pkg structural correctness
        shell: bash
        run: |
          set -euo pipefail
          PKG="${{ steps.macos-assets.outputs.pkg }}"
          EXPAND_DIR="$(mktemp -d)/expanded"
          PAYLOAD_DIR="$(mktemp -d)/payload"
          fail=0

          echo "==> Verifying $PKG"

          # 1) Flat-package expansion
          if pkgutil --expand "$PKG" "$EXPAND_DIR"; then
            echo "PASS: pkgutil --expand"
          else
            echo "FAIL: pkgutil --expand"
            fail=1
          fi

          # Extract the cpio.gz Payload so we can inspect installed file layout
          PAYLOAD_FILE="$(find "$EXPAND_DIR" -name Payload -type f | head -n 1)"
          if [[ -z "$PAYLOAD_FILE" ]]; then
            echo "FAIL: no Payload file inside expanded pkg"
            fail=1
          else
            mkdir -p "$PAYLOAD_DIR"
            (cd "$PAYLOAD_DIR" && gzip -dc "$PAYLOAD_FILE" | cpio -i --quiet)
            echo "PASS: extracted Payload to $PAYLOAD_DIR"
          fi

          # 2) Binary at canonical install path (./usr/local/bin/fips inside payload)
          BIN_PATH="$PAYLOAD_DIR/usr/local/bin/fips"
          if [[ -f "$BIN_PATH" ]]; then
            echo "PASS: binary present at usr/local/bin/fips"
          else
            echo "FAIL: binary missing at usr/local/bin/fips"
            echo "  fallback search:"
            find "$PAYLOAD_DIR" -name fips -type f -print || true
            fail=1
          fi
          for extra in fipsctl fipstop; do
            if [[ -f "$PAYLOAD_DIR/usr/local/bin/$extra" ]]; then
              echo "PASS: binary present at usr/local/bin/$extra"
            else
              echo "FAIL: binary missing at usr/local/bin/$extra"
              fail=1
            fi
          done

          # 3) LaunchDaemon plist at canonical location
          PLIST_PATH="$PAYLOAD_DIR/Library/LaunchDaemons/com.fips.daemon.plist"
          if [[ -f "$PLIST_PATH" ]]; then
            echo "PASS: plist present at Library/LaunchDaemons/com.fips.daemon.plist"
          else
            echo "FAIL: plist missing at Library/LaunchDaemons/com.fips.daemon.plist"
            echo "  fallback search:"
            find "$PAYLOAD_DIR" -name '*.plist' -print || true
            fail=1
          fi

          # 4) plutil -lint on the plist
          if [[ -f "$PLIST_PATH" ]]; then
            if plutil -lint "$PLIST_PATH"; then
              echo "PASS: plutil -lint"
            else
              echo "FAIL: plutil -lint"
              fail=1
            fi
          fi

          if [[ "$fail" -ne 0 ]]; then
            echo "==> .pkg verification FAILED"
            exit 1
          fi
          echo "==> .pkg verification PASSED"

      - name: SHA-256 hash and sidecar
        shell: bash
        run: |
          set -euo pipefail
          PKG="${{ steps.macos-assets.outputs.pkg }}"
          echo "==> macOS release asset:"
          # Capture the SHA-256 of the verified .pkg on the macOS runner and
          # write it to a sidecar file next to the .pkg, in the standard
          # `<hash>  <basename>` shasum format. The verify-handoff and release
          # jobs re-check the downloaded bytes against this value, so any
          # corruption introduced after this point is detected before
          # publication.
          ( cd "$(dirname "$PKG")" && shasum -a 256 "$(basename "$PKG")" | tee "$(basename "$PKG").sha256" )

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: fips_${{ needs.determine-versioning.outputs.macos_package_version }}_${{ matrix.arch }}_macos
          path: |
            ${{ steps.macos-assets.outputs.pkg }}
            ${{ steps.macos-assets.outputs.pkg }}.sha256
          retention-days: 30

      - name: Build summary
        run: |
          echo "Build Summary for macOS/${{ matrix.arch }}:"
          echo "  Package: ${{ steps.macos-assets.outputs.pkg }}"

  verify-handoff:
    name: Verify macOS package handoff integrity
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: Download macOS artifacts
        uses: actions/download-artifact@v8
        with:
          path: dist
          merge-multiple: true

      - name: Verify .pkg integrity across the handoff
        shell: bash
        run: |
          set -euo pipefail
          cd dist

          pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort)
          if [[ -z "$pkgs" ]]; then
            echo "FAIL: no .pkg artifacts were downloaded" >&2
            exit 1
          fi

          fail=0
          while IFS= read -r pkg; do
            base=$(basename "$pkg")
            sidecar="${pkg}.sha256"
            if [[ ! -f "$sidecar" ]]; then
              echo "FAIL: missing SHA-256 sidecar for $base" >&2
              fail=1
              continue
            fi
            expected=$(awk '{print $1}' "$sidecar")
            actual=$(sha256sum "$pkg" | awk '{print $1}')
            if [[ "$expected" != "$actual" ]]; then
              echo "FAIL: $base SHA-256 mismatch across the artifact handoff" >&2
              echo "  expected (macOS runner): $expected" >&2
              echo "  actual   (downloaded):   $actual" >&2
              fail=1
              continue
            fi
            echo "PASS: $base matches the macOS-runner SHA-256 ($actual)"
          done <<<"$pkgs"

          if [[ "$fail" -ne 0 ]]; then
            echo "==> macOS package handoff verification FAILED" >&2
            exit 1
          fi
          echo "==> macOS package handoff verification PASSED"

  release:
    name: Publish macOS assets to GitHub Release
    runs-on: ubuntu-latest
    needs: [build, verify-handoff]
    if: startsWith(github.ref, 'refs/tags/')
    permissions:
      contents: write

    steps:
      - name: Download macOS artifacts
        uses: actions/download-artifact@v8
        with:
          path: dist
          merge-multiple: true

      - name: Validate .pkg bytes before publishing
        shell: bash
        run: |
          set -euo pipefail
          cd dist

          pkgs=$(find . -maxdepth 1 -type f -name '*.pkg' | LC_ALL=C sort)
          if [[ -z "$pkgs" ]]; then
            echo "FAIL: no .pkg artifacts were downloaded" >&2
            exit 1
          fi

          fail=0
          while IFS= read -r pkg; do
            base=$(basename "$pkg")
            sidecar="${pkg}.sha256"
            if [[ ! -f "$sidecar" ]]; then
              echo "FAIL: missing SHA-256 sidecar for $base" >&2
              fail=1
              continue
            fi
            expected=$(awk '{print $1}' "$sidecar")
            actual=$(sha256sum "$pkg" | awk '{print $1}')
            if [[ "$expected" != "$actual" ]]; then
              echo "FAIL: $base SHA-256 mismatch on the bytes about to be published" >&2
              echo "  expected (macOS runner): $expected" >&2
              echo "  actual   (downloaded):   $actual" >&2
              fail=1
              continue
            fi
            echo "PASS: $base matches the macOS-runner SHA-256 ($actual)"
          done <<<"$pkgs"

          if [[ "$fail" -ne 0 ]]; then
            echo "==> pre-publish .pkg verification FAILED; not publishing" >&2
            exit 1
          fi
          echo "==> pre-publish .pkg verification PASSED"

      - name: Generate macOS release checksums
        run: |
          cd dist
          find . -maxdepth 1 -type f -name '*.pkg' -printf '%P\n' \
            | LC_ALL=C sort \
            | xargs sha256sum \
            > checksums-macos.txt

      - name: Wait for tag release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          for attempt in $(seq 1 20); do
            if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
              exit 0
            fi
            echo "Release ${GITHUB_REF_NAME} not available yet; waiting..."
            sleep 15
          done

          echo "Timed out waiting for release ${GITHUB_REF_NAME}" >&2
          exit 1

      - name: Upload macOS assets
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release upload "${GITHUB_REF_NAME}" \
            dist/*.pkg \
            dist/checksums-macos.txt \
            --clobber \
            --repo "${GITHUB_REPOSITORY}"
