name: Release

on:
  push:
    tags:
      - 'v*'

# We only ever publish a tag once. Don't cancel a running release —
# if the user re-pushed the same tag, that's a manual action and
# should fail loudly via Forgejo's "release exists" 409 from the API.
concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  release:
    runs-on: go
    steps:
      # Debian-based Docker images ship /etc/apt/apt.conf.d/docker-clean
      # which deletes /var/cache/apt/archives after every apt-get install.
      # That defeats actions/cache for the apt cache; disable it before
      # any apt operations.
      - name: Disable apt auto-clean (preserve cache for actions/cache)
        run: |
          rm -f /etc/apt/apt.conf.d/docker-clean
          echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache

      - name: Install Node (required by actions/cache)
        run: |
          apt-get update >/dev/null
          apt-get install -y --no-install-recommends nodejs >/dev/null

      - name: Checkout
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git clone --no-checkout "https://x-access-token:${GITHUB_TOKEN}@git.okcu.io/${GITHUB_REPOSITORY}.git" /tmp/src
          git -C /tmp/src checkout "$GITHUB_SHA"
          cp -a /tmp/src/. .

      # Cache Go modules — saves ~10-20s on warm runs.
      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
            ~/.cache/go-build
          key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
          restore-keys: go-${{ runner.os }}-

      # Cache apt downloads — saves ~50s on the wine + innoextract install.
      # Bump the cache key (-v2, -v3, ...) when the package list changes.
      - name: Cache apt packages
        id: apt-cache
        uses: actions/cache@v4
        with:
          path: |
            /var/cache/apt/archives
            /var/lib/apt/lists
          key: apt-trixie-wine-innoextract-v2

      - name: Extract version from tag
        id: version
        run: |
          TAG="${GITHUB_REF#refs/tags/}"
          VERSION="${TAG#v}"
          echo "tag=${TAG}"         | tee -a "$GITHUB_OUTPUT"
          echo "version=${VERSION}" | tee -a "$GITHUB_OUTPUT"

      - name: Cross-compile drover.exe (windows/amd64)
        env:
          GOOS: windows
          GOARCH: amd64
          CGO_ENABLED: '0'
        run: |
          set -e
          BUILD_DATE="$(date -u +%Y-%m-%d)"
          SHORT_SHA="${GITHUB_SHA:0:7}"
          mkdir -p dist
          # -H=windowsgui makes drover.exe a GUI subsystem binary so the
          # double-click experience doesn't flash a console window. main.go
          # calls AttachConsole on startup so CLI runs still print to the
          # parent terminal when launched from cmd/PowerShell.
          go build -trimpath -ldflags="-s -w -H=windowsgui \
            -X main.Version=${{ steps.version.outputs.version }} \
            -X main.Commit=${SHORT_SHA} \
            -X main.BuildDate=${BUILD_DATE}" \
            -o "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" \
            ./cmd/drover

      - name: Build portable zip
        run: |
          set -e
          # Stage files for the zip in a clean directory so the archive root
          # contains exactly what users see when they unpack.
          apt-get update >/dev/null
          DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends zip >/dev/null
          STAGE="$(mktemp -d)"
          cp "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" "$STAGE/drover.exe"
          mkdir -p "$STAGE/windivert"
          cp third_party/windivert/WinDivert.dll   "$STAGE/windivert/"
          cp third_party/windivert/WinDivert64.sys "$STAGE/windivert/"
          cp third_party/windivert/LICENSE-LGPL    "$STAGE/windivert/"
          cp third_party/windivert/README.md       "$STAGE/windivert/"
          cp LICENSE   "$STAGE/LICENSE.txt"
          cp README.md "$STAGE/README.md"
          # zip from inside STAGE so paths are relative
          ( cd "$STAGE" && zip -r9 - . ) > "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.zip"

      - name: Install Wine + Inno Setup (via innoextract, no Wine GUI install)
        run: |
          set -e
          dpkg --add-architecture i386
          apt-get update
          # innoextract unpacks Inno Setup .exe installers without running them
          # under Wine. We then copy the extracted compiler files into the Wine
          # prefix and call ISCC.exe via Wine — much more reliable than running
          # the whole interactive installer through Wine + Xvfb (which failed
          # silently in the previous iteration).
          # ISCC.exe is a 32-bit binary even from Inno Setup 6 — without
          # wine32:i386 wine bails with "init_peb starting ... in experimental
          # wow64 mode / failed to load syswow64/ntdll.dll error c0000135".
          DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
            wine wine64 wine32:i386 innoextract xvfb xauth curl ca-certificates \
            >/dev/null
          export WINEPREFIX="$HOME/.wine"
          export WINEDLLOVERRIDES="mscoree,mshtml="
          export DISPLAY=":99"
          Xvfb :99 -screen 0 1024x768x16 &
          sleep 1
          wineboot --init >/dev/null 2>&1 || true
          # Pin Inno Setup version. The innoextract package in Debian
          # trixie (1.9.x) parses headers up through Inno Setup 6.2.x.
          # 6.3+ already fails with "Unexpected setup data version" /
          # "Stream error while parsing setup headers", 6.5+ with
          # "Setup loader checksum mismatch". Stay on 6.2.2 until either
          # innoextract gets bumped in Debian or we replace it with a
          # docker-image-based ISCC step.
          INNO_VERSION="6.2.2"
          INNO_TAG="is-6_2_2"
          curl -fsSL -o /tmp/innosetup.exe \
            "https://github.com/jrsoftware/issrc/releases/download/${INNO_TAG}/innosetup-${INNO_VERSION}.exe"
          # innoextract unpacks the installer payload to ./app — that's the
          # full Inno Setup directory tree (ISCC.exe + support DLLs/scripts).
          mkdir -p /tmp/inno-extract
          ( cd /tmp/inno-extract && innoextract /tmp/innosetup.exe )
          # Place it where Windows-style paths can resolve. Wine maps
          # C:\InnoSetup to ~/.wine/drive_c/InnoSetup so we copy there.
          mkdir -p "$HOME/.wine/drive_c/InnoSetup"
          cp -a /tmp/inno-extract/app/. "$HOME/.wine/drive_c/InnoSetup/"
          # Sanity check
          ls -la "$HOME/.wine/drive_c/InnoSetup/ISCC.exe"
          xvfb-run -a wine "C:\\InnoSetup\\ISCC.exe" /? 2>&1 | head -5 || true

      - name: Compile Inno Setup installer
        env:
          DISPLAY: ":99"
          WINEDLLOVERRIDES: "mscoree,mshtml="
        run: |
          set -e
          export WINEPREFIX="$HOME/.wine"
          # installer.iss references "..\drover.exe" relative to installer/,
          # i.e. expects a plain drover.exe at repo root. Stage a copy of the
          # versioned binary there before invoking ISCC.
          cp "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" drover.exe
          # Inno Setup wants Windows-style /D defines and the script path
          # in DOS form. We invoke from repo root; iss script resolves its
          # own ..\ paths from installer/.
          mkdir -p installer/Output
          xvfb-run -a wine "C:\\InnoSetup\\ISCC.exe" \
            "/DMyAppVersion=${{ steps.version.outputs.version }}" \
            "installer\\installer.iss"
          # Inno Setup default OutputDir is "installer/Output/" (relative
          # to the .iss). Move the produced exe to dist/ with the canonical
          # asset name.
          mv "installer/Output/drover-${{ steps.version.outputs.version }}-setup.exe" \
             "dist/drover-${{ steps.version.outputs.tag }}-setup.exe"
          ls -la dist/

      - name: Compute SHA256SUMS.txt
        run: |
          set -e
          ( cd dist && sha256sum \
            "drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" \
            "drover-${{ steps.version.outputs.tag }}-windows-amd64.zip" \
            "drover-${{ steps.version.outputs.tag }}-setup.exe" \
          ) > dist/SHA256SUMS.txt
          cat dist/SHA256SUMS.txt

      - name: Create Forgejo release and upload assets
        env:
          FJ_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          FJ_HOST: git.okcu.io
          FJ_REPO: ${{ github.repository }}
          TAG: ${{ steps.version.outputs.tag }}
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -e
          # jq is used to build the JSON body safely. Install if missing.
          command -v jq >/dev/null || {
            apt-get update >/dev/null
            DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends jq >/dev/null
          }
          # Detect prerelease from tag (anything with a hyphen after the version
          # number, e.g. v0.1.0-rc.1, v0.1.0-beta) is a prerelease.
          PRERELEASE="false"
          case "$VERSION" in
            *-*) PRERELEASE="true" ;;
          esac

          # 1. Create the release. If a release for this tag already exists,
          #    Forgejo returns 409 and we exit non-zero so the user sees the
          #    real reason in the log instead of getting silent overwrites.
          BODY=$(jq -n \
            --arg tag "$TAG" \
            --arg name "Drover-Go $TAG" \
            --argjson prerelease "$PRERELEASE" \
            --arg body "Automated release built from commit $GITHUB_SHA. See [docs](https://git.okcu.io/$FJ_REPO/src/tag/$TAG/docs/) for installation and troubleshooting." \
            '{tag_name: $tag, name: $name, prerelease: $prerelease, body: $body, draft: false}')
          RELEASE_JSON=$(curl -fsS -X POST \
            -H "Authorization: token $FJ_TOKEN" \
            -H "Content-Type: application/json" \
            -d "$BODY" \
            "https://$FJ_HOST/api/v1/repos/$FJ_REPO/releases")
          RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id')
          echo "Created release id=$RELEASE_ID"

          # 2. Upload each asset via multipart/form-data
          for asset in dist/*.exe dist/*.zip dist/SHA256SUMS.txt; do
            [ -f "$asset" ] || continue
            name="$(basename "$asset")"
            echo "Uploading $name..."
            curl -fsS -X POST \
              -H "Authorization: token $FJ_TOKEN" \
              -F "attachment=@$asset;filename=$name" \
              "https://$FJ_HOST/api/v1/repos/$FJ_REPO/releases/$RELEASE_ID/assets?name=$name" \
              >/dev/null
          done

          echo "Release $TAG published with $(ls dist | wc -l) assets."
