name: Publish crates.io

on:
  pull_request:
  workflow_dispatch:
  push:
    branches:
      - main
    tags:
      - "v*"

env:
  CARGO_TERM_COLOR: always

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust nightly toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: nightly
          components: rustfmt, clippy

      - name: Check formatting
        run: cargo +nightly fmt -- --check

      - name: Run clippy
        run: cargo +nightly clippy --all-targets --all-features -- -D warnings

      - name: Test crate
        run: cargo +nightly test --all-features --all-targets

      - name: Authenticate to crates.io
        if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Release dyns crate
        shell: bash
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          set -euo pipefail

          if [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" == v* ]]; then
            mode=publish
          else
            mode=dry-run
          fi

          package_name=dyns
          package_version="$(cargo +nightly metadata --no-deps --format-version 1 | python3 -c 'import json, sys; print(json.load(sys.stdin)["packages"][0]["version"])')"

          crate_state="$(
            python3 - <<'PY' "$package_name" "$package_version"
          import sys
          import urllib.error
          import urllib.request

          name, version = sys.argv[1], sys.argv[2]
          headers = {"User-Agent": "genmeta ddns publish workflow"}
          version_url = f"https://crates.io/api/v1/crates/{name}/{version}"
          version_request = urllib.request.Request(version_url, headers=headers)
          try:
              with urllib.request.urlopen(version_request, timeout=20) as response:
                  if response.status == 200:
                      print("published_version")
                  else:
                      raise SystemExit(f"unexpected crates.io status for {name} {version}: {response.status}")
          except urllib.error.HTTPError as error:
              if error.code == 404:
                  crate_url = f"https://crates.io/api/v1/crates/{name}"
                  crate_request = urllib.request.Request(crate_url, headers=headers)
                  try:
                      with urllib.request.urlopen(crate_request, timeout=20) as response:
                          if response.status == 200:
                              print("missing_version")
                          else:
                              raise SystemExit(f"unexpected crates.io crate status for {name}: {response.status}")
                  except urllib.error.HTTPError as crate_error:
                      if crate_error.code == 404:
                          print("missing_crate")
                      else:
                          raise
              else:
                  raise
          PY
          )"

          if [[ "$crate_state" == "published_version" ]]; then
            echo "skip $package_name $package_version (already on crates.io)"
            exit 0
          fi

          if [[ "$crate_state" == "missing_crate" ]]; then
            echo "skip $package_name $package_version (crate not yet initialized on crates.io)"
            exit 0
          fi

          if [[ "$mode" == "dry-run" ]]; then
            echo "dry-run $package_name $package_version"
            cargo +nightly publish --dry-run
            exit 0
          fi

          echo "publish $package_name $package_version"
          cargo +nightly publish
