name: PR Build

on:
  pull_request:
    branches:
      - "*"
  workflow_dispatch:

jobs:
  prepare:
    name: Prepare Version
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      clean_version: ${{ steps.version.outputs.clean_version }}
      latest_tag: ${{ steps.version.outputs.latest_tag }}
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1
        with:
          fetch-depth: 0

      - name: Generate PR version
        id: version
        shell: bash
        env:
          PR_NUMBER: ${{ github.event.number }}
        run: |
          set -eo pipefail

          LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
          COMMIT_HASH=$(git rev-parse --short HEAD)

          if [ -n "${PR_NUMBER}" ]; then
            VERSION="${LATEST_TAG}-dev-${COMMIT_HASH}-pr${PR_NUMBER}"
          else
            VERSION="${LATEST_TAG}-dev-${COMMIT_HASH}"
          fi

          echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
          echo "clean_version=${VERSION#v}" >> "$GITHUB_OUTPUT"
          echo "latest_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
          echo "Generated version: ${VERSION}"

  verify-oas:
    name: Verify OpenAPI Artifacts
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1

      - name: Set up Go
        uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff  # v5.6.0
        with:
          go-version: "1.25"

      - name: Install swag
        run: go install github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc4

      - name: Regenerate and verify OpenAPI spec
        run: scripts/verify-oas.sh

  # Build frontend once and share across all jobs
  build-frontend:
    name: Build Frontend
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1

      - name: Setup Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020  # v4.4.0
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json

      - name: Install frontend dependencies
        run: cd frontend && npm ci

      - name: Build frontend
        run: cd frontend && npm run build

      - name: Upload frontend artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2
        with:
          name: frontend-dist-pr
          path: frontend/dist/
          retention-days: 1

  build:
    name: Build Binaries
    needs: [prepare, build-frontend, verify-oas]
    runs-on: ${{ matrix.os }}
    env:
      VERSION: ${{ needs.prepare.outputs.version }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            goos: linux
            goarch: amd64
            cgo: "0"
            name: mcpproxy-linux-amd64
            archive_format: tar.gz
          - os: ubuntu-latest
            goos: linux
            goarch: arm64
            cgo: "0"
            name: mcpproxy-linux-arm64
            archive_format: tar.gz
          - os: windows-latest
            goos: windows
            goarch: amd64
            cgo: "1"
            name: mcpproxy-windows-amd64.exe
            archive_format: zip
          - os: windows-latest
            goos: windows
            goarch: arm64
            cgo: "1"
            name: mcpproxy-windows-arm64.exe
            archive_format: zip
          - os: macos-15
            goos: darwin
            goarch: amd64
            cgo: "1"
            name: mcpproxy-darwin-amd64
            archive_format: tar.gz
          - os: macos-15
            goos: darwin
            goarch: arm64
            cgo: "1"
            name: mcpproxy-darwin-arm64
            archive_format: tar.gz

    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff  # v5.6.0
        with:
          go-version: "1.25"
          cache: false  # Disable built-in cache to use explicit cache step below

      - name: Clean Go module cache (macOS workaround)
        if: runner.os == 'macOS'
        run: |
          # Remove potentially corrupted toolchain files
          rm -rf ~/go/pkg/mod/golang.org/toolchain* || true

      - name: Cache Go modules and build
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830  # v4.3.0
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
            ~/AppData/Local/go-build
          key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-1.25-

      - name: Download dependencies
        run: go mod download

      - name: Download frontend artifact
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
        with:
          name: frontend-dist-pr
          path: frontend/dist

      - name: Copy frontend dist for embedding
        shell: bash
        run: |
          rm -rf web/frontend
          mkdir -p web/frontend/dist
          cp -r frontend/dist/. web/frontend/dist/
          # Recreate the tracked .gitkeep so //go:embed all:frontend/dist
          # always has something to embed (matches the Makefile frontend-build target).
          touch web/frontend/dist/.gitkeep

      - name: Run tests (skip binary E2E tests - not compatible with cross-compilation)
        # Skip tests for Windows ARM64 (cross-compilation - can't run ARM64 binaries on AMD64 runner)
        if: '!(matrix.goos == ''windows'' && matrix.goarch == ''arm64'')'
        shell: bash
        run: |
          # On Windows, skip internal/server tests (E2E tests timeout after 11 minutes)
          if [ "${{ matrix.goos }}" = "windows" ]; then
            go test -tags nogui -v -skip "E2E|Binary|MCPProtocol" $(go list ./... | grep -v 'internal/server$')
          else
            go test -tags nogui -v -skip "E2E|Binary|MCPProtocol" ./...
          fi

      - name: Build binary and create archives
        shell: bash
        env:
          CGO_ENABLED: ${{ matrix.cgo }}
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          VERSION: ${{ env.VERSION }}
          # ✅ Force minimum supported macOS version for compatibility (13.0 for Swift tray app)
          MACOSX_DEPLOYMENT_TARGET: "13.0"
          # Defensive CGO flags to ensure proper deployment target
          CGO_CFLAGS: "-mmacosx-version-min=13.0"
          CGO_LDFLAGS: "-mmacosx-version-min=13.0"
        run: |
          LDFLAGS="-s -w -X mcpproxy-go/cmd/mcpproxy.version=${VERSION} -X main.version=${VERSION}"

          # Determine clean binary name
          if [ "${{ matrix.goos }}" = "windows" ]; then
            CLEAN_BINARY="mcpproxy.exe"
          else
            CLEAN_BINARY="mcpproxy"
          fi

          # Create clean core binary for archive
          go build -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy

          # Build tray binary for platforms with GUI support (macOS and Windows)
          if [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; then
            echo "Building mcpproxy-tray for ${{ matrix.goos }}..."

            # Determine tray binary name
            if [ "${{ matrix.goos }}" = "windows" ]; then
              TRAY_BINARY="mcpproxy-tray.exe"
            else
              TRAY_BINARY="mcpproxy-tray"
            fi

            go build -ldflags "${LDFLAGS}" -o ${TRAY_BINARY} ./cmd/mcpproxy-tray
          fi

          # Ad-hoc sign macOS binaries (development only)
          if [ "${{ matrix.goos }}" = "darwin" ]; then
            echo "Ad-hoc signing macOS binaries for development..."
            codesign --force --deep --sign - --identifier "com.smartmcpproxy.mcpproxy.dev" ${CLEAN_BINARY}
            codesign --force --deep --sign - --identifier "com.smartmcpproxy.mcpproxy-tray.dev" mcpproxy-tray

            # Verify signing
            codesign --verify --verbose ${CLEAN_BINARY}
            codesign --verify --verbose mcpproxy-tray
            echo "Binaries signed successfully (development)"
          fi

          # Create archive with version info
          ARCHIVE_BASE="mcpproxy-${VERSION#v}-${{ matrix.goos }}-${{ matrix.goarch }}"

          # Determine files to include in archive
          FILES_TO_ARCHIVE="${CLEAN_BINARY}"

          # Add tray binary if it exists (Windows and macOS)
          if [ "${{ matrix.goos }}" = "windows" ] && [ -f "mcpproxy-tray.exe" ]; then
            FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray.exe"
            echo "Including mcpproxy-tray.exe in archive"
          elif [ "${{ matrix.goos }}" = "darwin" ] && [ -f "mcpproxy-tray" ]; then
            FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray"
            echo "Including mcpproxy-tray in archive"
          fi

          if [ "${{ matrix.archive_format }}" = "zip" ]; then
            # Create ZIP archive (Windows)
            # Use PowerShell Compress-Archive on Windows since zip command isn't available
            if [ "${{ matrix.goos }}" = "windows" ]; then
              # Convert space-separated list to comma-separated for PowerShell
              PS_FILES=$(echo ${FILES_TO_ARCHIVE} | sed 's/ /,/g')
              powershell -Command "Compress-Archive -Path ${PS_FILES} -DestinationPath '${ARCHIVE_BASE}.zip'"
            else
              zip "${ARCHIVE_BASE}.zip" ${FILES_TO_ARCHIVE}
            fi
          else
            # Create tar.gz archive (Unix)
            tar -czf "${ARCHIVE_BASE}.tar.gz" ${FILES_TO_ARCHIVE}
          fi

      # Build Swift tray app (macOS only)
      - name: Build Swift tray app
        if: matrix.goos == 'darwin'
        run: |
          chmod +x scripts/build-swift-app.sh
          OUTPUT_DIR="$(pwd)/swift-build"
          mkdir -p "$OUTPUT_DIR"
          ./scripts/build-swift-app.sh "${{ env.VERSION }}" "${{ matrix.goarch }}" "$OUTPUT_DIR"
          SWIFT_APP="$OUTPUT_DIR/MCPProxy.app"
          if [ -d "$SWIFT_APP" ]; then
            # Ad-hoc sign for development
            codesign --force --deep --sign - --identifier "com.smartmcpproxy.mcpproxy.dev" "$SWIFT_APP"
            echo "SWIFT_APP_PATH=$SWIFT_APP" >> $GITHUB_ENV
          else
            echo "❌ Swift app build failed"
            exit 1
          fi

      - name: Create .icns icon (macOS)
        if: matrix.goos == 'darwin'
        run: |
          chmod +x scripts/create-icns.sh
          ./scripts/create-icns.sh

      - name: Create app bundle and DMG installer (macOS)
        if: matrix.goos == 'darwin'
        env:
          VERSION: ${{ env.VERSION }}
          # Ensure DMG creation also uses correct deployment target
          MACOSX_DEPLOYMENT_TARGET: "13.0"
          CGO_CFLAGS: "-mmacosx-version-min=13.0"
          CGO_LDFLAGS: "-mmacosx-version-min=13.0"
        run: |
          chmod +x scripts/create-pkg.sh
          chmod +x scripts/create-app-dmg.sh

          # Set up ad-hoc signing for PR builds
          echo "=== Creating app bundle and DMG for PR build (ad-hoc signed) ==="

          # Determine binary names
          TRAY_BINARY="mcpproxy-tray"
          CORE_BINARY="mcpproxy"

          # Create app bundle using create-pkg.sh in adhoc mode
          # This creates the app bundle but skips PKG creation
          export APP_CERT_IDENTITY=""  # Empty = ad-hoc signing for app bundle
          export PKG_CERT_IDENTITY="adhoc"  # Skip PKG creation, just create app bundle

          # Create app bundle (prefer Swift app bundle over Go tray binary)
          if [ -n "${SWIFT_APP_PATH:-}" ] && [ -d "${SWIFT_APP_PATH:-}" ]; then
            echo "Using Swift app bundle: $SWIFT_APP_PATH"
            ./scripts/create-pkg.sh "$SWIFT_APP_PATH" ${CORE_BINARY} ${VERSION} ${{ matrix.goarch }}
          else
            echo "Using Go tray binary: ${TRAY_BINARY}"
            ./scripts/create-pkg.sh ${TRAY_BINARY} ${CORE_BINARY} ${VERSION} ${{ matrix.goarch }}
          fi

          # App bundle is created at pkg_temp/pkg_root/Applications/mcpproxy.app
          APP_BUNDLE_PATH="pkg_temp/pkg_root/Applications/mcpproxy.app"

          if [ -d "$APP_BUNDLE_PATH" ]; then
            echo "✅ App bundle created successfully"

            # Create DMG containing the app bundle (with Applications symlink for drag-and-drop)
            ./scripts/create-app-dmg.sh "$APP_BUNDLE_PATH" ${VERSION} ${{ matrix.goarch }}

            # Clean up temp directory now that DMG is created
            rm -rf pkg_temp

            echo "✅ Installer DMG created successfully (ad-hoc signed, for testing)"
          else
            echo "❌ App bundle creation failed"
            exit 1
          fi

      - name: Skip notarization (PR Build)
        if: matrix.goos == 'darwin'
        run: |
          echo "Skipping notarization for PR build - this is a development build"
          echo "Production builds go through full notarization in release workflow"

      - name: Upload archive artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2
        with:
          name: archive-${{ matrix.goos }}-${{ matrix.goarch }}
          path: mcpproxy-*-${{ matrix.goos }}-${{ matrix.goarch }}.${{ matrix.archive_format }}
          retention-days: 14

      - name: Upload macOS installer DMG
        if: matrix.goos == 'darwin'
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2
        with:
          name: installer-dmg-${{ matrix.goos }}-${{ matrix.goarch }}
          path: mcpproxy-*-darwin-${{ matrix.goarch }}-installer.dmg
          retention-days: 14
          if-no-files-found: warn
