name: Auto PR V2 Deployment

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]
  workflow_dispatch:
    inputs:
      pr:
        description: "PR number to deploy"
        required: true
      allow_fork:
        description: "Allow deploying fork PR?"
        required: false
        default: "false"

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  check-pr:
    if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    outputs:
      should_deploy: ${{ steps.decide.outputs.should_deploy }}
      is_fork: ${{ steps.resolve.outputs.is_fork }}
      allow_fork: ${{ steps.decide.outputs.allow_fork }}
      pr_number: ${{ steps.resolve.outputs.pr_number }}
      pr_repository: ${{ steps.resolve.outputs.repository }}
      pr_ref: ${{ steps.resolve.outputs.ref }}
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
        with:
          egress-policy: audit

      - name: Resolve PR info
        id: resolve
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            const { owner, repo } = context.repo;
            let prNumber = context.eventName === 'workflow_dispatch'
              ? parseInt(process.env.INPUT_PR, 10)
              : context.payload.number;

            if (!Number.isInteger(prNumber)) { core.setFailed('Invalid PR number'); return; }

            const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
            core.setOutput('pr_number', String(prNumber));
            core.setOutput('repository', pr.head.repo.full_name);
            core.setOutput('ref', pr.head.ref);
            core.setOutput('is_fork', String(pr.head.repo.fork));
            core.setOutput('base_ref', pr.base.ref);
            core.setOutput('author', pr.user.login);
            core.setOutput('state', pr.state);

      - name: Decide deploy
        id: decide
        shell: bash
        env:
          EVENT_NAME: ${{ github.event_name }}
          STATE: ${{ steps.resolve.outputs.state }}
          IS_FORK: ${{ steps.resolve.outputs.is_fork }}
          # nur bei workflow_dispatch gesetzt:
          ALLOW_FORK_INPUT: ${{ inputs.allow_fork }}
          # für Auto-PR-Logik:
          PR_TITLE: ${{ github.event.pull_request.title }}
          PR_BRANCH: ${{ github.event.pull_request.head.ref }}
          PR_BASE: ${{ steps.resolve.outputs.base_ref }}
          PR_AUTHOR: ${{ steps.resolve.outputs.author }}
        run: |
          set -e
          # Standard: nichts deployen
          should=false
          allow_fork="$(echo "${ALLOW_FORK_INPUT:-false}" | tr '[:upper:]' '[:lower:]')"

          if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
            if [ "$STATE" != "open" ]; then
              echo "PR not open -> skip"
            else
              if [ "$IS_FORK" = "true" ] && [ "$allow_fork" != "true" ]; then
                echo "Fork PR and allow_fork=false -> skip"
              else
                should=true
              fi
            fi
          else
            auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "DarioGii" "ConnorYoh" "EthanHealy01" "jbrunton96")
            is_auth=false; for u in "${auth_users[@]}"; do [ "$u" = "$PR_AUTHOR" ] && is_auth=true && break; done
            if [ "$PR_BASE" = "V2" ] && [ "$is_auth" = true ]; then
              should=true
            else
              title_has_v2=false; echo "$PR_TITLE" | grep -qiE 'v2|version.?2|version.?two' && title_has_v2=true
              branch_has_kw=false; echo "$PR_BRANCH" | grep -qiE 'v2|react' && branch_has_kw=true
              if [ "$is_auth" = true ] && { [ "$title_has_v2" = true ] || [ "$branch_has_kw" = true ]; }; then
                should=true
              fi
            fi
          fi

          echo "should_deploy=$should" >> $GITHUB_OUTPUT
          echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT

  deploy-v2-pr:
    needs: check-pr
    runs-on: ubuntu-latest
    if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true')
    # Concurrency control - only one deployment per PR at a time
    concurrency:
      group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }}
      cancel-in-progress: true
    permissions:
      contents: read
      issues: write
      pull-requests: write

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
        with:
          egress-policy: audit

      - name: Checkout main repository
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          repository: ${{ github.repository }}
          ref: main

      - name: Setup GitHub App Bot
        if: github.actor != 'dependabot[bot]'
        id: setup-bot
        uses: ./.github/actions/setup-bot
        continue-on-error: true
        with:
          app-id: ${{ secrets.GH_APP_ID }}
          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

      - name: Add deployment started comment
        id: deployment-started
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.setup-bot.outputs.token }}
          script: |
            const { owner, repo } = context.repo;
            const prNumber = ${{ needs.check-pr.outputs.pr_number }};
            
            // Delete previous V2 deployment comments to avoid clutter
            const { data: comments } = await github.rest.issues.listComments({
              owner,
              repo,
              issue_number: prNumber,
              per_page: 100
            });

            const v2Comments = comments.filter(comment => 
              comment.body.includes('🚀 **Auto-deploying V2 version**') ||
              comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') ||
              comment.body.includes('❌ **V2 Auto-deployment failed**')
            );

            for (const comment of v2Comments) {
              console.log(`Deleting old V2 comment: ${comment.id}`);
              await github.rest.issues.deleteComment({
                owner,
                repo,
                comment_id: comment.id
              });
            }
            
            // Create new deployment started comment
            const { data: newComment } = await github.rest.issues.createComment({
              owner,
              repo,
              issue_number: prNumber,
              body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.`
            });
            return newComment.id;

      - name: Checkout PR
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          repository: ${{ needs.check-pr.outputs.pr_repository }}
          ref: ${{ needs.check-pr.outputs.pr_ref }}
          token: ${{ secrets.GITHUB_TOKEN }}
          fetch-depth: 0  # Fetch full history for commit hash detection

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1

      - name: Get version number
        id: versionNumber
        run: |
          VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
          echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT

      - name: Login to Docker Hub
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_API }}

      - name: Get commit hashes for frontend and backend
        id: commit-hashes
        run: |
          # Get last commit that touched the frontend folder, docker/frontend, or docker/compose
          FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
          if [ -z "$FRONTEND_HASH" ]; then
            FRONTEND_HASH="no-frontend-changes"
          fi

          # Get last commit that touched backend code, docker/backend, or docker/compose
          BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
          if [ -z "$BACKEND_HASH" ]; then
            BACKEND_HASH="no-backend-changes"
          fi
          
          echo "Frontend hash: $FRONTEND_HASH"
          echo "Backend hash: $BACKEND_HASH"
          
          echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
          echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
          
          # Short hashes for tags
          if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
            echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
          else
            echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
          fi
          
          if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
            echo "backend_short=no-backend" >> $GITHUB_OUTPUT
          else
            echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
          fi

      - name: Check if frontend image exists
        id: check-frontend
        run: |
          if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "Frontend image already exists, skipping build"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
            echo "Frontend image needs to be built"
          fi

      - name: Check if backend image exists
        id: check-backend
        run: |
          if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "Backend image already exists, skipping build"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
            echo "Backend image needs to be built"
          fi

      - name: Build and push V2 frontend image
        if: steps.check-frontend.outputs.exists == 'false'
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          file: ./docker/frontend/Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
          build-args: VERSION_TAG=v2-alpha
          platforms: linux/amd64

      - name: Build and push V2 backend image
        if: steps.check-backend.outputs.exists == 'false'
        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
        with:
          context: .
          file: ./docker/backend/Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
          build-args: VERSION_TAG=v2-alpha
          platforms: linux/amd64

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh/
          echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
          sudo chmod 600 ../private.key

      - name: Deploy V2 to VPS
        id: deploy
        run: |
          # Use same port strategy as regular PRs - just the PR number
          V2_PORT=${{ needs.check-pr.outputs.pr_number }}
          BACKEND_PORT=$((V2_PORT + 10000))  # Backend on higher port to avoid conflicts
          
          # Create docker-compose for V2 with separate frontend and backend
          cat > docker-compose.yml << EOF
          version: '3.3'
          services:
            stirling-pdf-v2-backend:
              container_name: stirling-pdf-v2-backend-pr-${{ needs.check-pr.outputs.pr_number }}
              image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
              ports:
                - "${BACKEND_PORT}:8080"    # Backend API port
              volumes:
                - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw
                - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw
                - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw
              environment:
                DISABLE_ADDITIONAL_FEATURES: "true"
                SECURITY_ENABLELOGIN: "false"
                SYSTEM_DEFAULTLOCALE: en-GB
                UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
                UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Frontend/Backend Split Architecture"
                UI_APPNAMENAVBAR: "V2 PR#${{ needs.check-pr.outputs.pr_number }}"
                SYSTEM_MAXFILESIZE: "100"
                METRICS_ENABLED: "true"
                SYSTEM_GOOGLEVISIBILITY: "false"
                SWAGGER_SERVER_URL: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
                baseUrl: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
              restart: on-failure:5

            stirling-pdf-v2-frontend:
              container_name: stirling-pdf-v2-frontend-pr-${{ needs.check-pr.outputs.pr_number }}
              image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
              ports:
                - "${V2_PORT}:80"          # Frontend port (same as regular PRs)
              environment:
                VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:${BACKEND_PORT}"
              depends_on:
                - stirling-pdf-v2-backend
              restart: on-failure:5
          EOF

          # Deploy to VPS
          scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose-v2.yml

          ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
            # Create V2 PR-specific directories
            mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs}

            # Move docker-compose file to correct location
            mv /tmp/docker-compose-v2.yml /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml

            # Stop any existing container and clean up
            cd /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}
            docker-compose down --remove-orphans 2>/dev/null || true
            
            # Start the new container
            docker-compose pull
            docker-compose up -d
            
            # Clean up unused Docker resources to save space
            docker system prune -af --volumes || true

            # Clean up old backend/frontend images (older than 2 weeks)
            docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
          ENDSSH

          # Set port for output
          echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT

      - name: Post V2 deployment URL to PR
        if: success()
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.setup-bot.outputs.token }}
          script: |
            const { owner, repo } = context.repo;
            const prNumber = ${{ needs.check-pr.outputs.pr_number }};
            const v2Port = ${{ steps.deploy.outputs.v2_port }};
            
            // Delete the "deploying..." comment since we're posting the final result
            const deploymentStartedId = ${{ steps.deployment-started.outputs.result }};
            if (deploymentStartedId) {
              console.log(`Deleting deployment started comment: ${deploymentStartedId}`);
              try {
                await github.rest.issues.deleteComment({
                  owner,
                  repo,
                  comment_id: deploymentStartedId
                });
              } catch (error) {
                console.log(`Could not delete deployment started comment: ${error.message}`);
              }
            }
            
            const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${v2Port}`;
            const httpsUrl = `https://${v2Port}.ssl.stirlingpdf.cloud`;

            const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
                              `Your V2 PR with the new frontend/backend split architecture has been deployed!\n\n` +
                              `🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` +
                              `🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` +
                              `_This deployment will be automatically cleaned up when the PR is closed._\n\n` +
                              `🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`;

            await github.rest.issues.createComment({
              owner,
              repo,
              issue_number: prNumber,
              body: commentBody
            });

  cleanup-v2-deployment:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
      pull-requests: write

    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
        with:
          egress-policy: audit

      - name: Checkout repository
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Setup GitHub App Bot
        if: github.actor != 'dependabot[bot]'
        id: setup-bot
        uses: ./.github/actions/setup-bot
        continue-on-error: true
        with:
          app-id: ${{ secrets.GH_APP_ID }}
          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

      - name: Clean up V2 deployment comments
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.setup-bot.outputs.token }}
          script: |
            const { owner, repo } = context.repo;
            const prNumber = ${{ github.event.pull_request.number }};
            
            // Find and delete V2 deployment comments
            const { data: comments } = await github.rest.issues.listComments({
              owner,
              repo,
              issue_number: prNumber
            });
            
            const v2Comments = comments.filter(c =>
              c.body?.includes("## 🚀 V2 Auto-Deployment Complete!") &&
              c.user?.type === "Bot"
            );
            
            for (const comment of v2Comments) {
              await github.rest.issues.deleteComment({
                owner,
                repo,
                comment_id: comment.id
              });
              console.log(`Deleted V2 deployment comment (ID: ${comment.id})`);
            }

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh/
          echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
          sudo chmod 600 ../private.key

      - name: Cleanup V2 deployment
        run: |
          ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
            if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
              echo "Found V2 PR directory, proceeding with cleanup..."

              # Stop and remove V2 containers
              cd /stirling/V2-PR-${{ github.event.pull_request.number }}
              docker-compose down || true

              # Go back to root before removal
              cd /

              # Remove V2 PR-specific directories
              rm -rf /stirling/V2-PR-${{ github.event.pull_request.number }}

              # Clean up V2 containers by name (in case compose cleanup missed them)
              docker rm -f stirling-pdf-v2-frontend-pr-${{ github.event.pull_request.number }} || true
              docker rm -f stirling-pdf-v2-backend-pr-${{ github.event.pull_request.number }} || true

              echo "V2 cleanup completed"
            else
              echo "V2 PR directory not found, nothing to clean up"
            fi
            
            # Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
            docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true

            # Note: We don't remove the commit-based images since they can be reused across PRs
            # Only remove PR-specific containers and directories
          ENDSSH

      - name: Cleanup temporary files
        if: always()
        run: |
          rm -f ../private.key
        continue-on-error: true