name: Release

# This workflow builds and packages Rustnet for multiple platforms.
#
# For macOS code signing and notarization, configure these GitHub repository secrets:
# - APPLE_DEVELOPER_CERTIFICATE_P12: Base64-encoded .p12 certificate file
#   (Export from Keychain: Developer ID Application certificate → Export as .p12 → base64 encode)
# - APPLE_DEVELOPER_CERTIFICATE_PASSWORD: Password for the .p12 file
# - APPLE_ID_ISSUER_ID: App Store Connect API issuer ID (from App Store Connect → Users and Access → Keys)
# - APPLE_ID_KEY_ID: App Store Connect API key ID
# - APPLE_ID_KEY: App Store Connect API key content (.p8 file contents)
#
# Without these secrets, the DMG will be unsigned and users must use "Open Anyway" in
# System Settings → Privacy & Security to bypass Gatekeeper protection.
#
# To obtain Apple Developer credentials:
# 1. Join the Apple Developer Program ($99/year): https://developer.apple.com/programs/
# 2. Create a Developer ID Application certificate in your Apple Developer account
# 3. Create an App Store Connect API key with "Developer" role
# 4. Export certificate as .p12, encode with: base64 -i certificate.p12 | pbcopy

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"
  workflow_dispatch:

permissions:
  contents: write
  packages: write
  attestations: write
  id-token: write

env:
  RUST_BACKTRACE: 1
  RUSTNET_ASSET_DIR: assets

