name: build

on:
  push:
    branches:
      - "master"
      - "develop"
    tags:
      - 'v*'
    paths-ignore:
      - '.github/ISSUE_TEMPLATE/**'
      - '.github/images/**'
      - 'config.toml'
      - 'docker-compose.yml'
      - 'Makefile'
      - '**.md'
  pull_request:
    paths-ignore:
      - '.github/ISSUE_TEMPLATE/**'
      - '.github/images/**'
      - 'config.toml'
      - 'docker-compose.yml'
      - 'Makefile'
      - '**.md'

env:
  REGISTRY: ghcr.io
  REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
  GO_VERSION: '1.23'
  NODE_VERSION: '20.17.0'

permissions:
  contents: write
  packages: write

jobs:
  web:
    name: Build web
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Set up corepack
        run: corepack enable

      # It can not be done before enable corepack
      - name: Set up cache
        uses: actions/setup-node@v4
        with:
          cache: pnpm
          cache-dependency-path: web/pnpm-lock.yaml

      - name: Fetch web dependencies
        working-directory: web
        run: pnpm install --frozen-lockfile

      - name: Build web frontend
        working-directory: web
        run: CI= pnpm run build

      - name: Upload web production build
        uses: actions/upload-artifact@v4
        with:
          name: web-dist
          path: web/dist

  test:
    strategy:
      fail-fast: true
      matrix:
        os: [ ubuntu-latest ]
        cgo: [ 1, 0 ]
    name: Test${{ matrix.cgo == 1 && ' CGO'|| '' }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    services:
      test_postgres:
        image: postgres:12.10
        ports:
          - "5437:5432"
        env:
          POSTGRES_USER: testdb
          POSTGRES_PASSWORD: testdb
          POSTGRES_DB: autobrr
        options: --health-cmd pg_isready --health-interval 1s --health-timeout 5s --health-retries 60
    env:
      CGO_ENABLED: ${{ matrix.cgo }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true

      - name: Create Profile environment
        run:
          |
          printf '#!/usr/bin/env bash\nset -eu\nfor pkg in $(go list "$@"); do\n\tgo test -json -cpuprofile="profile/$(echo $pkg | tr / -)-${{ matrix.cgo }}.pprof" ${{ startsWith(matrix.os, 'ubuntu') && '-tags=integration ' || '' }}"$pkg"\ndone' | tee -a profile.sh;
          chmod +x profile.sh;
          mkdir profile;

      - name: Test
        run: go run gotest.tools/gotestsum@latest --junitfile unit-tests.xml --format pkgname --raw-command ./profile.sh -- ./...

      - name: Upload pprof
        uses: actions/upload-artifact@v4
        with:
          name: pprof-test-${{ matrix.os }}-${{ matrix.cgo }}
          path: profile

      - name: Test Summary
        uses: test-summary/action@v2
        with:
          paths: "unit-tests.xml"
        if: always()

  testother:
    strategy:
      fail-fast: true
      matrix:
        os: [ macos-latest, windows-latest ]
        cgo: [ 1, 0 ]
    name: Test${{ matrix.cgo == 1 && ' CGO'|| '' }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    env:
      GOPATH: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\go' || '' }}
      GOCACHE: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\cache' || '' }}
      GOMODCACHE: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\modcache' || '' }}
      USERPROFILE: ${{ startsWith(matrix.os, 'windows') && 'D:\homedir' || '' }}
      CGO_ENABLED: ${{ matrix.cgo }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true

      - name: Create Profile environment
        shell: bash
        run:
          |
          printf '#!/usr/bin/env bash\nset -eu\nfor pkg in $(go list "$@"); do\n\tgo test -json -cpuprofile="profile/$(echo $pkg | tr / -)-${{ matrix.cgo }}.pprof" ${{ startsWith(matrix.os, 'ubuntu') && '-tags=integration ' || '' }}"$pkg"\ndone' | tee -a profile.sh;
          chmod +x profile.sh;
          mkdir profile;

      - name: Profile
        shell: bash
        run: ${{ startsWith(matrix.os, 'windows') && './profile.sh ./...' || 'go run gotest.tools/gotestsum@latest --junitfile unit-tests.xml --format pkgname --raw-command ./profile.sh -- ./...' }}

      - name: Upload pprof
        uses: actions/upload-artifact@v4
        with:
          name: pprof-test-${{ matrix.os }}-${{ matrix.cgo }}
          path: profile

      - name: Test Summary
        uses: test-summary/action@v2
        with:
          paths: "unit-tests.xml"
        if: always() && startsWith(matrix.os, 'windows') == false

  pgo:
    strategy:
      fail-fast: true
      matrix:
        os: [ ubuntu-latest, macos-latest, windows-latest ]
        cgo: [ 1, 0 ]
    name: Automatic PGO ${{ matrix.cgo == 1 && 'CGO ' || ''}}run ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    needs: [ web ]
    env:
      GOPATH: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\go' || '' }}
      GOCACHE: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\cache' || '' }}
      GOMODCACHE: ${{ startsWith(matrix.os, 'windows') && 'D:\golang\modcache' || '' }}
      USERPROFILE: ${{ startsWith(matrix.os, 'windows') && 'D:\homedir' || '' }}
      CGO_ENABLED: ${{ matrix.cgo }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download web production build
        uses: actions/download-artifact@v4
        with:
          name: web-dist
          path: web/dist

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true

      - name: Generate Profile
        run: go run cmd/autobrr/main.go --pgo cpu-${{ matrix.os }}-${{ matrix.cgo }}.pprof

      - name: Upload pprof
        uses: actions/upload-artifact@v4
        with:
          name: pprof-pgo-${{ matrix.os }}-${{ matrix.cgo }}
          path: cpu-${{ matrix.os }}-${{ matrix.cgo }}.pprof

  goprofilecombine:
    name: Combine pprof profiles
    runs-on: ubuntu-latest
    needs: [ pgo, test, testother ]
    steps:
      - name: Download pprof profiles
        uses: actions/download-artifact@v4
        with:
          pattern: pprof-*
          merge-multiple: true
          path: profile

      - name: List contents
        run: ls -la profile

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: false

      - name: Merge Profiles
        run: go tool pprof -proto profile/*.pprof | tee -a cpu.pprof

      - name: Upload pprof
        uses: actions/upload-artifact@v4
        with:
          name: pprof
          path: cpu.pprof

  goreleaserbuild:
    name: Build distribution binaries
    runs-on: ubuntu-latest
    needs: [ web, goprofilecombine ]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download web production build
        uses: actions/download-artifact@v4
        with:
          name: web-dist
          path: web/dist

      - name: Download pprof profile
        uses: actions/download-artifact@v4
        with:
          name: pprof

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true

      - name: Run GoReleaser build
        if: github.event_name == 'pull_request'
        uses: goreleaser/goreleaser-action@v6
        with:
          distribution: goreleaser
          version: "~> v2"
          args: release --clean --skip=validate,publish --parallelism 5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Run GoReleaser build and publish tags
        if: startsWith(github.ref, 'refs/tags/')
        uses: goreleaser/goreleaser-action@v6
        with:
          distribution: goreleaser
          version: "~> v2"
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create web-dist artifact
        if: startsWith(github.ref, 'refs/tags/')
        run: mkdir -p dist && tar czf dist/web-dist.tar.gz web/dist

      - name: Upload web-dist to release assets
        uses: softprops/action-gh-release@v2
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: |
            dist/web-dist.tar.gz

      - name: Upload assets
        uses: actions/upload-artifact@v4
        with:
          name: autobrr
          path: |
            dist/*.tar.gz
            dist/*.zip
            dist/*.apk
            dist/*.deb
            dist/*.rpm

  docker:
    name: Build Docker images
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        platform:
          - linux/386
          - linux/amd64
          - linux/amd64/v2
          - linux/amd64/v3
          - linux/arm/v6
          - linux/arm/v7
          - linux/arm64
          # - linux/mips64le
          # - linux/mips64
          - linux/ppc64le
          # - linux/riscv64
          - linux/s390x
          - windows/amd64
    needs: [ web, goprofilecombine ]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download web production build
        uses: actions/download-artifact@v4
        with:
          name: web-dist
          path: web/dist

      - name: Download pprof profile
        uses: actions/download-artifact@v4
        with:
          name: pprof

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

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY_IMAGE }}
          tags: |
            type=semver,pattern={{version}},prefix=v
            type=semver,pattern={{major}}.{{minor}},prefix=v
            type=ref,event=branch
            type=ref,event=pr
          flavor: |
            latest=auto

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

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

      - name: Supported Architectures
        run: docker buildx ls

      - name: Build and publish image
        id: docker_build
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ${{ startsWith(matrix.platform, 'windows') && './ciwindows.Dockerfile' || './ci.Dockerfile' }}
          platforms: ${{ matrix.platform }}
          outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=${{ (github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request') && 'true' || 'false' }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
            VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
            REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: false

      - name: Export image digest
        id: digest-prep
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.docker_build.outputs.digest }}"
          echo "manifest-hash=${digest#sha256:}" >> "$GITHUB_OUTPUT"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload image digest
        uses: actions/upload-artifact@v4
        with:
          name: docker-digests-${{ steps.digest-prep.outputs.manifest-hash }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  docker-merge:
    name: Publish Docker multi-arch manifest
    if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request' }}
    runs-on: ubuntu-latest
    needs: [ docker ]
    steps:
      - name: Download image digests
        uses: actions/download-artifact@v4
        with:
          path: /tmp/digests
          pattern: docker-digests-*
          merge-multiple: true

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

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

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY_IMAGE }}
          tags: |
            type=semver,pattern={{version}},prefix=v
            type=semver,pattern={{major}}.{{minor}},prefix=v
            type=ref,event=branch
            type=ref,event=pr
          flavor: |
            latest=auto

      - name: Create manifest list and push
        working-directory: /tmp/digests
        run: |
          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)

      - name: Inspect image
        run: |
          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
