const puppeteer = require('puppeteer');
const { spawn, execSync } = require('child_process');
const { setTimeout: sleep } = require('node:timers/promises');
const http = require('http');

const CONFIG = {
  PORT: 3000,
  BROWSER_TIMEOUT: 300000,    // Increased to 5m for browser operations
  SERVER_TIMEOUT: 300000,     // Increased to 5m for server to start
  POLL_INTERVAL: 100,         // Check server every 100ms
  PROCESS_KILL_WAIT: 10000,    // 10s to wait after killing process
  REQUEST_TIMEOUT: 10000,      // Increased to 10s for individual HTTP requests
  PAGE_CLOSE_TIMEOUT: 5000,    // 5s timeout for page closing
  VIEWPORT: { width: 1280, height: 800 }
};

// Global timeout to prevent hanging
const globalTimeout = setTimeout(() => {
  console.error('Test timed out after 5 minutes');
  process.exit(1);
}, 300000);

async function setupAssets() {
  console.log('Setting up assets...');
  execSync('node test/setup-standalone-editor-assets.js', { stdio: 'inherit' });
  console.log('Assets setup complete');
}

function checkServer() {
  return new Promise((resolve) => {
    const req = http.get(`http://localhost:${CONFIG.PORT}`, (res) => {
      res.destroy();
      resolve(res.statusCode === 200);
    });
    
    req.on('error', () => {
      resolve(false);
    });
    
    req.setTimeout(CONFIG.REQUEST_TIMEOUT, () => {
      req.destroy();
      resolve(false);
    });
  });
}

async function waitForServer(server, timeout) {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    console.log('Checking if server is ready...');
    if (await checkServer()) {
      console.log('Server is ready!');
      return true;
    }
    await sleep(CONFIG.POLL_INTERVAL);
  }
  
  return false;
}

async function startServer() {
  console.log('Starting server...');
  const server = spawn('node', ['test/start-standalone-server.js', '--no-open'], { stdio: 'inherit' });

  return new Promise((resolve, reject) => {
    server.on('error', (error) => {
      console.error('Server error:', error);
      reject(error);
    });

    server.on('exit', (code) => {
      if (code !== null && code !== 0) {
        console.error('Server exited with code:', code);
        reject(new Error(`Server process exited with code ${code}`));
      }
    });

    // Wait for the server to be ready
    waitForServer(server, CONFIG.SERVER_TIMEOUT)
      .then((isReady) => {
        if (isReady) {
          resolve(server);
        } else {
          reject(new Error(`Server failed to respond within ${CONFIG.SERVER_TIMEOUT}ms`));
        }
      })
      .catch((error) => {
        reject(error);
      });
  });
}