jobs:
  # Build all platforms using shared workflow
  build:
    uses: ./.github/workflows/build-platforms.yml
    with:
      create-archives: true
      strip-symbols: true
      version: ${{ github.ref_name }}

  create-release:
    name: create-release
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Download all artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts

      - name: Extract changelog for release
        run: |
          # Extract the version-specific section from CHANGELOG.md
          VERSION="${{ github.ref_name }}"

          # Remove 'v' prefix if present for matching changelog headers
          VERSION_NUMBER="${VERSION#v}"

          # Extract content between version headers
          awk -v version="$VERSION_NUMBER" '
            /^## \[/ {
              if (found) exit
              if ($0 ~ "\\[" version "\\]") {
                found=1
                next
              }
            }
            found { print }
          ' CHANGELOG.md > release_notes.md

          # Check if we extracted any content
          if [ ! -s release_notes.md ]; then
            echo "⚠️  No changelog entry found for $VERSION, will use auto-generated notes"
            echo "USE_GENERATED_NOTES=true" >> $GITHUB_ENV
          else
            echo "✅ Extracted changelog content for $VERSION"
            cat release_notes.md
          fi

      - name: Create Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Create the release if it doesn't exist
          if ! gh release view ${{ github.ref_name }} 2>/dev/null; then
            if [ "$USE_GENERATED_NOTES" = "true" ]; then
              # Fallback to auto-generated notes if changelog extraction failed
              gh release create ${{ github.ref_name }} \
                --title "Release ${{ github.ref_name }}" \
                --draft \
                --generate-notes
            else
              # Use extracted changelog content
              gh release create ${{ github.ref_name }} \
                --title "Release ${{ github.ref_name }}" \
                --draft \
                --notes-file release_notes.md
            fi
          fi

          # Upload all build artifacts
          for artifact in artifacts/build-*/rustnet-*; do
            gh release upload ${{ github.ref_name }} "$artifact" --clobber
          done

  # aarch64 static: JS actions don't work in Alpine containers on ARM runners,
  # so the reusable workflow can't upload artifacts. Build and upload here instead,
  # where we have contents: write from the workflow-level permissions.
  upload-arm-static:
    name: upload-arm-static
    runs-on: ubuntu-24.04-arm
    needs: create-release
    container:
      image: rust:alpine
    steps:
      - name: Checkout repository (ARM workaround)
        run: |
          apk add --no-cache git
          git config --global --add safe.directory /__w/rustnet/rustnet
          git clone --depth 1 https://github.com/${{ github.repository }}.git .
          if [[ "${{ github.ref }}" == refs/tags/* ]]; then
            git fetch --depth 1 origin tag "${{ github.ref_name }}"
            git checkout "${{ github.ref_name }}"
          fi

      - name: Install dependencies
        run: |
          apk add --no-cache \
            musl-dev libpcap-dev pkgconfig build-base perl \
            elfutils-dev zlib-dev zlib-static zstd-dev zstd-static \
            clang llvm linux-headers
          rustup component add rustfmt

      - name: Build static binary
        env:
          CFLAGS: "-mno-outline-atomics"
          CXXFLAGS: "-mno-outline-atomics"
          RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
        run: cargo build --release

      - name: Verify static linking
        run: |
          file target/release/rustnet
          file target/release/rustnet | grep -q "static.* linked" || \
            (echo "ERROR: Binary is not statically linked" && exit 1)

      - name: Create and upload release archive
        run: |
          staging="rustnet-${{ github.ref_name }}-aarch64-unknown-linux-musl"
          mkdir -p "$staging/assets"

          cp target/release/rustnet "$staging/"
          cp assets/services "$staging/assets/" 2>/dev/null || true
          cp README.md "$staging/"
          cp LICENSE "$staging/" 2>/dev/null || true

          tar czf "$staging.tar.gz" "$staging"

          apk add --no-cache github-cli
          gh release upload "${{ github.ref_name }}" "$staging.tar.gz" --clobber
        env:
          GH_TOKEN: ${{ github.token }}

  upload-android-static:
    name: upload-${{ matrix.arch }}-android-static
    runs-on: ${{ matrix.runner }}
    needs: create-release
    container:
      image: rust:alpine
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: aarch64
            runner: ubuntu-24.04-arm
            artifact: aarch64-linux-android-musl
          - arch: x86_64
            runner: ubuntu-latest
            artifact: x86_64-linux-android-musl
          - arch: armv7
            runner: ubuntu-latest
            artifact: armv7-linux-android-musl
          - arch: x86
            runner: ubuntu-latest
            artifact: i686-linux-android-musl

    steps:
      # aarch64: JS actions don't work in Alpine containers on ARM runners
      - name: Checkout repository (aarch64 workaround)
        if: matrix.arch == 'aarch64'
        run: |
          apk add --no-cache git
          git config --global --add safe.directory /__w/rustnet/rustnet
          git clone --depth 1 https://github.com/${{ github.repository }}.git .
          if [[ "${{ github.ref }}" == refs/tags/* ]]; then
            git fetch --depth 1 origin tag "${{ github.ref_name }}"
            git checkout "${{ github.ref_name }}"
          fi

      - name: Checkout repository
        if: matrix.arch != 'aarch64'
        uses: actions/checkout@v6

      - name: Install common dependencies
        run: |
          apk add --no-cache \
            musl-dev pkgconfig build-base perl \
            zlib-dev zlib-static \
            clang llvm linux-headers github-cli curl
          rustup component add rustfmt

      # aarch64/x86_64: libpcap-dev provides a native static libpcap.
      # zstd-static is required because Alpine's libpcap-dev links against zstd.
      - name: Install libpcap (native)
        if: matrix.arch == 'aarch64' || matrix.arch == 'x86_64'
        run: apk add --no-cache libpcap-dev elfutils-dev zstd-dev zstd-static

      # armv7: use zig cc as the cross-compiler (no external toolchain download needed).
      # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
      - name: Install cross-compiler and libpcap (armv7)
        if: matrix.arch == 'armv7'
        run: |
          rustup target add armv7-unknown-linux-musleabihf
          apk add zig flex bison bash

          # zig cc wrapper: cc-rs detects it as clang and passes --target=armv7-*
          # which zig rejects (zig uses 'arm' not 'armv7'). Filter those args out.
          printf '%s\n' \
            '#!/bin/bash' \
            'args=()' \
            'for arg in "$@"; do' \
            '  case "$arg" in --target=*) ;; *) args+=("$arg") ;; esac' \
            'done' \
            'exec zig cc -target arm-linux-musleabihf "${args[@]}"' \
            > /usr/local/bin/arm-linux-musleabihf-gcc
          chmod +x /usr/local/bin/arm-linux-musleabihf-gcc

          curl -fsSL --retry 3 --retry-delay 5 \
            -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
          tar xzf libpcap.tar.gz
          cd libpcap-1.10.5
          CC=arm-linux-musleabihf-gcc \
            ./configure --host=arm-linux-musleabihf \
              --disable-shared --enable-static \
              --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
              --prefix=/arm-sysroot
          make -j$(nproc) && make install
          cd ..

      # x86: use zig cc as the cross-compiler (no external toolchain download needed).
      # zig bundles musl libc for all targets, so no -l:libzstd.a needed.
      - name: Install cross-compiler and libpcap (x86/i686)
        if: matrix.arch == 'x86'
        run: |
          rustup target add i686-unknown-linux-musl
          apk add zig flex bison bash

          # zig cc wrapper: cc-rs passes --target=i686-* which zig doesn't accept;
          # filter those args and use -target x86-linux-musl instead.
          printf '%s\n' \
            '#!/bin/bash' \
            'args=()' \
            'for arg in "$@"; do' \
            '  case "$arg" in --target=*) ;; -Wl,-melf_i386) ;; *) args+=("$arg") ;; esac' \
            'done' \
            'exec zig cc -target x86-linux-musl "${args[@]}"' \
            > /usr/local/bin/i686-linux-musl-gcc
          chmod +x /usr/local/bin/i686-linux-musl-gcc

          curl -fsSL --retry 3 --retry-delay 5 \
            -o libpcap.tar.gz https://www.tcpdump.org/release/libpcap-1.10.5.tar.gz
          tar xzf libpcap.tar.gz
          cd libpcap-1.10.5
          CC=i686-linux-musl-gcc \
            ./configure --host=i686-linux-musl \
              --disable-shared --enable-static \
              --disable-usb --disable-dbus --disable-bluetooth --disable-remote \
              --prefix=/x86-sysroot
          make -j$(nproc) && make install
          cd ..

      - name: Build static binary (aarch64)
        if: matrix.arch == 'aarch64'
        env:
          CFLAGS: "-mno-outline-atomics"
          CXXFLAGS: "-mno-outline-atomics"
          RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
        run: cargo build --release --no-default-features

      - name: Build static binary (x86_64)
        if: matrix.arch == 'x86_64'
        env:
          RUSTFLAGS: "-C strip=symbols -C link-arg=-l:libzstd.a"
        run: cargo build --release --no-default-features

      - name: Build static binary (armv7)
        if: matrix.arch == 'armv7'
        env:
          # link-self-contained=no: let zig provide musl CRT (avoid duplicate _start
          # when both Rust's self-contained crt1.o and zig's crt1.o are linked)
          RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
          CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-musleabihf-gcc
          PKG_CONFIG_PATH: /arm-sysroot/lib/pkgconfig
          PKG_CONFIG_SYSROOT_DIR: /arm-sysroot
          PKG_CONFIG_ALLOW_CROSS: "1"
        run: cargo build --release --target armv7-unknown-linux-musleabihf --no-default-features

      - name: Build static binary (x86/i686)
        if: matrix.arch == 'x86'
        env:
          RUSTFLAGS: "-C strip=symbols -C link-self-contained=no"
          CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_LINKER: i686-linux-musl-gcc
          PKG_CONFIG_PATH: /x86-sysroot/lib/pkgconfig
          PKG_CONFIG_SYSROOT_DIR: /x86-sysroot
          PKG_CONFIG_ALLOW_CROSS: "1"
        run: cargo build --release --target i686-unknown-linux-musl --no-default-features

      - name: Verify static linking
        run: |
          BIN="${{ (matrix.arch == 'armv7' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}"
          file "$BIN"
          file "$BIN" | grep -q "static.* linked" || \
            (echo "ERROR: Binary is not statically linked" && exit 1)

      - name: Create release archive
        run: |
          VERSION="${{ github.ref_name }}"
          ARTIFACT="${{ matrix.artifact }}"

          case "${{ matrix.arch }}" in
            armv7) BIN="target/armv7-unknown-linux-musleabihf/release/rustnet" ;;
            x86)   BIN="target/i686-unknown-linux-musl/release/rustnet" ;;
            *)     BIN="target/release/rustnet" ;;
          esac

          staging="rustnet-${VERSION}-${ARTIFACT}"
          mkdir -p "$staging/assets"
          cp "$BIN" "$staging/rustnet"
          cp assets/services "$staging/assets/" 2>/dev/null || true
          cp README.md "$staging/"
          cp LICENSE "$staging/" 2>/dev/null || true
          tar czf "$staging.tar.gz" "$staging"

      - name: Upload to release
        run: |
          VERSION="${{ github.ref_name }}"
          ARCHIVE="rustnet-${VERSION}-${{ matrix.artifact }}.tar.gz"
          gh release upload "$VERSION" "$ARCHIVE" --repo "${{ github.repository }}" --clobber
        env:
          GH_TOKEN: ${{ github.token }}

  trigger-bsd-build:
    name: trigger-bsd-build
    runs-on: ubuntu-latest
    needs: create-release
    steps:
      - name: Trigger FreeBSD build
        run: |
          gh api repos/domcyrus/rustnet-bsd/dispatches \
            -f event_type=freebsd-build \
            -f "client_payload[tag]=${{ github.ref_name }}" \
            -f "client_payload[rustnet_ref]=${{ github.sha }}"
        env:
          GH_TOKEN: ${{ secrets.BSD_DISPATCH_TOKEN }}

  trigger-homebrew-update:
    name: trigger-homebrew-update
    runs-on: ubuntu-latest
    needs: create-release
    steps:
      - name: Trigger Homebrew formula update
        run: |
          gh workflow run update-formula.yml \
            --repo domcyrus/homebrew-rustnet \
            -f version=${{ github.ref_name }}
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_PAT }}

  trigger-chocolatey-update:
    name: trigger-chocolatey-update
    runs-on: ubuntu-latest
    needs: create-release
    steps:
      - name: Trigger Chocolatey package update
        run: |
          gh workflow run update-package.yml \
            --repo domcyrus/rustnet-chocolatey \
            -f version=${{ github.ref_name }}
        env:
          GH_TOKEN: ${{ secrets.CHOCOLATEY_PAT }}

  package-installers:
    name: package-installers
    runs-on: ubuntu-22.04
    needs: create-release
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Download build artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts

      - name: Setup Linux dependencies
        uses: ./.github/actions/setup-linux-deps

      - name: Install Rust and tools
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf
          components: rustfmt

      - name: Install packaging tools
        run: |
          cargo install cargo-deb cargo-generate-rpm

      - name: Package Debian packages
        run: |
          mkdir -p installers
          for arch in amd64 arm64 armhf; do
            case $arch in
              amd64) target="x86_64-unknown-linux-gnu" ;;
              arm64) target="aarch64-unknown-linux-gnu" ;;
              armhf) target="armv7-unknown-linux-gnueabihf" ;;
            esac

            # Extract binary and assets from artifact
            tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
            mkdir -p "target/$target/release"
            cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"

            # Ensure services file exists for packaging
            mkdir -p assets
            if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
              cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
            fi

            # Create deb package
            cargo deb --no-build --no-strip --target $target
            mv "target/$target/debian"/*.deb "installers/Rustnet_LinuxDEB_$arch.deb"

            # Clean up for next iteration
            rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
          done

      - name: Package RPM packages
        run: |
          for arch in x86_64 aarch64; do
            target="$arch-unknown-linux-gnu"

            # Extract binary and assets from artifact
            tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
            mkdir -p "target/$target/release"
            cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"

            # Ensure services file exists for packaging
            mkdir -p assets
            if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
              cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
            fi

            # Fix library linking if needed
            patchelf --replace-needed libpcap.so.0.8 libpcap.so.1 "target/$target/release/rustnet" 2>/dev/null || true

            # Create rpm package
            cargo generate-rpm --target $target
            mv "target/$target/generate-rpm"/*.rpm "installers/Rustnet_LinuxRPM_$arch.rpm"

            # Clean up for next iteration
            rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
          done

      - name: Upload installer packages
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Upload all installer packages
          for installer in installers/*; do
            gh release upload ${{ github.ref_name }} "$installer" --clobber
          done

  package-macos:
    name: package-macos
    runs-on: macos-latest
    needs: create-release
    strategy:
      matrix:
        include:
          - arch: Intel
            target: x86_64-apple-darwin
          - arch: AppleSilicon
            target: aarch64-apple-darwin
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Install packaging tools
        run: |
          cargo install toml-cli
          cargo install apple-codesign
          brew install create-dmg

      - name: Download build artifact
        uses: actions/download-artifact@v8
        with:
          name: build-${{ matrix.target }}
          path: artifacts

      - name: Package for macOS
        run: |
          # Extract binary
          tar -xzf "artifacts/rustnet-${{ github.ref_name }}-${{ matrix.target }}.tar.gz"

          # Get version and update plist
          VERSION=$(toml get Cargo.toml package.version --raw)
          sed -i'.bak' -e "s/0\.0\.0/${VERSION}/g" -e "s/fffffff/${GITHUB_SHA:0:7}/g" resources/packaging/macos/Info.plist

          # Create app bundle
          mkdir -p "Rustnet.app/Contents/"{MacOS,Resources/assets}
          cp resources/packaging/macos/Info.plist "Rustnet.app/Contents/"
          cp resources/packaging/macos/graphics/rustnet.icns "Rustnet.app/Contents/Resources/"
          cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/rustnet" "Rustnet.app/Contents/MacOS/"
          cp resources/packaging/macos/wrapper.sh "Rustnet.app/Contents/MacOS/"
          cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/assets/services" "Rustnet.app/Contents/Resources/assets/"
          chmod +x "Rustnet.app/Contents/MacOS/"{rustnet,wrapper.sh}

      - name: Code sign and notarize app bundle
        env:
          APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12 }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_ID_ISSUER_ID }}
          APPLE_API_KEY_ID: ${{ secrets.APPLE_ID_KEY_ID }}
          APPLE_API_KEY: ${{ secrets.APPLE_ID_KEY }}
        run: |
          # Skip signing if secrets are not configured
          if [ -z "$APPLE_CERTIFICATE_P12" ]; then
            echo "⚠️  Code signing secrets not configured. DMG will be unsigned."
            echo "⚠️  Users will need to use 'Open Anyway' in System Settings > Privacy & Security"
            echo "SKIP_SIGNING=true" >> $GITHUB_ENV
            exit 0
          fi

          # Decode certificate and save to file
          echo "$APPLE_CERTIFICATE_P12" | base64 --decode > certificate.p12

          # Create temporary JSON for API credentials
          cat > api-key.json <<EOF
          {
            "issuer": "$APPLE_API_ISSUER",
            "key_id": "$APPLE_API_KEY_ID",
            "private_key": "$APPLE_API_KEY"
          }
          EOF

          # Sign the app bundle with hardened runtime
          rcodesign sign \
            --p12-file certificate.p12 \
            --p12-password "$APPLE_CERTIFICATE_PASSWORD" \
            --code-signature-flags runtime \
            Rustnet.app

          # Create zip for notarization (required format)
          ditto -c -k --keepParent Rustnet.app Rustnet.zip

          # Submit for notarization and wait for result
          rcodesign notary-submit \
            --api-key-path api-key.json \
            --wait \
            Rustnet.zip

          # Staple the notarization ticket to the app
          rcodesign staple Rustnet.app

          # Cleanup sensitive files
          rm -f certificate.p12 api-key.json Rustnet.zip

      - name: Create DMG
        run: |
          create-dmg \
            --volname "Rustnet Installer" \
            --background "resources/packaging/macos/graphics/dmg_bg.png" \
            --window-pos 200 120 \
            --window-size 900 450 \
            --icon-size 100 \
            --app-drop-link 620 240 \
            --icon "Rustnet.app" 300 240 \
            --hide-extension "Rustnet.app" \
            "Rustnet_macOS_${{ matrix.arch }}.dmg" \
            "Rustnet.app"

      - name: Upload macOS package
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg" --clobber

  package-windows:
    name: package-windows
    runs-on: windows-latest
    needs: create-release
    strategy:
      matrix:
        include:
          - arch: 32-bit
            target: i686-pc-windows-msvc
          - arch: 64-bit
            target: x86_64-pc-windows-msvc
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Install dependencies
        shell: powershell
        run: |
          Write-Host "::group::WiX Toolset"
          Invoke-WebRequest `
            -Uri "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip" `
            -OutFile "$env:TEMP\wix-binaries.zip" -Verbose
          Expand-Archive -LiteralPath "$env:TEMP\wix-binaries.zip" -DestinationPath "$env:TEMP\wix" -Verbose

          # Add WiX to PATH for all subsequent steps
          "$env:TEMP\wix" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

          # Verify WiX installation
          Write-Host "WiX tools installed:"
          Get-ChildItem "$env:TEMP\wix\*.exe" | Select-Object Name
          Write-Host "::endgroup::"

      - name: Install Rust and tools
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install packaging tools
        shell: bash
        run: |
          cargo install cargo-wix
          cargo wix --version

      - name: Download build artifact
        uses: actions/download-artifact@v8
        with:
          name: build-${{ matrix.target }}
          path: artifacts

      - name: Package for Windows
        shell: powershell
        run: |
          # Extract binary and assets
          Write-Host "Extracting artifact..."
          Expand-Archive -LiteralPath "artifacts\rustnet-${{ github.ref_name }}-${{ matrix.target }}.zip" -DestinationPath . -Force

          # Create target directory structure
          New-Item -ItemType Directory -Path "target\${{ matrix.target }}\release" -Force

          # Copy binary
          Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\rustnet.exe" -Destination "target\${{ matrix.target }}\release\" -Force
          Write-Host "Binary copied to target directory"

          # Copy services file if it exists
          New-Item -ItemType Directory -Path "assets" -Force
          if (Test-Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services") {
            Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services" -Destination "assets\" -Force
            Write-Host "Services file copied successfully"
          } else {
            Write-Host "Warning: No services file found"
          }

          # Setup WiX configuration (cargo-wix expects main.wxs in wix/ directory)
          Write-Host "Setting up WiX configuration..."
          New-Item -ItemType Directory -Path "wix" -Force
          Copy-Item -Path "resources\packaging\windows\wix\main.wxs" -Destination "wix\main.wxs" -Force
          Write-Host "WiX configuration copied"

          # Verify candle.exe and light.exe are in PATH
          Write-Host "Checking WiX tools..."
          Get-Command candle.exe -ErrorAction SilentlyContinue | Select-Object Source
          Get-Command light.exe -ErrorAction SilentlyContinue | Select-Object Source

          # Create MSI package
          Write-Host "Creating MSI package..."
          cargo wix --no-build --nocapture --target ${{ matrix.target }}

          # Find and rename the MSI file
          $msiFiles = Get-ChildItem -Path "target\wix" -Filter "*.msi" -ErrorAction SilentlyContinue
          if ($msiFiles) {
            $msiFile = $msiFiles | Select-Object -First 1
            Move-Item -Path $msiFile.FullName -Destination "Rustnet_Windows_${{ matrix.arch }}.msi" -Force
            Write-Host "MSI package created: Rustnet_Windows_${{ matrix.arch }}.msi"
          } else {
            Write-Error "No MSI file found in target\wix\"
            Write-Host "Contents of target directory:"
            Get-ChildItem -Path "target" -Recurse -ErrorAction SilentlyContinue | Select-Object FullName
            exit 1
          }

      - name: Upload Windows package
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi" --clobber

  publish-crates:
    name: Publish to crates.io
    needs: create-release
    uses: ./.github/workflows/publish.yml
    secrets: inherit

  publish-docker:
    name: Publish Docker image
    needs: create-release
    uses: ./.github/workflows/docker.yml
    secrets: inherit

  update-copr:
    name: Update COPR spec
    needs: create-release
    uses: ./.github/workflows/copr-update-spec.yml
    secrets: inherit

  release-ppa:
    name: Release to PPA
    needs: create-release
    uses: ./.github/workflows/ppa-release.yml
    secrets: inherit
