name: Release NuGet Packages

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      package_version:
        description: Optional SemVer package version. Defaults to a timestamped dev prerelease.
        type: string
        required: false
      push_to_baget:
        description: Push to internal BaGet (nuget.sabp.ir)
        type: boolean
        default: true
      push_to_nuget_org:
        description: Also push to nuget.org (manual opt-in only)
        type: boolean
        default: false

env:
  DOTNET_NOLOGO: true
  DOTNET_CLI_TELEMETRY_OPTOUT: true
  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
  HARBOR_REGISTRY: reg.sabp.ir
  SDK_IMAGE: reg.sabp.ir/sufi-chain/dotnet-sdk-build:10.0.202
  GIT_HOST: git.sabp.ir
  REPOSITORY: ${{ gitea.repository }}
  REF_NAME: ${{ gitea.ref_name }}
  RELEASE_TAG: ${{ gitea.event.release.tag_name }}
  INPUT_PACKAGE_VERSION: ${{ inputs.package_version }}
  INPUT_PUSH_TO_BAGET: ${{ inputs.push_to_baget }}
  INPUT_PUSH_TO_NUGET_ORG: ${{ inputs.push_to_nuget_org }}
  BAGET_SOURCE_URL: https://nuget.sabp.ir/v3/index.json
  NUGET_ORG_SOURCE_URL: https://api.nuget.org/v3/index.json
  NUGET_PUSH_PARALLELISM: 4
  PACKAGE_OUTPUT: ./artifacts/nuget
  PACKAGE_PROJECTS: src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj src/SufiChain.SufiBlazor.Demo.Localization/SufiChain.SufiBlazor.Demo.Localization.csproj src/SufiChain.SufiBlazor.Demo/SufiChain.SufiBlazor.Demo.csproj
  ROOT_SLNX: SufiChain.SufiBlazor.slnx
  WORKSPACE_DIR: ${{ github.workspace }}

concurrency:
  group: release-nuget-${{ gitea.ref_name }}
  cancel-in-progress: false

