name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (e.g., 0.1.0)'
        required: true
        type: string
      dry_run:
        description: 'Dry run (skip publishing)'
        required: false
        default: false
        type: boolean

env:
  CARGO_INCREMENTAL: 0
  RUST_BACKTRACE: short

permissions:
  contents: write

jobs:
  create-release:
    runs-on: ubuntu-latest
    outputs:
      release_id: ${{ steps.create_release.outputs.id }}
      release_upload_url: ${{ steps.create_release.outputs.upload_url }}
      version: ${{ steps.get_version.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Get version
        id: get_version
        run: |
          if [[ -n "${{ github.event.inputs.version }}" ]]; then
            echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
            echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
          else
            echo "version=0.0.0-dev" >> $GITHUB_OUTPUT
          fi

      - name: Extract changelog for release
        env:
          VERSION: ${{ steps.get_version.outputs.version }}
        run: |
          node - <<'NODE'
          const fs = require('fs');
          const version = process.env.VERSION;
          const changelogPath = 'CHANGELOG.md';
          if (!fs.existsSync(changelogPath)) {
            throw new Error('CHANGELOG.md not found; add it before releasing.');
          }
          const changelog = fs.readFileSync(changelogPath, 'utf8');
          const sections = changelog.split(/^## /m);
          const section = sections.find(s => s.startsWith('[' + version + ']'));
          if (!section) {
            throw new Error('Changelog section [' + version + '] not found. Add a section like "## [' + version + '] - YYYY-MM-DD".');
          }
          const content = ('## ' + section).trim();
          fs.mkdirSync('artifacts', { recursive: true });
          fs.writeFileSync('artifacts/release-notes.md', content + '\n');
          NODE

      - name: Create GitHub Release
        id: create_release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ steps.get_version.outputs.version }}
          draft: true
          generate_release_notes: false
          body_path: artifacts/release-notes.md
          name: OpenChamber v${{ steps.get_version.outputs.version }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-desktop-macos:
    needs: create-release
    runs-on: macos-26
    strategy:
      fail-fast: false
      matrix:
        target: [aarch64-apple-darwin, x86_64-apple-darwin]
        include:
          - target: aarch64-apple-darwin
            arch: aarch64
            platform: darwin-aarch64
          - target: x86_64-apple-darwin
            arch: x86_64
            platform: darwin-x86_64
    outputs:
      version: ${{ needs.create-release.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup bun
        uses: oven-sh/setup-bun@v2

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

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

      - name: Rust cache
        uses: swatinem/rust-cache@v2
        with:
          workspaces: packages/desktop/src-tauri
          key: ${{ matrix.target }}

      - name: Install dependencies
        run: bun install --frozen-lockfile

      - name: Install Apple Certificate
        env:
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
        run: |
          # Create temporary keychain
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)

          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

          # Import certificate
          echo "$APPLE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
          security import $RUNNER_TEMP/certificate.p12 \
            -P "$APPLE_CERTIFICATE_PASSWORD" \
            -A -t cert -f pkcs12 \
            -k "$KEYCHAIN_PATH"

          security list-keychain -d user -s "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: \
            -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

      - name: Set up notarization credentials
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
        run: |
          # Validate secrets are set
          if [ -z "$APPLE_ID" ] || [ -z "$APPLE_TEAM_ID" ] || [ -z "$APPLE_PASSWORD" ]; then
            echo "Error: Missing Apple notarization credentials"
            exit 1
          fi

          xcrun notarytool store-credentials "openchamber-notarize" \
            --apple-id "$APPLE_ID" \
            --team-id "$APPLE_TEAM_ID" \
            --password "$APPLE_PASSWORD"

      - name: Build UI package
        run: bun run --cwd packages/ui build

      - name: Build Desktop app
        # Note: We use inline commands instead of desktop:build to pass architecture-specific --target flag
        # This enables cross-compilation for both arm64 and x86_64 from the same runner
        run: |
          export TAURI_ENV_TARGET_TRIPLE=${{ matrix.target }}
          bun run --cwd packages/desktop build
          bun run --cwd packages/desktop tauri build --target ${{ matrix.target }}
        env:
          TAURI_ENV_TARGET_TRIPLE: ${{ matrix.target }}
          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

      - name: Verify binary architectures
        run: |
          set -euo pipefail
          
          BUNDLE_DIR="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos"
          
          if [ ! -d "$BUNDLE_DIR" ]; then
            echo "❌ Error: bundle directory not found: $BUNDLE_DIR"
            exit 1
          fi
          
          APP_PATH=$(find "$BUNDLE_DIR" -maxdepth 2 -name "*.app" -print -quit)
          if [ -z "$APP_PATH" ]; then
            echo "❌ Error: .app bundle not found under $BUNDLE_DIR"
            exit 1
          fi
          
          echo "🔍 Verifying binary architectures in $APP_PATH"
          
          # Extract raw architecture names (macOS file command reports ARM as "arm64")
          MAIN_ARCH_RAW=$(file "$APP_PATH/Contents/MacOS/openchamber-desktop" | grep -oE 'arm64|x86_64|aarch64' | head -1)
          SIDEARCH_ARCH_RAW=$(file "$APP_PATH/Contents/MacOS/openchamber-server" | grep -oE 'arm64|x86_64|aarch64' | head -1)
          
          # Normalize architecture names (arm64 -> aarch64 for consistency with Rust/Tauri)
          normalize_arch() {
            case "$1" in
              arm64) echo "aarch64" ;;
              aarch64|x86_64) echo "$1" ;;
              *) echo "unknown" ;;
            esac
          }
          
          MAIN_ARCH=$(normalize_arch "$MAIN_ARCH_RAW")
          SIDEARCH_ARCH=$(normalize_arch "$SIDEARCH_ARCH_RAW")
          EXPECTED_ARCH=$(echo "${{ matrix.target }}" | grep -oE 'aarch64|x86_64' | head -1)
          
          echo "   Main: $MAIN_ARCH_RAW → $MAIN_ARCH"
          echo "   Sidecar: $SIDEARCH_ARCH_RAW → $SIDEARCH_ARCH"
          echo "   Expected: $EXPECTED_ARCH"
          
          if [ "$MAIN_ARCH" != "$EXPECTED_ARCH" ]; then
            echo "❌ ERROR: Main binary architecture mismatch!"
            echo "   Expected: $EXPECTED_ARCH"
            echo "   Got: $MAIN_ARCH (raw: $MAIN_ARCH_RAW)"
            exit 1
          fi
          
          if [ "$SIDEARCH_ARCH" != "$EXPECTED_ARCH" ]; then
            echo "❌ ERROR: Sidecar binary architecture mismatch!"
            echo "   Expected: $EXPECTED_ARCH"
            echo "   Got: $SIDEARCH_ARCH (raw: $SIDEARCH_ARCH_RAW)"
            exit 1
          fi
          
          echo "✅ Architecture verification passed: both binaries match $EXPECTED_ARCH"

      - name: Verify macOS entitlements
        run: |
          set -euo pipefail

          BUNDLE_DIR="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos"

          if [ ! -d "$BUNDLE_DIR" ]; then
            echo "Error: bundle directory not found: $BUNDLE_DIR"
            exit 1
          fi

          APP_PATH=$(find "$BUNDLE_DIR" -maxdepth 2 -name "*.app" -print -quit)
          if [ -z "$APP_PATH" ]; then
            echo "Error: .app bundle not found under $BUNDLE_DIR"
            echo "Contents:"; ls -la "$BUNDLE_DIR"
            exit 1
          fi

          echo "Verifying app bundle: $APP_PATH"
          codesign -vv "$APP_PATH"

          ENTITLEMENTS=$(codesign -d --entitlements :- "$APP_PATH" 2>&1 || true)
          echo "$ENTITLEMENTS"

          if echo "$ENTITLEMENTS" | grep -q "com.apple.security.app-sandbox"; then
            echo "Error: app sandbox entitlement is present"
            exit 1
          fi

          for key in \
            com.apple.security.cs.allow-jit \
            com.apple.security.cs.allow-unsigned-executable-memory \
            com.apple.security.cs.disable-executable-page-protection \
            com.apple.security.cs.disable-library-validation
          do
            if ! echo "$ENTITLEMENTS" | grep -q "<key>$key</key>"; then
              echo "Error: required entitlement missing: $key"
              exit 1
            fi
          done

      - name: Prepare release artifacts
        run: |
          mkdir -p artifacts
          VERSION="${{ needs.create-release.outputs.version }}"

          # Copy DMG (Tauri names it with the target triple in the path)
          DMG_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg"
          if ls $DMG_PATH 1> /dev/null 2>&1; then
            DMG_FILE=$(ls $DMG_PATH | head -n 1)
            DMG_NAME="OpenChamber_${VERSION}_${{ matrix.platform }}.dmg"
            cp "$DMG_FILE" "artifacts/$DMG_NAME"
          else
            echo "Error: DMG file not found at $DMG_PATH"
            exit 1
          fi

          # Copy tar.gz and signature for updater
          TAR_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz"
          SIG_PATH="packages/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.tar.gz.sig"

          if ls $TAR_PATH 1> /dev/null 2>&1; then
            TAR_FILE=$(ls $TAR_PATH | head -n 1)
            TAR_BASE=$(basename "$TAR_FILE")
            TAR_NAME="${TAR_BASE%.tar.gz}-${{ matrix.platform }}.tar.gz"
            cp "$TAR_FILE" "artifacts/$TAR_NAME"
          else
            echo "Error: tar.gz file not found at $TAR_PATH"
            exit 1
          fi

          if ls $SIG_PATH 1> /dev/null 2>&1; then
            SIG_FILE=$(ls $SIG_PATH | head -n 1)
            SIG_BASE=$(basename "$SIG_FILE")
            SIG_NAME="${SIG_BASE%.tar.gz.sig}-${{ matrix.platform }}.tar.gz.sig"
            cp "$SIG_FILE" "artifacts/$SIG_NAME"
          else
            echo "Error: signature file not found at $SIG_PATH"
            exit 1
          fi

          echo "Successfully prepared artifacts:"
          ls -lh artifacts/

      - name: Generate update manifest
        run: |
          VERSION="${{ needs.create-release.outputs.version }}"

          # Find the signature file for this platform
          SIG_FILE=$(find artifacts -name "*-${{ matrix.platform }}.tar.gz.sig" | head -1)
          if [ -f "$SIG_FILE" ]; then
            SIGNATURE=$(cat "$SIG_FILE")
          else
            SIGNATURE=""
          fi

          # Find the tar.gz file name for this platform
          TAR_FILE=$(find artifacts -name "*-${{ matrix.platform }}.tar.gz" ! -name "*.sig" | head -1)
          TAR_NAME=$(basename "$TAR_FILE" 2>/dev/null || echo "OpenChamber-${{ matrix.platform }}.app.tar.gz")

          cat > artifacts/latest-${{ matrix.platform }}.json << EOF
          {
            "version": "${VERSION}",
            "notes": "See release notes at https://github.com/${{ github.repository }}/releases/tag/v${VERSION}",
            "pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
            "platforms": {
              "${{ matrix.platform }}": {
                "signature": "${SIGNATURE}",
                "url": "https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${TAR_NAME}"
              }
            }
          }
          EOF

          echo "Generated latest-${{ matrix.platform }}.json:"
          cat artifacts/latest-${{ matrix.platform }}.json

      - name: Upload release assets
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ needs.create-release.outputs.version }}
          files: |
            artifacts/*.dmg
            artifacts/*.tar.gz
            artifacts/*.tar.gz.sig
            artifacts/latest-${{ matrix.platform }}.json
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload manifest as artifact
        uses: actions/upload-artifact@v4
        with:
          name: manifest-${{ matrix.platform }}
          path: artifacts/latest-${{ matrix.platform }}.json
          retention-days: 1

  publish-npm:
    needs: create-release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup bun
        uses: oven-sh/setup-bun@v2

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: bun install --frozen-lockfile

      - name: Build packages
        run: bun run build

      - name: Create npm tarball
        working-directory: packages/web
        run: npm pack

      - name: Upload npm tarball to release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ needs.create-release.outputs.version }}
          files: packages/web/*.tgz
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Publish to npm
        if: ${{ github.event.inputs.dry_run != 'true' }}
        working-directory: packages/web
        run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  combine-manifests:
    needs: [create-release, build-desktop-macos]
    runs-on: ubuntu-latest
    steps:
      - name: Download aarch64 manifest
        uses: actions/download-artifact@v4
        with:
          name: manifest-darwin-aarch64
          path: artifacts

      - name: Download x86_64 manifest
        uses: actions/download-artifact@v4
        with:
          name: manifest-darwin-x86_64
          path: artifacts

      - name: Combine manifests
        run: |
          VERSION="${{ needs.create-release.outputs.version }}"
          PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
          REPO="${{ github.repository }}"

          # Validate that both manifest files exist and are valid JSON
          if [ ! -f artifacts/latest-darwin-aarch64.json ]; then
            echo "Error: aarch64 manifest not found"
            exit 1
          fi

          if [ ! -f artifacts/latest-darwin-x86_64.json ]; then
            echo "Error: x86_64 manifest not found"
            exit 1
          fi

          # Validate JSON structure
          if ! jq empty artifacts/latest-darwin-aarch64.json 2>/dev/null; then
            echo "Error: aarch64 manifest is not valid JSON"
            exit 1
          fi

          if ! jq empty artifacts/latest-darwin-x86_64.json 2>/dev/null; then
            echo "Error: x86_64 manifest is not valid JSON"
            exit 1
          fi

          # Validate platform data exists in manifests
          if ! jq -e '.platforms["darwin-aarch64"]' artifacts/latest-darwin-aarch64.json > /dev/null; then
            echo "Error: darwin-aarch64 platform data not found in manifest"
            exit 1
          fi

          if ! jq -e '.platforms["darwin-x86_64"]' artifacts/latest-darwin-x86_64.json > /dev/null; then
            echo "Error: darwin-x86_64 platform data not found in manifest"
            exit 1
          fi

          # Use jq to properly combine the manifests
          jq -n \
            --arg version "$VERSION" \
            --arg notes "See release notes at https://github.com/${REPO}/releases/tag/v${VERSION}" \
            --arg pub_date "$PUB_DATE" \
            --slurpfile aarch64 artifacts/latest-darwin-aarch64.json \
            --slurpfile x86_64 artifacts/latest-darwin-x86_64.json \
            '{
              version: $version,
              notes: $notes,
              pub_date: $pub_date,
              platforms: {
                "darwin-aarch64": $aarch64[0].platforms["darwin-aarch64"],
                "darwin-x86_64": $x86_64[0].platforms["darwin-x86_64"]
              }
            }' > artifacts/latest.json

          echo "Generated combined latest.json:"
          cat artifacts/latest.json

      - name: Upload combined manifest
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ needs.create-release.outputs.version }}
          files: artifacts/latest.json
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  finalize-release:
    needs: [create-release, build-desktop-macos, publish-npm, combine-manifests]
    runs-on: ubuntu-latest
    env:
      DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
    steps:
      - name: Publish release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ needs.create-release.outputs.version }}
          draft: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Send release to Discord
        if: ${{ env.DISCORD_WEBHOOK_URL != '' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ needs.create-release.outputs.version }}
          REPOSITORY: ${{ github.repository }}
        run: |
          node - <<'NODE'
          (async () => {
            const tag = `v${process.env.VERSION}`;
            const repo = process.env.REPOSITORY;

            const releaseRes = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${tag}`, {
              headers: {
                Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
                Accept: 'application/vnd.github+json',
              },
            });

            if (!releaseRes.ok) {
              const body = await releaseRes.text();
              throw new Error(`Failed to fetch release ${tag}: ${releaseRes.status} ${body}`);
            }

            const release = await releaseRes.json();
            const description = (release.body || `OpenChamber ${tag} released.`).slice(0, 4096);

            const payload = {
              username: 'OpenChamber Releases',
              embeds: [
                {
                  title: release.name || `OpenChamber ${tag}`,
                  url: release.html_url,
                  description,
                  color: 2105893,
                  footer: { text: 'OpenChamber Changelog' },
                },
              ],
            };

            const discordRes = await fetch(process.env.DISCORD_WEBHOOK_URL, {
              method: 'POST',
              headers: { 'content-type': 'application/json' },
              body: JSON.stringify(payload),
            });

            if (!discordRes.ok) {
              const body = await discordRes.text();
              throw new Error(`Failed to send Discord release: ${discordRes.status} ${body}`);
            }
          })().catch((error) => {
            console.error(error);
            process.exit(1);
          });
          NODE

      - name: Trigger openchamber-website site refresh (optional)
        env:
          WEBSITE_REPO: openchamber/openchamber-website
          WEBSITE_TOKEN: ${{ secrets.OPENCHAMBER_WEBSITE_REPO_TOKEN }}
          VERSION: ${{ needs.create-release.outputs.version }}
        run: |
          if [ -z "$WEBSITE_TOKEN" ]; then
            echo "OPENCHAMBER_WEBSITE_REPO_TOKEN not set; skip site refresh dispatch."
            exit 0
          fi

          curl --fail-with-body -sS -X POST \
            -H "Authorization: Bearer $WEBSITE_TOKEN" \
            -H "Accept: application/vnd.github+json" \
            https://api.github.com/repos/$WEBSITE_REPO/dispatches \
            -d @- <<JSON
          {
            "event_type": "site_refresh_requested",
            "client_payload": {
              "source_repo": "${{ github.repository }}",
              "release_tag": "v$VERSION"
            }
          }
          JSON