async function runTest() {
  let server;
  let browser;
  const testFiles = [
    { path: 'test-bs5-anon.html', name: 'Anonymous Editor' },
    { path: 'test-bs5-editor.html', name: 'Full Editor' }
  ];

  let testError = null;
  try {
    await setupAssets();
    server = await startServer();

    // Launch browser once for all tests
    console.log('Launching browser...');
    browser = await puppeteer.launch({
      headless: !process.env.HEADED,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-accelerated-2d-canvas',
        '--disable-gpu'
      ],
      protocolTimeout: 180000,
      timeout: 180000
    });
    console.log('Browser launched');

    for (const testFile of testFiles) {
      console.log(`\nTesting ${testFile.name}...`);
      
      const page = await browser.newPage();
      console.log('Page created');
      
      // Set default navigation timeout
      page.setDefaultNavigationTimeout(120000);
      
      await page.setViewport(CONFIG.VIEWPORT);
      console.log('Viewport set');
      let hasErrors = false;
      let testSuccess = false;
      
      // Add error handler for JavaScript errors with full stack trace
      page.on('pageerror', (error) => {
        console.error(`JavaScript error in ${testFile.name}:`);
        console.error('Message:', error.message);
        console.error('Stack:', error.stack || 'No stack trace available');
        console.error('Full error:', error);
        hasErrors = true;
      });
      
      // Enable request interception
      await page.setRequestInterception(true);
      
      // Handle request interception
      page.on('request', request => {
        request.continue();
      });
      
      console.log('Request interception enabled');
      
      // Add timeout for the entire test
      const testTimeout = setTimeout(() => {
        console.error(`Test timeout reached for ${testFile.name}`);
        hasErrors = true;
      }, CONFIG.BROWSER_TIMEOUT);
      
      try {
        console.log(`Navigating to ${testFile.path}...`);
        await page.goto(`http://localhost:${CONFIG.PORT}/standalone-editor/${testFile.path}`, {
          waitUntil: 'domcontentloaded',
          timeout: CONFIG.BROWSER_TIMEOUT
        });
        console.log('Page loaded');
        
        console.log('Waiting for network idle...');
        await page.waitForNetworkIdle({ 
          timeout: 120000,
          idleTime: 1000
        });
        console.log('Network is idle');
        
        console.log('Waiting for body...');
        await page.waitForSelector('body', { timeout: CONFIG.BROWSER_TIMEOUT });
        console.log('Body found');
        
        // Only mark as success if we haven't encountered any errors so far
        if (!hasErrors) {
          // Test button interactions only for Full Editor (not Anonymous Editor)
          if (testFile.name === 'Full Editor') {
            console.log('Testing button interactions...');
            // Wait for and click the Save container button
            await page.waitForSelector('.control.save-container', { timeout: 10000 });
            console.log('Save container button found');
            
            // Set up request interception to capture the save request
            let saveRequestPayload = null;
            let htmlContentSize = 0;
            
            // Listen for the specific save request
            page.on('request', (request) => {
              const url = request.url();
              const method = request.method();
              const postData = request.postData();
              
              // Check if this is the save container request
              if (method === 'POST' && postData && postData.includes('action=dxpr_save_container')) {
                console.log('📡 Captured dxpr_save_container request');
                saveRequestPayload = postData;
                
                // Extract htmlContent size
                const htmlContentMatch = postData.match(/htmlContent=([^&]*)/);
                if (htmlContentMatch) {
                  const encodedHtmlContent = htmlContentMatch[1];
                  const decodedHtmlContent = decodeURIComponent(encodedHtmlContent);
                  htmlContentSize = decodedHtmlContent.length;
                  console.log(`📏 htmlContent size: ${htmlContentSize} characters`);
                  console.log(`📏 htmlContent size: ${(htmlContentSize / 1024).toFixed(2)} KB`);
                }
              }
            });
            
            await page.click('.control.save-container');
            console.log('✓ Save container button clicked successfully');
            
            // Wait for the save request to complete
            await sleep(2000);
            
            // Validate htmlContent size
            if (htmlContentSize > 0) {
              // REFERENCE SIZE - Based on baseline measurement of current HTML output
              const REFERENCE_SIZE = 52900; // 63.67 KB baseline (measured from current output)
              
              console.log(`📊 Reference size: ${REFERENCE_SIZE} characters (${(REFERENCE_SIZE / 1024).toFixed(2)} KB)`);
              console.log(`📊 Current size: ${htmlContentSize} characters (${(htmlContentSize / 1024).toFixed(2)} KB)`);
              
              if (htmlContentSize > REFERENCE_SIZE) {
                const increasePercent = ((htmlContentSize - REFERENCE_SIZE) / REFERENCE_SIZE * 100).toFixed(1);
                throw new Error(
                  `❌ HTML BLOAT DETECTED! ` +
                  `htmlContent size (${htmlContentSize} chars) exceeds reference size (${REFERENCE_SIZE} chars). ` +
                  `This is a ${increasePercent}% increase from the baseline. ` +
                  `NO INCREASES ARE ALLOWED - only same size or smaller. Please optimize the HTML output.`
                );
              } else {
                const changePercent = ((htmlContentSize - REFERENCE_SIZE) / REFERENCE_SIZE * 100).toFixed(1);
                const changeIndicator = htmlContentSize > REFERENCE_SIZE ? '📈' : htmlContentSize < REFERENCE_SIZE ? '📉' : '🟰';
                const changeMessage = htmlContentSize === REFERENCE_SIZE ? 'Same size' : `${changePercent}% change from reference`;
                console.log(`✅ HTML size check passed! ${changeIndicator} ${changeMessage}`);
              }
            } else {
              console.log('⚠️  Warning: Could not measure htmlContent size (request may not have fired)');
            }
          } else {
            console.log('Skipping button interactions for Anonymous Editor (no save functionality)');
          }
          
          console.log(`✓ ${testFile.name} test completed successfully`);
          testSuccess = true;
        } else {
          throw new Error(`${testFile.name} failed due to JavaScript errors`);
        }
      } catch (error) {
        console.error(`✗ ${testFile.name} test failed:`, error.message);
        throw error;
      } finally {
        clearTimeout(testTimeout);
        
        // Close page with timeout
        console.log(`Closing page for ${testFile.name}...`);
        try {
          await Promise.race([
            page.close(),
            sleep(CONFIG.PAGE_CLOSE_TIMEOUT).then(() => {
              throw new Error('Page close timeout');
            })
          ]);
          console.log(`Page closed for ${testFile.name}`);
        } catch (closeError) {
          if (closeError.message === 'Page close timeout') {
            console.warn(`Warning: Page close timed out for ${testFile.name}, but test ${testSuccess ? 'succeeded' : 'failed'}`);
          } else {
            console.error(`Error closing page for ${testFile.name}:`, closeError);
            if (testSuccess) {
              console.log('Test was successful despite page close error');
            }
          }
        }
      }
    }

  } catch (error) {
    testError = error;
    console.error('Test suite failed:', error.message);
    if (error.stack) {
      console.error('Stack trace:', error.stack);
    }
  } finally {
    try {
      if (browser) {
        await Promise.race([
          browser.close(),
          sleep(CONFIG.PAGE_CLOSE_TIMEOUT)
        ]);
      }
    } catch (closeError) {
      console.warn('Warning: Browser close timed out or failed');
    }
    // Exit with appropriate code based on test results, not closing status
    process.exit(testError ? 1 : 0);
  }
}

console.log('Starting test...');
runTest(); 
