name: CI

on:
  pull_request:
    branches: [ main, stage ]
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0
    
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Cache global dependencies
      id: cache-globals
      uses: actions/cache@v3
      with:
        path: |
          ${{ runner.os == 'Windows' && 'C:\Users\runneradmin\AppData\Roaming\npm\node_modules\node-gyp' || '~/.npm/node_modules/node-gyp' }}
          ${{ runner.os == 'Windows' && 'C:\Users\runneradmin\AppData\Roaming\npm\node_modules\cmake-js' || '~/.npm/node_modules/cmake-js' }}
        key: ${{ runner.os }}-global-deps-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-global-deps-
    
    - name: Install global dependencies
      if: steps.cache-globals.outputs.cache-hit != 'true'
      run: |
        npm install -g node-gyp
        npm install -g cmake-js
    
    - name: Install dependencies
      run: npm ci
      
    - name: Security audit
      run: npm audit --audit-level=high
      
    - name: Check for known vulnerabilities
      run: npx audit-ci --high
      
    - name: Build project
      run: node scripts/build.js
    
    - name: Run ESLint
      run: npm run lint

    - name: Cache build output
      id: cache-build
      uses: actions/cache@v3
      with:
        path: build
        key: ${{ runner.os }}-build-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-build-
    
    - name: Run tests
      run: npm test
      
    - name: Upload coverage reports to Codecov
      uses: codecov/codecov-action@v4
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        
  pr-review:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'pull_request'
    
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0
    
    - name: Send PR data to webhook for code review
      uses: actions/github-script@v6
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        script: |
          console.log('Processing PR #' + context.issue.number + ' in ' + context.repo.owner + '/' + context.repo.repo);
          
          try {
            // Get PR details
            const pr = await github.rest.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            });
            
            // Get PR files
            const files = await github.rest.pulls.listFiles({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            });
            console.log('Files changed:', files.data.length);
            
            // Setup webhook URL
            const webhookUrl = '${{ vars.WEBHOOK_URL }}';
            
            // Validate webhook URL
            if (!webhookUrl || !webhookUrl.trim()) {
              throw new Error('WEBHOOK_URL is not configured');
            }
            
            const url = new URL(webhookUrl);
            // Ensure HTTPS is used for security
            if (url.protocol !== 'https:') {
              throw new Error('WEBHOOK_URL must use HTTPS protocol for security');
            }
            
            // Get PR comments
            const comments = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number
            });
            
            // Get PR review comments
            const reviewComments = await github.rest.pulls.listReviewComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            });
            
            // Import PR webhook utilities
            const fs = require('fs');
            const path = require('path');
            
            // Define the path to the utils file
            const utilsPath = path.join(process.env.GITHUB_WORKSPACE, '.github', 'pr-webhook-utils.cjs');
            console.log(`Loading PR webhook utilities from: ${utilsPath}`);
            
            // Load the utilities from the external file
            const prDataUtils = require(utilsPath);
            
            // Build PR data payload
            const prData = {
              id: pr.data.id,
              number: pr.data.number,
              title: prDataUtils.sanitizeText(pr.data.title),
              body: prDataUtils.sanitizeText(pr.data.body),
              state: pr.data.state,
              created_at: pr.data.created_at,
              updated_at: pr.data.updated_at,
              repository: {
                name: context.repo.repo,
                owner: context.repo.owner
              },
              head: {
                ref: pr.data.head.ref,
                sha: pr.data.head.sha
              },
              base: {
                ref: pr.data.base.ref,
                sha: pr.data.base.sha
              },
              user: {
                login: pr.data.user.login,
                id: pr.data.user.id
              },
              // Filter sensitive files and limit payload size
              changed_files: files.data
                .filter(file => prDataUtils.shouldIncludeFile(file.filename))
                .slice(0, 100) // Limit to 100 files max
                .map(file => ({
                  filename: file.filename,
                  status: file.status,
                  additions: file.additions,
                  deletions: file.deletions,
                  changes: file.changes,
                  patch: prDataUtils.limitPatch(file.patch)
                })),
              // Sanitize comments
              comments: comments.data
                .slice(0, 100) // Limit to 100 comments max
                .map(comment => ({
                  id: comment.id,
                  body: prDataUtils.sanitizeText(comment.body),
                  user: comment.user.login,
                  created_at: comment.created_at
                })),
              // Sanitize review comments
              review_comments: reviewComments.data
                .slice(0, 100) // Limit to 100 review comments max
                .map(comment => ({
                  id: comment.id,
                  body: prDataUtils.sanitizeText(comment.body),
                  user: comment.user.login,
                  path: comment.path,
                  position: comment.position,
                  created_at: comment.created_at
                }))
            };
            
            console.log('Sending PR data to webhook...');
            
            // Calculate payload size for logging
            const payloadSize = JSON.stringify(prData).length;
            console.log(`Payload size: ${(payloadSize / 1024).toFixed(2)} KB`);
            
            // Fail if payload is too large (>5MB)
            const maxPayloadSize = 5 * 1024 * 1024;
            if (payloadSize > maxPayloadSize) {
              throw new Error(`Payload size (${payloadSize} bytes) exceeds maximum allowed size (${maxPayloadSize} bytes)`);
            }
            
            // Use https request
            const https = require('https');
            
            // Properly stringify and send the data using safe stringify utility
            const stringifyResult = prDataUtils.safeStringify(prData);
            
            if (!stringifyResult.success) {
              console.error(`JSON stringify error: ${stringifyResult.error}`);
              
              // Use the simplified data creator utility
              const simplifiedData = prDataUtils.createSimplifiedPrData(pr, context);
              
              // Try to stringify the simplified data
              const simplifiedResult = prDataUtils.safeStringify(simplifiedData);
              
              if (!simplifiedResult.success) {
                // Last resort - send minimal JSON
                console.error(`Even simplified data failed: ${simplifiedResult.error}`);
                stringifyResult.data = JSON.stringify({ error: "Failed to process PR data", pr_number: context.issue.number });
              } else {
                console.log('Using simplified PR data instead');
                stringifyResult.data = simplifiedResult.data;
              }
            } else {
              console.log('JSON data prepared successfully');
            }
            
            // Log payload size instead of full content for security
            console.log(`Payload prepared successfully: ${(stringifyResult.data.length / 1024).toFixed(2)} KB`);
            
            const options = {
              hostname: url.hostname,
              port: url.port || 443,
              path: url.pathname,
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(stringifyResult.data),
                'CF-Access-Client-Id': '${{ secrets.CF_ACCESS_CLIENT_ID }}',
                'CF-Access-Client-Secret': '${{ secrets.CF_ACCESS_CLIENT_SECRET }}'
              },
              timeout: 10000 // 10 second timeout
            };
            
            // Make the request
            const req = https.request(options, (res) => {
              let data = '';
              res.on('data', (chunk) => { data += chunk; });
              
              res.on('end', () => {
                if (res.statusCode >= 200 && res.statusCode < 300) {
                  console.log(`Successfully sent PR data to webhook (Status: ${res.statusCode})`);
                } else {
                  const errorMsg = `Failed to send PR data to webhook: Status ${res.statusCode}`;
                  console.error(errorMsg);
                  console.error(`Response: ${data}`);
                  // Fail the job if the webhook returns an error
                  core.setFailed(errorMsg);
                }
              });
            });
            
            req.on('error', (error) => {
              const errorMsg = `Network error when sending to webhook: ${error.message}`;
              console.error(errorMsg);
              core.setFailed(errorMsg);
            });
            
            req.on('timeout', () => {
              req.destroy();
              const errorMsg = 'Request to webhook timed out after 10 seconds';
              console.error(errorMsg);
              core.setFailed(errorMsg);
            });
            
            req.write(stringifyResult.data);
            req.end();
            
          } catch (error) {
            console.error(`Failed to process PR data: ${error.message}`);
            core.setFailed(`PR review webhook error: ${error.message}`);
          }
