name: Push Release Images to Docker Hub and GitHub Container Registry

on:
  push:
    branches:
      - "release"
  workflow_dispatch:
    inputs:
      publish_android_to_store:
        description: 'Publish Android app to Google Play Store'
        required: false
        type: boolean
        default: false
      publish_ios_to_store:
        description: 'Publish iOS app to App Store'
        required: false
        type: boolean
        default: false

permissions:
  contents: write
  packages: write

jobs:
  generate-build-number:
    runs-on: ubuntu-latest
    outputs:
      build_number: ${{ steps.buildnumber.outputs.build_number }}
    steps:
    - name: Generate build number
      id: buildnumber
      uses: onyxmueller/build-tag-number@v1.0.2
      with:
        token: ${{secrets.github_token}}
    - run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"


  read-version:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      major_minor: ${{ steps.determine.outputs.semver_base }}
      semver_base: ${{ steps.determine.outputs.semver_base }}
      major: ${{ steps.determine.outputs.major }}
      minor: ${{ steps.determine.outputs.minor }}
      patch: ${{ steps.determine.outputs.patch }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}
      - name: Check version and verify not already released
        id: determine
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          REPOSITORY: ${{ github.repository }}
        run: |
          set -euo pipefail

          VERSION_RAW="$(tr -d ' \n' < VERSION)"
          if [[ -z "$VERSION_RAW" ]]; then
            echo "VERSION is empty" >&2
            exit 1
          fi

          echo "Version from VERSION file: $VERSION_RAW"

          IFS='.' read -r major minor patch <<< "$VERSION_RAW"
          if [[ -z "$minor" ]]; then
            echo "VERSION must contain major and minor components" >&2
            exit 1
          fi
          patch="${patch:-0}"

          for part_name in major minor patch; do
            part="${!part_name}"
            if ! [[ "$part" =~ ^[0-9]+$ ]]; then
              echo "Invalid ${part_name} component '$part' in VERSION" >&2
              exit 1
            fi
          done

          # Check if this version is already released on GitHub
          echo "Checking if version $VERSION_RAW is already released on GitHub..."

          if gh release view "$VERSION_RAW" --repo "$REPOSITORY" &>/dev/null; then
            echo "::error::Version $VERSION_RAW is already released on GitHub. Please update the VERSION file to a new version."
            exit 1
          fi

          # Also check with 'v' prefix just in case
          if gh release view "v$VERSION_RAW" --repo "$REPOSITORY" &>/dev/null; then
            echo "::error::Version v$VERSION_RAW is already released on GitHub. Please update the VERSION file to a new version."
            exit 1
          fi

          echo "✅ Version $VERSION_RAW is not yet released. Proceeding with release."

          echo "semver_base=${VERSION_RAW}" >> "$GITHUB_OUTPUT"
          echo "major=${major}" >> "$GITHUB_OUTPUT"
          echo "minor=${minor}" >> "$GITHUB_OUTPUT"
          echo "patch=${patch}" >> "$GITHUB_OUTPUT"
          echo "Using version: ${VERSION_RAW}"



  helm-chart-deploy:
    runs-on: ubuntu-latest
    needs: [generate-build-number, read-version]
    env:
        CI_COMMIT_AUTHOR: Continuous Integration
    steps:

      - name: Install Helm
        run: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name:  Build and Package Helm chart
        run: |
          cd ..
          echo '${{ secrets.GPG_PRIVATE_KEY }}' > private.key
          gpg --import private.key || true
          rm private.key
          echo "GPG key imported successfully"
          gpg --export-secret-keys >~/.gnupg/secring.gpg
          echo "GPG key exported successfully"
          eval `ssh-agent -s`
          ssh-add - <<< '${{ secrets.HELM_CHART_GITHUB_REPO_DEPLOY_KEY }}'
          git clone git@github.com:OneUptime/helm-chart.git
          cd oneuptime/HelmChart/Public
          helm lint oneuptime
          helm template oneuptime --values oneuptime/values.yaml
          helm package --sign --key 'key@oneuptime.com' --keyring ~/.gnupg/secring.gpg oneuptime --version ${{needs.read-version.outputs.major_minor}} --app-version ${{needs.read-version.outputs.major_minor}}
          echo "OneUptime Helm Chart Package created successfully"
          helm lint kubernetes-agent
          helm template kubernetes-agent --values kubernetes-agent/values.yaml
          helm package --sign --key 'key@oneuptime.com' --keyring ~/.gnupg/secring.gpg kubernetes-agent --version ${{needs.read-version.outputs.major_minor}} --app-version ${{needs.read-version.outputs.major_minor}}
          echo "Kubernetes Agent Helm Chart Package created successfully"
          cd ..
          ls
          echo "Copying the package to helm-chart repo"
          rm -rf ../../helm-chart/oneuptime
          rm -rf ../../helm-chart/kubernetes-agent
          cp -r ./Public/* ../../helm-chart
          echo "Package copied successfully"
          cd .. && cd .. && cd helm-chart
          echo "Updating helm-chart repo"
          git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
          git config --global user.email "hello@oneuptime.com"
          echo "Git config set successfully"
          echo "Adding the package to helm-chart repo"
          helm repo index .
          git add -A
          git commit -m "Helm Chart Release ${{needs.read-version.outputs.major_minor}}"
          git push origin master

  # ─── Docker image build jobs (per-arch matrix) ───────────────────────

  nginx-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image nginx \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./Nginx/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  nginx-docker-image-merge:
    needs: [nginx-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image nginx \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  e2e-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image e2e \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./E2E/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  e2e-docker-image-merge:
    needs: [e2e-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image e2e \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  home-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image home \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./Home/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  home-docker-image-merge:
    needs: [home-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image home \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  test-server-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image test-server \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./TestServer/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  test-server-docker-image-merge:
    needs: [test-server-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image test-server \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  test-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image test \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./Tests/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  test-docker-image-merge:
    needs: [test-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image test \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  probe-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image probe \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./Probe/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  probe-docker-image-merge:
    needs: [probe-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image probe \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  app-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image app \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./App/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  app-docker-image-merge:
    needs: [app-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image app \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  ai-agent-docker-image-build:
    needs: [generate-build-number, read-version]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Free Disk Space (Ubuntu)
        if: matrix.platform == 'linux/amd64'
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true

      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

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

      - name: Generate Dockerfile from Dockerfile.tpl
        run: npm run prerun

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Build and push
        run: |
          bash ./Scripts/GHA/build_docker_images.sh \
            --image ai-agent \
            --version "${{needs.read-version.outputs.major_minor}}" \
            --dockerfile ./AIAgent/Dockerfile \
            --context . \
            --platforms ${{ matrix.platform }} \
            --git-sha "${{ github.sha }}"

  ai-agent-docker-image-merge:
    needs: [ai-agent-docker-image-build, read-version]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

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

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Merge multi-arch manifests
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          bash ./Scripts/GHA/merge_docker_manifests.sh \
            --image ai-agent \
            --tags "${SANITIZED_VERSION},enterprise-${SANITIZED_VERSION}"

  publish-npm-packages:
    runs-on: ubuntu-latest
    needs: [generate-build-number, read-version]
    permissions:
      contents: read
      id-token: write  # Required for npm OIDC trusted publishing
    env:
      CI_PIPELINE_ID: ${{github.run_number}}
      PACKAGE_VERSION: ${{needs.read-version.outputs.major_minor}}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: latest
          registry-url: 'https://registry.npmjs.org'
      - name: Update npm for OIDC support
        run: npm install -g npm@latest
      - name: Preinstall
        run: npm run prerun
      - name: Publish NPM Packages
        run: bash ./Scripts/NPM/PublishAllPackages.sh

  # ─── Non-Docker jobs (downstream dependencies updated) ───────────────

  publish-terraform-provider:
    runs-on: ubuntu-latest
    needs: [generate-build-number, read-version]
    env:
      CI_PIPELINE_ID: ${{github.run_number}}
      GITHUB_TOKEN: ${{ secrets.SIMLARSEN_GITHUB_PAT }}
      GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
      TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY: ${{ secrets.TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY }}
    permissions:
      contents: write  # For creating releases
      packages: write  # For publishing packages
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 'latest'
          cache: 'npm'

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: 'stable'
          cache: true

      - name: Install GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          install-only: true

      - name: Determine version
        id: version
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Publishing Terraform provider version: $VERSION"

      - name: Install dependencies
        run: |
          npm install
          if [ -d "Common" ]; then cd Common && npm install && cd ..; fi
          if [ -d "Scripts" ]; then cd Scripts && npm install && cd ..; fi

      - name:  Import GPG key
        run: |
          echo '${{ secrets.GPG_PRIVATE_KEY }}' > private.key
          gpg --import private.key || true
          rm private.key
          echo "GPG key imported successfully"
          gpg --export-secret-keys >~/.gnupg/secring.gpg
          echo "GPG key exported successfully"

      - name: Generate and publish Terraform provider
        run: npm run publish-terraform-provider -- --version "${{ steps.version.outputs.version }}" --github-token "${{ secrets.SIMLARSEN_GITHUB_PAT }}" --github-repo-deploy-key "${{ secrets.TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY }}"



  push-release-tags:
    name: Push release tags before GitHub release
    needs:
      - read-version
      - generate-build-number
      - nginx-docker-image-merge
      - e2e-docker-image-merge
      - home-docker-image-merge
      - test-server-docker-image-merge
      - test-docker-image-merge
      - probe-docker-image-merge
      - app-docker-image-merge
      - ai-agent-docker-image-merge
      - test-e2e-release-saas
      - test-e2e-release-self-hosted
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        image: [
          "nginx",
          "e2e",
          "home",
          "test-server",
          "test",
          "probe",
          "app",
          "ai-agent"
        ]
    steps:
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        run: |
          echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

      - name: Login to GitHub Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin

      - name: Create Docker Hub release tag from version
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          docker buildx imagetools create \
            --tag oneuptime/${{ matrix.image }}:release \
            ghcr.io/oneuptime/${{ matrix.image }}:${VERSION}

      - name: Create GHCR release tag from version
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          docker buildx imagetools create \
            --tag ghcr.io/oneuptime/${{ matrix.image }}:release \
            ghcr.io/oneuptime/${{ matrix.image }}:${VERSION}

      - name: Create Docker Hub enterprise release tag from version
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          docker buildx imagetools create \
            --tag oneuptime/${{ matrix.image }}:enterprise-release \
            ghcr.io/oneuptime/${{ matrix.image }}:enterprise-${VERSION}

      - name: Create GHCR enterprise release tag from version
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          docker buildx imagetools create \
            --tag ghcr.io/oneuptime/${{ matrix.image }}:enterprise-release \
            ghcr.io/oneuptime/${{ matrix.image }}:enterprise-${VERSION}



  test-e2e-release-saas:
    runs-on: ubuntu-latest
    needs: [ai-agent-docker-image-merge, app-docker-image-merge, home-docker-image-merge, probe-docker-image-merge, test-docker-image-merge, test-server-docker-image-merge, publish-npm-packages, e2e-docker-image-merge, helm-chart-deploy, generate-build-number, read-version, nginx-docker-image-merge]
    env:
      CI_PIPELINE_ID: ${{github.run_number}}
    steps:
      # Aggressively free disk space before anything else
      - name: Aggressive Disk Cleanup
        run: |
          echo "=== Disk space BEFORE cleanup ==="
          df -h /
          # Remove pre-installed software not needed for this job
          sudo rm -rf /usr/share/dotnet || true
          sudo rm -rf /usr/local/lib/android || true
          sudo rm -rf /opt/ghc || true
          sudo rm -rf /opt/hostedtoolcache || true
          sudo rm -rf /usr/local/share/boost || true
          sudo rm -rf /usr/local/graalvm/ || true
          sudo rm -rf /usr/local/share/powershell || true
          sudo rm -rf /usr/local/share/chromium || true
          sudo rm -rf /usr/local/lib/node_modules || true
          sudo rm -rf /usr/share/swift || true
          sudo rm -rf /usr/share/miniconda || true
          sudo rm -rf /usr/lib/google-cloud-sdk || true
          sudo rm -rf /usr/lib/jvm || true
          sudo rm -rf /usr/lib/firefox || true
          sudo rm -rf /usr/lib/heroku || true
          sudo rm -rf /usr/local/julia* || true
          sudo rm -rf /opt/az || true
          sudo rm -rf /opt/microsoft || true
          sudo rm -rf /opt/pipx || true
          sudo rm -rf /opt/actionarchivecache || true
          sudo rm -rf /imagegeneration || true
          sudo rm -rf /usr/share/az_* || true
          sudo rm -rf /usr/share/sbt || true
          sudo rm -rf /usr/share/gradle* || true
          sudo rm -rf /usr/share/kotlinc || true
          sudo rm -rf /usr/share/ri || true
          sudo rm -rf /usr/local/.ghcup || true
          # Clean apt cache
          sudo apt-get clean || true
          sudo rm -rf /var/lib/apt/lists/* || true
          # Clean temp files
          sudo rm -rf /tmp/* || true
          # Docker cleanup
          docker system prune -af --volumes || true
          echo "=== Disk space AFTER aggressive cleanup ==="
          df -h /
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - name: Final Disk Space Check
        run: |
          echo "=== Disk space after all cleanup ==="
          df -h /
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: latest
      - name: Preinstall and enable billing
        run: |
          set -euo pipefail
          npm run prerun
          bash ./Tests/Scripts/enable-billing-env-var.sh
      - name: Pin APP_TAG to versioned release
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          if [ -f config.env ]; then
            if grep -q '^APP_TAG=' config.env; then
              sed -i "s/^APP_TAG=.*/APP_TAG=${SANITIZED_VERSION}/" config.env
            else
              echo "APP_TAG=${SANITIZED_VERSION}" >> config.env
            fi
          else
            echo "APP_TAG=${SANITIZED_VERSION}" > config.env
          fi
      - name: Start Server with version tag
        run: |
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          docker compose -f docker-compose.billing.yml up --remove-orphans -d
          npm run status-check
      - name: Wait for server to start
        run: bash ./Tests/Scripts/status-check.sh http://localhost
      - name: Pull E2E test image
        run: |
          set -euo pipefail
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          docker compose -f docker-compose.e2e.yml pull e2e
      - name: Run E2E Tests. Run docker container e2e in docker compose file
        run: |
          set -euo pipefail
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          trap 'docker compose -f docker-compose.e2e.yml down -v || true' EXIT
          if ! docker compose -f docker-compose.e2e.yml up --exit-code-from e2e --abort-on-container-exit e2e; then
            docker compose -f docker-compose.e2e.yml logs e2e
            exit 1
          fi
      - name: Upload test results
        uses: actions/upload-artifact@v4
        # Run this on failure
        if: failure()
        with:
          # Name of the artifact to upload.
          # Optional. Default is 'artifact'
          name: test-results-${{ github.job }}-${{ github.run_attempt }}

          # A file, directory or wildcard pattern that describes what to upload
          # Required.
          path: |
            ./E2E


          # Duration after which artifact will expire in days. 0 means using default retention.
          # Minimum 1 day.
          # Maximum 90 days unless changed from the repository settings page.
          # Optional. Defaults to repository settings.
          retention-days: 7


  test-e2e-release-self-hosted:
    runs-on: ubuntu-latest
    # After all the jobs runs
    needs: [ai-agent-docker-image-merge, app-docker-image-merge, home-docker-image-merge, probe-docker-image-merge, test-docker-image-merge, test-server-docker-image-merge, publish-npm-packages, e2e-docker-image-merge, helm-chart-deploy, generate-build-number, read-version, nginx-docker-image-merge]
    env:
      CI_PIPELINE_ID: ${{github.run_number}}
    steps:
      # Aggressively free disk space before anything else
      - name: Aggressive Disk Cleanup
        run: |
          echo "=== Disk space BEFORE cleanup ==="
          df -h /
          # Remove pre-installed software not needed for this job
          sudo rm -rf /usr/share/dotnet || true
          sudo rm -rf /usr/local/lib/android || true
          sudo rm -rf /opt/ghc || true
          sudo rm -rf /opt/hostedtoolcache || true
          sudo rm -rf /usr/local/share/boost || true
          sudo rm -rf /usr/local/graalvm/ || true
          sudo rm -rf /usr/local/share/powershell || true
          sudo rm -rf /usr/local/share/chromium || true
          sudo rm -rf /usr/local/lib/node_modules || true
          sudo rm -rf /usr/share/swift || true
          sudo rm -rf /usr/share/miniconda || true
          sudo rm -rf /usr/lib/google-cloud-sdk || true
          sudo rm -rf /usr/lib/jvm || true
          sudo rm -rf /usr/lib/firefox || true
          sudo rm -rf /usr/lib/heroku || true
          sudo rm -rf /usr/local/julia* || true
          sudo rm -rf /opt/az || true
          sudo rm -rf /opt/microsoft || true
          sudo rm -rf /opt/pipx || true
          sudo rm -rf /opt/actionarchivecache || true
          sudo rm -rf /imagegeneration || true
          sudo rm -rf /usr/share/az_* || true
          sudo rm -rf /usr/share/sbt || true
          sudo rm -rf /usr/share/gradle* || true
          sudo rm -rf /usr/share/kotlinc || true
          sudo rm -rf /usr/share/ri || true
          sudo rm -rf /usr/local/.ghcup || true
          # Clean apt cache
          sudo apt-get clean || true
          sudo rm -rf /var/lib/apt/lists/* || true
          # Clean temp files
          sudo rm -rf /tmp/* || true
          # Docker cleanup
          docker system prune -af --volumes || true
          echo "=== Disk space AFTER aggressive cleanup ==="
          df -h /
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: true
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - name: Final Disk Space Check
        run: |
          echo "=== Disk space after all cleanup ==="
          df -h /
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: latest
      - name: Preinstall
        run: |
          set -euo pipefail
          npm run prerun
      - name: Pin APP_TAG to versioned release
        run: |
          VERSION="${{needs.read-version.outputs.major_minor}}"
          SANITIZED_VERSION="${VERSION//+/-}"
          if [ -f config.env ]; then
            if grep -q '^APP_TAG=' config.env; then
              sed -i "s/^APP_TAG=.*/APP_TAG=${SANITIZED_VERSION}/" config.env
            else
              echo "APP_TAG=${SANITIZED_VERSION}" >> config.env
            fi
          else
            echo "APP_TAG=${SANITIZED_VERSION}" > config.env
          fi
      - name: Start Server with version tag
        run: |
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          docker compose up --remove-orphans -d
          npm run status-check
      - name: Wait for server to start
        run: bash ./Tests/Scripts/status-check.sh http://localhost
      - name: Pull E2E test image
        run: |
          set -euo pipefail
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          docker compose -f docker-compose.e2e.yml pull e2e
      - name: Run E2E Tests. Run docker container e2e in docker compose file
        run: |
          set -euo pipefail
          export $(grep -v '^#' config.env | xargs)
          export APP_TAG=${{needs.read-version.outputs.major_minor}}
          trap 'docker compose -f docker-compose.e2e.yml down -v || true' EXIT
          if ! docker compose -f docker-compose.e2e.yml up --exit-code-from e2e --abort-on-container-exit e2e; then
            docker compose -f docker-compose.e2e.yml logs e2e
            exit 1
          fi
      - name: Upload test results
        uses: actions/upload-artifact@v4
        # Run this on failure
        if: failure()
        with:
          # Name of the artifact to upload.
          # Optional. Default is 'artifact'
          name: test-results-${{ github.job }}-${{ github.run_attempt }}

          # A file, directory or wildcard pattern that describes what to upload
          # Required.
          path: |
            ./E2E


          # Duration after which artifact will expire in days. 0 means using default retention.
          # Minimum 1 day.
          # Maximum 90 days unless changed from the repository settings page.
          # Optional. Defaults to repository settings.
          retention-days: 7

  draft-github-release:
    name: Create draft GitHub release
    needs: [test-e2e-release-saas, test-e2e-release-self-hosted, generate-build-number, read-version, push-release-tags]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/release'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}
      - run: echo "${{needs.generate-build-number.outputs.build_number}}"
      - name: "Build Changelog"
        id: build_changelog
        uses: mikepenz/release-changelog-builder-action@v4.2.0
        with:
          configuration: "./Scripts/Release/ChangelogConfig.json"
      - run: echo "Changelog:"
      - run: echo "${{steps.build_changelog.outputs.changelog}}"
      - name: Fallback to commit messages if changelog empty
        id: fallback_changelog
        shell: bash
        run: |
          set -euo pipefail
          CHANGELOG_CONTENT="${{steps.build_changelog.outputs.changelog}}"
          OLD_PLACEHOLDER="No significant changes were made. We have just fixed minor bugs for this release. You can find the detailed information in the commit history."
          NEW_PLACEHOLDER="(auto) No categorized pull requests. Fallback will list raw commit messages."
          if echo "$CHANGELOG_CONTENT" | grep -Fq "$OLD_PLACEHOLDER" || echo "$CHANGELOG_CONTENT" | grep -Fq "$NEW_PLACEHOLDER"; then
            echo "Detected empty placeholder changelog. Building commit list fallback."
            # Find previous tag (skip the most recent tag which might be for an older release). If none, include all commits.
            if prev_tag=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null); then
              echo "Previous tag: $prev_tag"
              commits=$(git log --pretty=format:'- %s (%h)' "$prev_tag"..HEAD)
            else
              echo "No previous tag found; using full commit history on this branch."
              commits=$(git log --pretty=format:'- %s (%h)')
            fi
            # If still empty (e.g., no commits), keep placeholder to avoid empty body.
            if [ -z "$commits" ]; then
              commits="(no commits found)"
            fi
            {
              echo "changelog<<EOF"
              echo "## Commit Messages"
              echo ""
              echo "$commits"
              echo "EOF"
            } >> "$GITHUB_OUTPUT"
          else
            # Pass through original changelog
            {
              echo "changelog<<EOF"
              echo "$CHANGELOG_CONTENT"
              echo "EOF"
            } >> "$GITHUB_OUTPUT"
          fi
      - uses: ncipollo/release-action@v1
        with:
          tag: "${{needs.read-version.outputs.major_minor}}"
          artifactErrorsFailBuild: true
          draft: true
          allowUpdates: true
          prerelease: false
          body: |
            ${{steps.fallback_changelog.outputs.changelog}}

  infrastructure-agent-deploy:
    needs: [draft-github-release, generate-build-number, read-version]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@main
        with:
          tool-cache: false
          android: true
          dotnet: true
          haskell: true
          large-packages: true
          docker-images: true
          swap-storage: true
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: Set up Go
        uses: actions/setup-go@v4

      - name: Install GoReleaser
        uses: goreleaser/goreleaser-action@v6.1.0
        with:
          install-only: true

      - name: GoReleaser Version
        run: goreleaser -v

      # This tool is used to generate .rpm and .deb packages
      - name: Install NFPM
        run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest

      - name: Show GoReleaser version
        run: goreleaser -v

      - name: Run GoReleaser
        run: cd InfrastructureAgent && export GORELEASER_CURRENT_TAG=${{needs.read-version.outputs.major_minor}} && goreleaser release --clean --snapshot

      - name: Release MSI Images
        run: cd InfrastructureAgent && bash build-msi.sh ${{needs.read-version.outputs.major_minor}}

      # Upload binaries to github release
      - name: Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            InfrastructureAgent/dist/*
          token: ${{ secrets.GITHUB_TOKEN }}
          draft: true
          prerelease: false
          tag_name: ${{needs.read-version.outputs.major_minor}}


  # Build Android release APK and attach to GitHub Release.
  # Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md
  mobile-app-android-deploy:
    needs: [draft-github-release, generate-build-number, read-version]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: Setup Java 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - uses: actions/setup-node@v4
        with:
          node-version: latest

      - name: Install dependencies
        run: cd MobileApp && npm install

      - name: Generate native Android project
        run: cd MobileApp && npx expo prebuild --platform android --no-install

      - name: Decode Android keystore
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/release.keystore

      - name: Build release APK
        env:
          ANDROID_KEYSTORE_FILE: /tmp/release.keystore
          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        run: |
          cd MobileApp/android
          ./gradlew assembleRelease \
            -PversionName=${{ needs.read-version.outputs.major_minor }} \
            -PversionCode=${{ needs.generate-build-number.outputs.build_number }}

      - name: Rename APK
        run: mv MobileApp/android/app/build/outputs/apk/release/*.apk MobileApp/android/app/build/outputs/apk/release/oneuptime-on-call-android-app.apk

      - name: Upload APK to GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: MobileApp/android/app/build/outputs/apk/release/oneuptime-on-call-android-app.apk
          token: ${{ secrets.GITHUB_TOKEN }}
          draft: true
          prerelease: false
          tag_name: ${{ needs.read-version.outputs.major_minor }}

  # Build iOS release IPA and attach to GitHub Release.
  # Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md
  mobile-app-ios-deploy:
    needs: [draft-github-release, generate-build-number, read-version]
    runs-on: macos-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

      - name: Install dependencies
        run: cd MobileApp && npm install

      - name: Generate native iOS project
        run: cd MobileApp && npx expo prebuild --platform ios --no-install

      - name: Install CocoaPods dependencies
        run: cd MobileApp/ios && pod install

      - name: Import signing certificate
        env:
          IOS_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }}
          IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }}
        run: |
          CERTIFICATE_PATH=$RUNNER_TEMP/distribution.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

          echo "$IOS_DISTRIBUTION_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"

          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

          security import "$CERTIFICATE_PATH" -P "$IOS_DISTRIBUTION_CERTIFICATE_PASSWORD" \
            -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security list-keychain -d user -s "$KEYCHAIN_PATH"

      - name: Install provisioning profile
        env:
          IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
        run: |
          PROFILE_PATH=$RUNNER_TEMP/profile.mobileprovision
          echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"

          # Extract the UUID from the profile and install with UUID as filename
          # This is required for Xcode to find the profile during manual code signing
          PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i "$PROFILE_PATH"))
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/"$PROFILE_UUID".mobileprovision

          echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV

      - name: Build archive
        env:
          IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
        run: |
          if [ -z "$IOS_TEAM_ID" ]; then
            echo "::error::IOS_TEAM_ID secret is not set"
            exit 1
          fi
          cd MobileApp
          xcodebuild -workspace ios/OneUptimeOnCall.xcworkspace \
            -scheme OneUptimeOnCall \
            -configuration Release \
            -sdk iphoneos \
            -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \
            archive \
            CODE_SIGN_STYLE=Manual \
            CODE_SIGN_IDENTITY="iPhone Distribution" \
            DEVELOPMENT_TEAM="$IOS_TEAM_ID" \
            PROVISIONING_PROFILE="${{ env.PROFILE_UUID }}" \
            MARKETING_VERSION=${{ needs.read-version.outputs.major_minor }} \
            CURRENT_PROJECT_VERSION=${{ needs.generate-build-number.outputs.build_number }}

      - name: Prepare ExportOptions.plist with team ID
        env:
          IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
        run: |
          /usr/libexec/PlistBuddy -c "Add :teamID string $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist || \
          /usr/libexec/PlistBuddy -c "Set :teamID $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist

      - name: Export IPA
        run: |
          cd MobileApp
          xcodebuild -exportArchive \
            -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \
            -exportOptionsPlist ios/ExportOptions.plist \
            -exportPath $RUNNER_TEMP/build

      - name: Rename IPA
        run: mv ${{ runner.temp }}/build/*.ipa ${{ runner.temp }}/build/oneuptime-on-call-ios-app.ipa

      - name: Upload IPA to GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: ${{ runner.temp }}/build/oneuptime-on-call-ios-app.ipa
          token: ${{ secrets.GITHUB_TOKEN }}
          draft: true
          prerelease: false
          tag_name: ${{ needs.read-version.outputs.major_minor }}

      - name: Cleanup keychain
        if: always()
        run: |
          security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true

  finalize-github-release:
    name: Publish GitHub release
    needs: [infrastructure-agent-deploy, generate-build-number, read-version]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/release'
    permissions:
      contents: write
    steps:
      - name: Publish release
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const tag = '${{needs.read-version.outputs.major_minor}}';
            try {
              const releases = await github.paginate(github.rest.repos.listReleases, {
                owner: context.repo.owner,
                repo: context.repo.repo,
                per_page: 100,
              });

              const release = releases.find((item) => item.tag_name === tag);

              if (!release) {
                throw new Error(`Release with tag ${tag} not found in repository ${context.repo.owner}/${context.repo.repo}`);
              }

              await github.rest.repos.updateRelease({
                owner: context.repo.owner,
                repo: context.repo.repo,
                release_id: release.id,
                draft: false,
                prerelease: false,
                make_latest: 'true',
              });

              console.log(`Published release for ${tag}`);
            } catch (error) {
              throw new Error(`Failed to publish release for tag ${tag}: ${error.message ?? error}`);
            }

  # Publish Android app to Google Play Store.
  # This job only runs when manually triggered via workflow_dispatch with publish_android_to_store=true.
  # Required secrets:
  #   - GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: Service account JSON key with Play Store publishing access
  #   - ANDROID_KEYSTORE_BASE64: Base64-encoded release keystore
  #   - ANDROID_KEYSTORE_PASSWORD: Keystore password
  #   - ANDROID_KEY_ALIAS: Signing key alias
  #   - ANDROID_KEY_PASSWORD: Signing key password
  publish-android-to-play-store:
    needs: [generate-build-number, read-version]
    if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_android_to_store == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: Setup Java 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - uses: actions/setup-node@v4
        with:
          node-version: latest

      - name: Install dependencies
        run: cd MobileApp && npm install

      - name: Generate native Android project
        run: cd MobileApp && npx expo prebuild --platform android --no-install

      - name: Decode Android keystore
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/release.keystore

      - name: Build release AAB for Play Store
        env:
          ANDROID_KEYSTORE_FILE: /tmp/release.keystore
          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        run: |
          cd MobileApp/android
          ./gradlew bundleRelease \
            -PversionName=${{ needs.read-version.outputs.major_minor }} \
            -PversionCode=${{ needs.generate-build-number.outputs.build_number }}

      - name: Upload AAB as build artifact
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: android-aab-${{ needs.read-version.outputs.major_minor }}
          path: MobileApp/android/app/build/outputs/bundle/release/*.aab
          retention-days: 90

      - name: Upload AAB to Google Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
          packageName: com.oneuptime.oncall
          releaseFiles: MobileApp/android/app/build/outputs/bundle/release/*.aab
          track: production
          status: completed


  # Publish iOS app to App Store.
  # This job only runs when manually triggered via workflow_dispatch with publish_ios_to_store=true.
  # Required secrets:
  #   - IOS_DISTRIBUTION_CERTIFICATE_BASE64: Base64-encoded P12 distribution certificate
  #   - IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: Password for the P12 certificate
  #   - IOS_PROVISIONING_PROFILE_BASE64: Base64-encoded App Store distribution provisioning profile
  #   - APP_STORE_CONNECT_API_KEY_ID: App Store Connect API key ID
  #   - APP_STORE_CONNECT_API_ISSUER_ID: App Store Connect API issuer ID
  #   - APP_STORE_CONNECT_API_KEY_BASE64: Base64-encoded App Store Connect API private key (.p8)
  publish-ios-to-app-store:
    needs: [generate-build-number, read-version]
    if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_ios_to_store == 'true'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - uses: actions/setup-node@v4
        with:
          node-version: latest

      - name: Install dependencies
        run: cd MobileApp && npm install

      - name: Generate native iOS project
        run: cd MobileApp && npx expo prebuild --platform ios --no-install

      - name: Install CocoaPods dependencies
        run: cd MobileApp/ios && pod install

      - name: Setup Apple signing certificate and provisioning profile
        env:
          IOS_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }}
          IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }}
          IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
        run: |
          # Create a temporary keychain
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          # Import distribution certificate
          CERTIFICATE_PATH=$RUNNER_TEMP/distribution_certificate.p12
          echo "$IOS_DISTRIBUTION_CERTIFICATE_BASE64" | base64 --decode > $CERTIFICATE_PATH
          security import $CERTIFICATE_PATH -P "$IOS_DISTRIBUTION_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH

          # Install provisioning profile
          PROFILE_PATH=$RUNNER_TEMP/distribution_profile.mobileprovision
          echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > $PROFILE_PATH

          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i $PROFILE_PATH))
          cp $PROFILE_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision

          echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
          echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV

      - name: Build iOS archive
        run: |
          cd MobileApp/ios
          xcodebuild archive \
            -workspace OneUptimeOnCall.xcworkspace \
            -scheme OneUptimeOnCall \
            -configuration Release \
            -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \
            -destination 'generic/platform=iOS' \
            MARKETING_VERSION=${{ needs.read-version.outputs.major_minor }} \
            CURRENT_PROJECT_VERSION=${{ needs.generate-build-number.outputs.build_number }} \
            CODE_SIGN_STYLE=Manual \
            CODE_SIGN_IDENTITY="Apple Distribution" \
            PROVISIONING_PROFILE_SPECIFIER="${{ env.PROFILE_UUID }}" \
            OTHER_CODE_SIGN_FLAGS="--keychain ${{ env.KEYCHAIN_PATH }}"

      - name: Export IPA
        run: |
          # Create export options plist
          cat > $RUNNER_TEMP/ExportOptions.plist << EOF
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
          <plist version="1.0">
          <dict>
            <key>method</key>
            <string>app-store</string>
            <key>destination</key>
            <string>upload</string>
            <key>signingStyle</key>
            <string>manual</string>
            <key>provisioningProfiles</key>
            <dict>
              <key>com.oneuptime.oncall</key>
              <string>${{ env.PROFILE_UUID }}</string>
            </dict>
            <key>uploadSymbols</key>
            <true/>
          </dict>
          </plist>
          EOF

          xcodebuild -exportArchive \
            -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \
            -exportOptionsPlist $RUNNER_TEMP/ExportOptions.plist \
            -exportPath $RUNNER_TEMP/ios-export

      - name: Upload IPA as build artifact
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: ios-ipa-${{ needs.read-version.outputs.major_minor }}
          path: ${{ runner.temp }}/ios-export/*.ipa
          retention-days: 90

      - name: Upload to App Store Connect
        env:
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
        run: |
          # Decode API key
          API_KEY_PATH=$RUNNER_TEMP/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8
          echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > $API_KEY_PATH

          xcrun notarytool store-credentials "appstore-credentials" \
            --key $API_KEY_PATH \
            --key-id $APP_STORE_CONNECT_API_KEY_ID \
            --issuer $APP_STORE_CONNECT_API_ISSUER_ID 2>/dev/null || true

          xcrun altool --upload-app \
            --type ios \
            --file $(find $RUNNER_TEMP/ios-export -name '*.ipa' -print -quit) \
            --apiKey $APP_STORE_CONNECT_API_KEY_ID \
            --apiIssuer $APP_STORE_CONNECT_API_ISSUER_ID

      - name: Cleanup signing artifacts
        if: always()
        run: |
          security delete-keychain ${{ env.KEYCHAIN_PATH }} 2>/dev/null || true
          rm -f $RUNNER_TEMP/distribution_certificate.p12
          rm -f $RUNNER_TEMP/distribution_profile.mobileprovision
          rm -f $RUNNER_TEMP/AuthKey_*.p8