jobs:
  pack-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Check runner prerequisites
        shell: sh
        run: |
          set -eu
          if ! command -v docker >/dev/null 2>&1; then
            echo "docker CLI is required on the runner." >&2
            echo "Map ubuntu-latest to docker:27-cli and mount /var/run/docker.sock into act_runner." >&2
            exit 1
          fi
          if ! docker version >/dev/null 2>&1; then
            echo "docker CLI exists, but cannot reach Docker daemon." >&2
            echo "Check Docker service and /var/run/docker.sock mount/permissions for the Gitea runner." >&2
            exit 1
          fi

      - name: Login to Harbor
        shell: sh
        env:
          HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
          HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
        run: |
          set -eu
          if [ -z "${HARBOR_USERNAME:-}" ] || [ -z "${HARBOR_PASSWORD:-}" ]; then
            echo "HARBOR_USERNAME and HARBOR_PASSWORD secrets are required." >&2
            exit 1
          fi
          echo "$HARBOR_PASSWORD" | docker login "$HARBOR_REGISTRY" \
            -u "$HARBOR_USERNAME" \
            --password-stdin

      - name: Checkout
        shell: sh
        env:
          GITEATOKEN: ${{ secrets.GITEATOKEN }}
        run: |
          set -eu
          workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}"
          cd "$workspace"
          echo "Using workspace: $workspace"

          if [ -f "$ROOT_SLNX" ]; then
            echo "Repository already checked out by the runner ($ROOT_SLNX)."
            exit 0
          fi

          echo "Runner workspace is empty; cloning with SDK container."
          CLONE_URL="https://${GIT_HOST}/${REPOSITORY}.git"
          if [ -n "${GITEATOKEN:-}" ]; then
            CLONE_URL="$(printf '%s' "$CLONE_URL" | sed "s#^https://#https://x-access-token:${GITEATOKEN}@#")"
          fi
          export CLONE_URL
          container_id="$(docker run -d \
            -w /src \
            -e REF_NAME \
            -e CLONE_URL \
            "$SDK_IMAGE" \
            sleep 3600)"
          trap 'docker rm -f "$container_id" >/dev/null 2>&1 || true' EXIT INT TERM
          docker exec "$container_id" sh -c '
            set -eu
            if [ -n "${REF_NAME:-}" ]; then
              git clone --depth 1 --branch "$REF_NAME" "$CLONE_URL" /src
            else
              git clone --depth 1 "$CLONE_URL" /src
            fi
            test -f "'"$ROOT_SLNX"'"
          '
          docker cp "${container_id}:/src/." "${workspace}/"
          docker rm -f "$container_id"
          trap - EXIT INT TERM

          if [ ! -f "$ROOT_SLNX" ]; then
            echo "Checkout did not produce $ROOT_SLNX under $workspace" >&2
            find "$workspace" -maxdepth 2 -type f -name '*.slnx' 2>/dev/null | head -20 >&2 || true
            exit 1
          fi

      - name: Resolve package version
        id: version
        shell: sh
        run: |
          set -eu
          if [ -n "${RELEASE_TAG:-}" ]; then
            version="$RELEASE_TAG"
          elif [ -n "${INPUT_PACKAGE_VERSION:-}" ]; then
            version="$INPUT_PACKAGE_VERSION"
          else
            version="0.0.0-dev.$(date +%Y%m%d%H%M%S)"
          fi
          version="${version#v}"
          if ! printf '%s' "$version" | grep -Eq '^[0-9]+([.][0-9]+){2}(-[0-9A-Za-z][0-9A-Za-z.-]*)?([+][0-9A-Za-z][0-9A-Za-z.-]*)?$'; then
            echo "Invalid NuGet package version: $version" >&2
            echo "Use SemVer like 1.2.3, 1.2.3-beta.1, or v1.2.3." >&2
            exit 1
          fi
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "Resolved version: $version"

      - name: Restore, build and pack packages
        shell: sh
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -eu
          if [ -z "${VERSION:-}" ]; then
            echo "Package version was not resolved (steps.version.outputs.version is empty)." >&2
            exit 1
          fi
          workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}"
          cd "$workspace"
          echo "Using workspace: $workspace"
          version_package_output="${PACKAGE_OUTPUT%/}/${VERSION}"
          package_dir="${workspace}/${version_package_output#./}"
          rm -rf "$package_dir"
          mkdir -p "$package_dir"
          container_id="$(docker run -d \
            -v "${workspace}:/src" \
            -w /src \
            -e VERSION \
            -e PACKAGE_OUTPUT="$version_package_output" \
            -e ROOT_SLNX \
            -e PACKAGE_PROJECTS \
            -e DOTNET_NOLOGO \
            -e DOTNET_CLI_TELEMETRY_OPTOUT \
            -e DOTNET_SKIP_FIRST_TIME_EXPERIENCE \
            "$SDK_IMAGE" \
            sleep 3600)"
          trap 'docker rm -f "$container_id" >/dev/null 2>&1 || true' EXIT INT TERM
          docker exec "$container_id" sh -c '
            set -eu
            package_output="/src/${PACKAGE_OUTPUT#./}"
            rm -rf "$package_output"
            mkdir -p "$package_output"
            echo "Package output: $package_output"
            for project in $PACKAGE_PROJECTS; do
              if [ ! -f "$project" ]; then
                echo "Package project does not exist: $project" >&2
                echo "Available projects:" >&2
                find /src -path "*/bin" -prune -o -path "*/obj" -prune -o -name "*.csproj" -print | sort >&2
                exit 1
              fi
              echo "Packing $project"
              dotnet restore "$project" \
                --verbosity minimal \
                -p:NuGetAudit=false
              dotnet build "$project" \
                --configuration Release \
                --no-restore \
                --verbosity minimal \
                -m \
                -p:PackageVersion="$VERSION" \
                -p:ContinuousIntegrationBuild=true \
                -p:BuildInParallel=true \
                -p:PackageOutputPath="$package_output" \
                -p:GeneratePackageOnBuild=true
            done
            stale_count="$(find "$package_output" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" ! -name "*.${VERSION}.nupkg" | wc -l | tr -d " ")"
            if [ "$stale_count" != "0" ]; then
              echo "Pack produced package(s) that do not match resolved version $VERSION:" >&2
              find "$package_output" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" ! -name "*.${VERSION}.nupkg" | sort >&2
              exit 1
            fi
          '
          rm -rf "$package_dir"
          mkdir -p "$package_dir"
          docker cp "${container_id}:/src/${version_package_output#./}/." "$package_dir/"
          docker rm -f "$container_id"
          trap - EXIT INT TERM
          stale_packages="$(find "$package_dir" -type f -name '*.nupkg' ! -name '*.symbols.nupkg' ! -name "*.${VERSION}.nupkg" | sort || true)"
          if [ -n "$stale_packages" ]; then
            echo "Removing package(s) that do not match resolved version $VERSION:" >&2
            printf '%s\n' "$stale_packages" >&2
            find "$package_dir" -type f -name '*.nupkg' ! -name '*.symbols.nupkg' ! -name "*.${VERSION}.nupkg" -exec rm -f {} +
          fi
          package_count="$(find "$package_dir" -type f -name "*.${VERSION}.nupkg" ! -name '*.symbols.nupkg' | wc -l | tr -d ' ')"
          if [ "$package_count" = "0" ]; then
            echo "No NuGet packages for resolved version $VERSION were produced in $package_dir." >&2
            exit 1
          fi
          echo "Packed $package_count package(s) for version $VERSION into $package_dir."

      - name: Push packages to BaGet
        if: ${{ gitea.event_name == 'release' || inputs.push_to_baget == true }}
        shell: sh
        env:
          NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -eu
          if [ -z "${NUGET_API_KEY:-}" ]; then
            echo "NUGET_API_KEY secret is required." >&2
            exit 1
          fi
          workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}"
          cd "$workspace"
          docker run --rm \
            -v "${workspace}:/src" \
            -w /src \
            -e NUGET_API_KEY \
            -e VERSION \
            -e BAGET_SOURCE_URL \
            -e NUGET_PUSH_PARALLELISM \
            -e PACKAGE_OUTPUT="${PACKAGE_OUTPUT%/}/${VERSION}" \
            "$SDK_IMAGE" \
            sh -c '
              set -eu
              package_dir="/src/${PACKAGE_OUTPUT#./}"
              find "$package_dir" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" ! -name "*.${VERSION}.nupkg" -exec rm -f {} +
              package_count="$(find "$package_dir" -type f -name "*.${VERSION}.nupkg" ! -name "*.symbols.nupkg" | wc -l | tr -d " ")"
              if [ "$package_count" = "0" ]; then
                echo "No NuGet packages found for resolved version $VERSION in $package_dir." >&2
                exit 1
              fi
              echo "Pushing $package_count package(s) for version $VERSION to BaGet."
              find "$package_dir" -type f -name "*.${VERSION}.nupkg" ! -name "*.symbols.nupkg" -print0 | \
                xargs -0 -n 1 -P "$NUGET_PUSH_PARALLELISM" sh -c '\''
                  dotnet nuget push "$1" \
                    --source "$BAGET_SOURCE_URL" \
                    --api-key "$NUGET_API_KEY" \
                    --skip-duplicate
                '\'' sh
            '

      - name: Push packages to nuget.org
        if: ${{ gitea.event_name == 'workflow_dispatch' && inputs.push_to_nuget_org == true && inputs.package_version != '' }}
        shell: sh
        env:
          NUGET_ORG_API_KEY: ${{ secrets.NUGET_ORG_API_KEY }}
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -eu
          if [ -z "${NUGET_ORG_API_KEY:-}" ]; then
            echo "NUGET_ORG_API_KEY secret is required for nuget.org push." >&2
            exit 1
          fi
          workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}"
          cd "$workspace"
          docker run --rm \
            -v "${workspace}:/src" \
            -w /src \
            -e NUGET_ORG_API_KEY \
            -e VERSION \
            -e NUGET_ORG_SOURCE_URL \
            -e NUGET_PUSH_PARALLELISM \
            -e PACKAGE_OUTPUT="${PACKAGE_OUTPUT%/}/${VERSION}" \
            "$SDK_IMAGE" \
            sh -c '
              set -eu
              package_dir="/src/${PACKAGE_OUTPUT#./}"
              find "$package_dir" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" ! -name "*.${VERSION}.nupkg" -exec rm -f {} +
              package_count="$(find "$package_dir" -type f -name "*.${VERSION}.nupkg" ! -name "*.symbols.nupkg" | wc -l | tr -d " ")"
              if [ "$package_count" = "0" ]; then
                echo "No NuGet packages found for resolved version $VERSION in $package_dir." >&2
                exit 1
              fi
              echo "Pushing $package_count package(s) for version $VERSION to nuget.org."
              find "$package_dir" -type f -name "*.${VERSION}.nupkg" ! -name "*.symbols.nupkg" -print0 | \
                xargs -0 -n 1 -P "$NUGET_PUSH_PARALLELISM" sh -c '\''
                  dotnet nuget push "$1" \
                    --source "$NUGET_ORG_SOURCE_URL" \
                    --api-key "$NUGET_ORG_API_KEY" \
                    --skip-duplicate
                '\'' sh
            '
