# =============================================================================
# WORKFLOW: Multi-Channel Package Publishing
# PURPOSE: Distribute releases to NPM, GitHub Packages, and Docker Hub
# TRIGGERS: GitHub release publication or manual dispatch
# OUTPUTS: Published packages to configured registries
# =============================================================================

name: Publish

on:
  release:
    types: [published] # Triggered when a GitHub release is published
  workflow_dispatch: # Manual trigger for re-publishing or testing
    inputs:
      tag:
        description: 'Release tag to publish (e.g., v1.2.3)'
        required: true
        type: string

# Allow only one publish workflow per branch
# cancel-in-progress: false to allow multiple releases to proceed
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false

# Global environment variables for consistency
env:
  PNPM_VERSION: 10.17.0 # Pinned: Must match packageManager in package.json
  NODE_VERSION: 22 # Pinned: Must match engines.node in package.json

# SECURITY: Minimal required permissions
# contents: read - Checkout code at release tag
# packages: write - Publish to GitHub Packages
# id-token: write - Generate provenance for npm
permissions:
  contents: read
  packages: write
  id-token: write

jobs:
  # =============================================================================
  # NPM PUBLISHING
  # Publishes package to npm registry with provenance
  # =============================================================================

  npm:
    name: Publish to NPM
    runs-on: ubuntu-latest
    # Only runs if ENABLE_NPM_RELEASE variable is set to 'true'
    # Configure in Settings > Secrets and variables > Variables
    if: vars.ENABLE_NPM_RELEASE == 'true'
    permissions:
      contents: read
      id-token: write # Required for npm provenance
      actions: read # Required to download artifacts
    steps:
      - name: Determine version
        id: version
        # Extract version from release tag or manual input
        # Strips 'v' prefix to get semver (v1.2.3 -> 1.2.3)
        run: |
          VERSION="${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}"
          VERSION="${VERSION#v}"  # Remove 'v' prefix
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "tag=v$VERSION" >> $GITHUB_OUTPUT
          echo "📦 Publishing to NPM: $VERSION"

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          # IMPORTANT: Checkout the exact release tag, not latest main
          # This ensures we publish exactly what was released
          ref: ${{ steps.version.outputs.tag }}

      - name: Setup Node.js
        # Node.js is required for npm publish command
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          # Configure npm registry for authentication
          registry-url: 'https://registry.npmjs.org'

      - name: Determine artifact source
        id: artifact
        # Use shared script to find the correct NPM package artifact from the release build
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          chmod +x .github/scripts/determine-artifact.sh
          .github/scripts/determine-artifact.sh \
            --tag "${{ steps.version.outputs.tag }}" \
            --repo "${{ github.repository }}" \
            --version "${{ steps.version.outputs.version }}" \
            --prefix "npm-package" \
            --output "$GITHUB_OUTPUT"

      - name: Download pre-built NPM package
        id: download
        # Download the pre-built, pre-scanned NPM package from main workflow
        # This ensures we publish exactly what was tested
        uses: actions/download-artifact@v4
        with:
          name: ${{ steps.artifact.outputs.artifact_name }}
          path: ./npm-artifact
          run-id: ${{ steps.artifact.outputs.run_id }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract pre-built package
        run: |
          # Check if any .tgz files exist
          TARBALL=$(find ./npm-artifact -name "*.tgz" -type f | head -1)
          if [ -z "$TARBALL" ]; then
            echo "❌ No .tgz file found in artifact!"
            echo "Contents of ./npm-artifact:"
            ls -la ./npm-artifact/
            exit 1
          fi

          echo "✅ Using pre-built NPM package from main workflow"
          echo "📦 Extracting: $TARBALL"
          tar -xzf "$TARBALL"

          # The package extracts to a 'package' directory
          # We need to move its contents to the current directory
          if [ -d package ]; then
            cp -r package/* .
            rm -rf package
          fi

          echo "📋 Verified package contents from manifest"
          if [ -f ./npm-artifact/npm-package-manifest.txt ]; then
            echo "Package contains $(wc -l < ./npm-artifact/npm-package-manifest.txt) files"
          fi

      - name: Check NPM token
        id: check-npm
        # Gracefully handle missing NPM_TOKEN
        # Allows workflow to succeed even without npm publishing
        run: |
          if [ -n "${{ secrets.NPM_TOKEN }}" ]; then
            echo "has_token=true" >> $GITHUB_OUTPUT
            echo "✅ NPM_TOKEN is configured"
          else
            echo "has_token=false" >> $GITHUB_OUTPUT
            echo "⚠️ NPM_TOKEN is not configured, skipping publish"
            # To fix: Add NPM_TOKEN secret in Settings > Secrets
          fi

      - name: Publish to NPM
        if: steps.check-npm.outputs.has_token == 'true'
        run: |
          # Remove private flag and prepare script (which runs husky)
          # The prepare script runs even with --ignore-scripts, so we must remove it
          jq 'del(.private) | del(.scripts.prepare)' package.json > tmp.json && mv tmp.json package.json

          # Publish with provenance for supply chain security
          # --provenance creates a signed attestation of the build
          npm publish --provenance --access public
        env:
          # SECURITY: NPM_TOKEN required for authentication
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  # =============================================================================
  # GITHUB PACKAGES PUBLISHING
  # Publishes package to GitHub's npm registry
  # =============================================================================

  github-packages:
    name: Publish to GitHub Packages
    runs-on: ubuntu-latest
    # Only runs if ENABLE_GITHUB_PACKAGES variable is set
    # Useful for private packages within organization
    if: vars.ENABLE_GITHUB_PACKAGES == 'true'
    permissions:
      contents: read
      packages: write # Required to publish to GitHub Packages
      id-token: write # Required for provenance
      actions: read # Required to download artifacts
    steps:
      - name: Determine version
        id: version
        run: |
          VERSION="${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}"
          VERSION="${VERSION#v}"
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "tag=v$VERSION" >> $GITHUB_OUTPUT
          echo "📦 Publishing to GitHub Packages: $VERSION"

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ steps.version.outputs.tag }}

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          # GitHub Packages npm registry URL
          registry-url: 'https://npm.pkg.github.com'

      - name: Determine artifact source
        id: artifact
        # Use shared script to find the correct NPM package artifact (same as npm job)
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          chmod +x .github/scripts/determine-artifact.sh
          .github/scripts/determine-artifact.sh \
            --tag "${{ steps.version.outputs.tag }}" \
            --repo "${{ github.repository }}" \
            --version "${{ steps.version.outputs.version }}" \
            --prefix "npm-package" \
            --output "$GITHUB_OUTPUT"

      - name: Download pre-built NPM package
        id: download
        uses: actions/download-artifact@v4
        with:
          name: ${{ steps.artifact.outputs.artifact_name }}
          path: ./npm-artifact
          run-id: ${{ steps.artifact.outputs.run_id }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract pre-built package
        run: |
          # Check if any .tgz files exist
          TARBALL=$(find ./npm-artifact -name "*.tgz" -type f | head -1)
          if [ -z "$TARBALL" ]; then
            echo "❌ No .tgz file found in artifact!"
            echo "Contents of ./npm-artifact:"
            ls -la ./npm-artifact/
            exit 1
          fi

          echo "✅ Using pre-built NPM package from main workflow"
          echo "📦 Extracting: $TARBALL"
          tar -xzf "$TARBALL"

          # The package extracts to a 'package' directory
          if [ -d package ]; then
            cp -r package/* .
            rm -rf package
          fi

          echo "📋 Verified package contents"

      - name: Publish to GitHub Packages
        run: |
          # Scope package name to organization and remove private flag and prepare script
          # The prepare script runs even with --ignore-scripts, so we must remove it
          jq '.name = "@${{ github.repository_owner }}/" + .name | del(.private) | del(.scripts.prepare)' package.json > tmp.json && mv tmp.json package.json

          npm publish --access public
        env:
          # SECURITY: Uses GITHUB_TOKEN for authentication
          # Automatically available, no configuration needed
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # =============================================================================
  # DOCKER HUB PUBLISHING
  # Copies pre-built multi-platform image from GHCR to Docker Hub
  # =============================================================================

  docker:
    name: Publish to Docker Hub
    runs-on: ubuntu-latest
    # Only runs if ENABLE_DOCKER_RELEASE variable is set
    # Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets
    if: vars.ENABLE_DOCKER_RELEASE == 'true'
    permissions:
      contents: read
      packages: read # Read from GitHub Container Registry
    steps:
      - name: Determine version
        id: version
        run: |
          VERSION="${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}"
          VERSION="${VERSION#v}"
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "tag=v$VERSION" >> $GITHUB_OUTPUT
          echo "🐳 Publishing Docker image: $VERSION"

      - name: Check Docker credentials
        id: check-docker
        # Validate Docker Hub credentials exist
        # Allows workflow to succeed without Docker publishing
        run: |
          if [ -n "${{ secrets.DOCKERHUB_USERNAME }}" ] && [ -n "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
            echo "has_credentials=true" >> $GITHUB_OUTPUT
            echo "✅ Docker Hub credentials are configured"
          else
            echo "has_credentials=false" >> $GITHUB_OUTPUT
            echo "⚠️ Docker Hub credentials are not configured, skipping publish"
            # To fix: Add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN in Settings > Secrets
            exit 0
          fi

      - name: Set up Docker Buildx
        # Required for imagetools commands
        if: steps.check-docker.outputs.has_credentials == 'true'
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        # Login to GHCR to pull the pre-built image
        if: steps.check-docker.outputs.has_credentials == 'true'
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to Docker Hub
        # SECURITY: Authenticate with Docker Hub for pushing
        if: steps.check-docker.outputs.has_credentials == 'true'
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Copy image from GHCR to Docker Hub
        # Use buildx imagetools to copy multi-platform image between registries
        # This properly handles multi-platform manifest lists
        if: steps.check-docker.outputs.has_credentials == 'true'
        run: |
          SOURCE_IMAGE="ghcr.io/${{ github.repository_owner }}/sonarqube-mcp-server"
          TARGET_REPO="${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}"
          VERSION="${{ steps.version.outputs.version }}"

          echo "📤 Copying multi-platform image from GHCR to Docker Hub..."
          echo "Source: $SOURCE_IMAGE:$VERSION"
          echo "Target: $TARGET_REPO:$VERSION"

          # Copy image with version tag
          docker buildx imagetools create \
            --tag $TARGET_REPO:$VERSION \
            $SOURCE_IMAGE:$VERSION

          echo "🏷️ Creating additional tags..."
          # Create alias tags for latest, major, and major.minor versions
          MAJOR=$(echo "$VERSION" | cut -d. -f1)
          MINOR=$(echo "$VERSION" | cut -d. -f2)

          docker buildx imagetools create --tag $TARGET_REPO:latest $TARGET_REPO:$VERSION
          docker buildx imagetools create --tag $TARGET_REPO:$MAJOR $TARGET_REPO:$VERSION
          docker buildx imagetools create --tag $TARGET_REPO:$MAJOR.$MINOR $TARGET_REPO:$VERSION

          echo "✅ Docker image published successfully to Docker Hub"
          echo "📋 Published tags: $VERSION, latest, $MAJOR, $MAJOR.$MINOR"

  # =============================================================================
  # NOTIFICATION
  # Send status updates to team communication channels
  # =============================================================================

  notify:
    name: Notify
    if: always() # Run even if publishing jobs fail
    needs: [npm, docker, github-packages]
    runs-on: ubuntu-latest
    steps:
      - name: Check Slack webhook
        id: check-slack
        # Gracefully handle missing Slack configuration
        run: |
          if [ -n "${{ secrets.SLACK_WEBHOOK }}" ]; then
            echo "has_webhook=true" >> $GITHUB_OUTPUT
          else
            echo "has_webhook=false" >> $GITHUB_OUTPUT
            # Optional: Configure SLACK_WEBHOOK in Settings > Secrets
          fi

      - name: Send Slack notification
        # Send release status to Slack channel
        # Shows success/skip/failure for each distribution channel
        if: steps.check-slack.outputs.has_webhook == 'true'
        uses: slackapi/slack-github-action@v2
        with:
          payload: |
            {
              "text": "🚀 Release ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}",
              "blocks": [
                {
                  "type": "section",
                  "fields": [
                    {"type": "mrkdwn", "text": "*Repo:*\n${{ github.repository }}"},
                    {"type": "mrkdwn", "text": "*NPM:*\n${{ needs.npm.result == 'success' && '✅' || needs.npm.result == 'skipped' && '⏭️' || '❌' }}"},
                    {"type": "mrkdwn", "text": "*Docker:*\n${{ needs.docker.result == 'success' && '✅' || needs.docker.result == 'skipped' && '⏭️' || '❌' }}"},
                    {"type": "mrkdwn", "text": "*GitHub:*\n${{ needs.github-packages.result == 'success' && '✅' || needs.github-packages.result == 'skipped' && '⏭️' || '❌' }}"}
                  ]
                }
              ]
            }
        env:
          # SECURITY: Webhook URL for Slack integration
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
