/**
 * URL rewriting utilities for proxying resources through the server
 */

import { createLogger } from './logger';

const logger = createLogger('URLRewriter');

/**
 * Decode HTML entities in a string
 */
function decodeHtmlEntities(text: string): string {
  const entities: { [key: string]: string } = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#39;': "'",
    '&apos;': "'",
  };

  return text.replace(/&[#\w]+;/g, (entity) => {
    return entities[entity] || entity;
  });
}

/**
 * Rewrite URLs in HTML to proxy through our server
 */
export function rewriteResourceUrls(html: string, baseUrl: string, sessionId: string, token: string): string {
  logger.info('Rewriting HTML URLs', {
    baseUrl,
    sessionId,
    htmlLength: html.length,
  });

  // For server-side rendering, we need to use the full API URL
  // This defaults to localhost:3000 which is where the Express API runs
  const apiOrigin = process.env.API_PUBLIC_URL || 'http://localhost:3000';

  // Create absolute URL helper
  const makeAbsolute = (url: string): string => {
    try {
      // Decode HTML entities first (e.g., &amp; -> &)
      const decodedUrl = decodeHtmlEntities(url);
      return new URL(decodedUrl, baseUrl).href;
    } catch {
      return url;
    }
  };

  // Create proxy URL with token for authentication
  // Use absolute URL to ensure requests go to the API server, not the frontend
  // Token is now in the path to prevent URL corruption when relative paths are appended
  const makeProxyUrl = (url: string): string => {
    const absoluteUrl = makeAbsolute(url);
    // Encode the URL and include token in the path (not query parameter)
    return `${apiOrigin}/api/proxy/session/${sessionId}/${encodeURIComponent(token)}/resource?url=${encodeURIComponent(absoluteUrl)}`;
  };

  let rewritten = html;

  // Remove any existing base tags to prevent conflicts with our base tag
  rewritten = rewritten.replace(
    /<base[^>]*>/gi,
    '<!-- original base tag removed by proxy -->'
  );

  // Remove or relax Content Security Policy that might block our inline scripts
  rewritten = rewritten.replace(
    /<meta[^>]*http-equiv=["']Content-Security-Policy["'][^>]*>/gi,
    '<!-- CSP removed by proxy -->'
  );

  // Remove CSP headers from meta tags
  rewritten = rewritten.replace(
    /<meta[^>]*content=["'][^"']*script-src[^"']*["'][^>]*>/gi,
    '<!-- CSP script-src removed by proxy -->'
  );

  // Inject scripts to intercept dynamic resource loading AND capture interactions
  // CRITICAL: This script MUST run before any other scripts to intercept all requests
  const interceptScript = `<script>
(function() {
  console.log('[Proxy Intercept] Initializing...');
  console.log('[Proxy Intercept] Expected baseUrl:', ${JSON.stringify(baseUrl)});
  console.log('[Proxy Intercept] document.baseURI:', document.baseURI);
  console.log('[Proxy Intercept] document.URL:', document.URL);
  console.log('[Proxy Intercept] window.location.href:', window.location.href);

  const originalCreateElement = document.createElement;
  const apiOrigin = ${JSON.stringify(apiOrigin)};
  const sessionId = ${JSON.stringify(sessionId)};
  const token = ${JSON.stringify(token)};
  const baseUrl = ${JSON.stringify(baseUrl)};

  // Verify base tag is working
  if (document.baseURI !== baseUrl) {
    console.warn('[Proxy Intercept] WARNING: Base tag not working correctly!', {
      expected: baseUrl,
      actual: document.baseURI
    });
  }

  // Function to make proxy URLs
  function makeProxyUrl(url) {
    try {
      let absoluteUrl;

      // For absolute paths starting with /, resolve against the base URL's origin
      // This handles cases where scripts from different origins try to load resources
      if (url.startsWith('/')) {
        // Absolute path - resolve against the base URL's origin
        const baseUrlObj = new URL(baseUrl);
        absoluteUrl = baseUrlObj.origin + url;
        console.log('[Proxy] Resolved absolute path:', url, '->', absoluteUrl);
      } else {
        // Relative path - resolve against the base URL
        absoluteUrl = new URL(url, baseUrl).href;
      }

      // Detect if URL was incorrectly resolved (contains /api/proxy/ in path)
      if (absoluteUrl.includes('/api/proxy/') && !absoluteUrl.includes('/api/proxy/session/')) {
        console.error('[Proxy] WARNING: URL appears to be incorrectly resolved!', {
          originalUrl: url,
          resolvedUrl: absoluteUrl,
          baseUrl: baseUrl,
          documentBaseURI: document.baseURI,
          documentURL: document.URL
        });
      }

      // Return proxy URL with token in path (prevents URL corruption)
      return apiOrigin + '/api/proxy/session/' + sessionId + '/' + encodeURIComponent(token) + '/resource?url=' + encodeURIComponent(absoluteUrl);
    } catch (e) {
      console.error('[Proxy] makeProxyUrl error:', e, 'url:', url, 'baseUrl:', baseUrl);
      return url;
    }
  }

  // Intercept fetch() calls to proxy them
  const originalFetch = window.fetch;
  window.fetch = function(resource, options) {
    let url = typeof resource === 'string' ? resource : resource.url;

    // Skip data: and blob: URLs, also skip if already proxied
    if (url && !url.startsWith('data:') && !url.startsWith('blob:') && !url.includes('/api/proxy/session/')) {
      try {
        // Resolve URL to absolute (handles relative URLs via base tag)
        const absoluteUrl = new URL(url, baseUrl).href;

        // Proxy ALL http/https URLs (external resources)
        if (absoluteUrl.startsWith('http://') || absoluteUrl.startsWith('https://')) {
          const proxiedUrl = makeProxyUrl(absoluteUrl);

          if (typeof resource === 'string') {
            resource = proxiedUrl;
          } else {
            resource = new Request(proxiedUrl, resource);
          }

          console.log('[Proxy] fetch intercepted:', absoluteUrl, '->', proxiedUrl);
        }
      } catch (e) {
        console.error('[Proxy] Failed to intercept fetch:', url, e);
      }
    }

    return originalFetch.call(window, resource, options);
  };
  console.log('[Proxy Intercept] fetch() interceptor installed');

  // Intercept XMLHttpRequest
  const originalXHROpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function(method, url, ...rest) {
    if (typeof url === 'string' && !url.startsWith('data:') && !url.startsWith('blob:') && !url.includes('/api/proxy/session/')) {
      try {
        // Resolve URL to absolute (handles relative URLs via base tag)
        const absoluteUrl = new URL(url, baseUrl).href;

        // Proxy ALL http/https URLs (external resources)
        if (absoluteUrl.startsWith('http://') || absoluteUrl.startsWith('https://')) {
          url = makeProxyUrl(absoluteUrl);
          console.log('[Proxy] XHR intercepted:', absoluteUrl, '->', url);
        }
      } catch (e) {
        console.error('[Proxy] Failed to intercept XHR:', url, e);
      }
    }

    return originalXHROpen.call(this, method, url, ...rest);
  };
  console.log('[Proxy Intercept] XMLHttpRequest interceptor installed');

  // Intercept dynamic import() calls (for ES modules)
  // This is critical because import() resolves paths relative to the module URL, not the document base
  if (window.importShim) {
    console.log('[Proxy Intercept] Note: importShim detected, may need additional configuration');
  }

  // Override document.createElement to intercept dynamic script/link loading
  document.createElement = function(tagName) {
    const element = originalCreateElement.call(document, tagName);
    const tag = tagName.toLowerCase();

    if (tag === 'script' || tag === 'link' || tag === 'img') {
      const originalSetAttribute = element.setAttribute;
      element.setAttribute = function(name, value) {
        // Intercept src (script, img) and href (link) attributes
        if ((name === 'src' || name === 'href') && value && !value.startsWith('data:') && !value.startsWith('blob:') && !value.includes('/api/proxy/session/')) {
          try {
            const absoluteUrl = new URL(value, baseUrl).href;
            if (absoluteUrl.startsWith('http://') || absoluteUrl.startsWith('https://')) {
              value = makeProxyUrl(absoluteUrl);
              console.debug('[Proxy] createElement ' + tag + '.' + name + ':', absoluteUrl, '->', value);
            }
          } catch (e) {
            // If URL parsing fails, use original value
          }
        }
        return originalSetAttribute.call(this, name, value);
      };

      // Also intercept direct src/href property assignment (only if configurable)
      try {
        const propertyName = tag === 'link' ? 'href' : 'src';
        const descriptor = Object.getOwnPropertyDescriptor(element, propertyName);
        if (!descriptor || descriptor.configurable !== false) {
          Object.defineProperty(element, propertyName, {
            set: function(value) {
              if (value && !value.startsWith('data:') && !value.startsWith('blob:') && !value.includes('/api/proxy/session/')) {
                try {
                  const absoluteUrl = new URL(value, baseUrl).href;
                  if (absoluteUrl.startsWith('http://') || absoluteUrl.startsWith('https://')) {
                    this.setAttribute(propertyName, makeProxyUrl(absoluteUrl));
                    return;
                  }
                } catch (e) {
                  // If URL parsing fails, use original value
                }
              }
              this.setAttribute(propertyName, value);
            },
            get: function() {
              return this.getAttribute(propertyName);
            },
            configurable: true
          });
        }
      } catch (e) {
        // If we can't redefine the property, setAttribute interception will still work
        console.debug('Could not redefine ' + tag + ' property, using setAttribute interception only');
      }
    }

    return element;
  };

  // Capture user interactions and send to parent window
  function sendInteraction(interactionData) {
    // Don't spread to avoid overwriting the 'type' field
    const message = {
      type: 'proxyInteraction',
      interactionData: interactionData
    };
    console.log('[Proxy Intercept] Sending interaction:', message);
    window.parent.postMessage(message, '*');
  }

  // Wait for DOM to be ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', setupInteractionCapture);
  } else {
    setupInteractionCapture();
  }

  function setupInteractionCapture() {
    // Handle link clicks
    document.addEventListener('click', function(e) {
      const target = e.target;
      const link = target.closest('a');

      if (link) {
        e.preventDefault();
        e.stopPropagation();

        const href = link.getAttribute('href') || link.getAttribute('data-original-href');

        if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
          let actualUrl = href;

          // If href contains our proxy URL structure, extract the original URL
          if (href.includes('/api/proxy/session/') && href.includes('?url=')) {
            try {
              const urlParams = new URLSearchParams(href.split('?')[1]);
              actualUrl = decodeURIComponent(urlParams.get('url') || href);
            } catch (err) {
              actualUrl = href;
            }
          }

          sendInteraction({
            type: 'preview_navigate',
            url: actualUrl
          });
        }
        return;
      }

      // For non-link clicks, send click coordinates
      sendInteraction({
        type: 'preview_click',
        x: e.clientX,
        y: e.clientY,
        viewportWidth: window.innerWidth,
        viewportHeight: window.innerHeight
      });
    }, true);

    // Capture scroll events
    document.addEventListener('scroll', function() {
      sendInteraction({
        type: 'preview_scroll',
        scrollX: document.documentElement.scrollLeft || document.body.scrollLeft,
        scrollY: document.documentElement.scrollTop || document.body.scrollTop
      });
    });

    // Capture keyboard input
    document.addEventListener('keydown', function(e) {
      sendInteraction({
        type: 'preview_keyboard',
        key: e.key
      });
    });

    // Prevent form submissions
    document.addEventListener('submit', function(e) {
      e.preventDefault();
      console.log('Form submission blocked');
    }, true);
  }

  console.log('[Proxy Intercept] All interceptors ready! Base URL:', baseUrl);
})();
</script>
`;

  // Insert base tag and intercept script right after <head> opening tag
  // The base tag ensures relative URLs resolve against the actual page URL, not the proxy URL
  const baseTag = `<base href="${baseUrl}">`;

  rewritten = rewritten.replace(
    /(<head[^>]*>)/i,
    `$1\n${baseTag}\n${interceptScript}`
  );

  // Rewrite script src attributes (handle both with and without space before src)
  // Add 'defer' to ensure they load after our intercept script
  rewritten = rewritten.replace(
    /<script([^>]*)src=["']([^"']+)["']/gi,
    (match, attrs, url) => {
      if (url.startsWith('data:') || url.startsWith('blob:')) return match;

      // Add defer if not already present (unless it has async)
      const hasDefer = /\bdefer\b/i.test(attrs);
      const hasAsync = /\basync\b/i.test(attrs);
      const deferAttr = (!hasDefer && !hasAsync) ? ' defer' : '';

      return `<script${attrs}${deferAttr} src="${makeProxyUrl(url)}"`;
    }
  );

  // Rewrite link href attributes (CSS, icons, etc.)
  rewritten = rewritten.replace(
    /<link([^>]*)\shref=["']([^"']+)["']/gi,
    (match, attrs, url) => {
      if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('#')) return match;
      return `<link${attrs} href="${makeProxyUrl(url)}"`;
    }
  );

  // Rewrite img src attributes
  rewritten = rewritten.replace(
    /<img([^>]*)\ssrc=["']([^"']+)["']/gi,
    (match, attrs, url) => {
      if (url.startsWith('data:') || url.startsWith('blob:')) return match;
      return `<img${attrs} src="${makeProxyUrl(url)}"`;
    }
  );

  // Rewrite img srcset attributes
  rewritten = rewritten.replace(
    /<img([^>]*)\ssrcset=["']([^"']+)["']/gi,
    (_match, attrs, srcset) => {
      const rewrittenSrcset = srcset
        .split(',')
        .map((entry: string) => {
          const [url, descriptor] = entry.trim().split(/\s+/);
          if (url.startsWith('data:') || url.startsWith('blob:')) return entry;
          return `${makeProxyUrl(url)} ${descriptor || ''}`.trim();
        })
        .join(', ');
      return `<img${attrs} srcset="${rewrittenSrcset}"`;
    }
  );

  // Rewrite inline style background images
  rewritten = rewritten.replace(
    /style=["']([^"']*)url\(["']?([^"')]+)["']?\)/gi,
    (match, stylePrefix, url) => {
      if (url.startsWith('data:') || url.startsWith('blob:')) return match;
      return `style="${stylePrefix}url('${makeProxyUrl(url)}')`;
    }
  );

  // Rewrite meta tags (og:image, twitter:image, etc.)
  rewritten = rewritten.replace(
    /<meta([^>]*)\scontent=["']([^"']+)["']/gi,
    (match, attrs, content) => {
      // Only rewrite if it looks like a URL
      if (content.startsWith('http://') || content.startsWith('https://') || content.startsWith('//')) {
        return `<meta${attrs} content="${makeProxyUrl(content)}"`;
      }
      return match;
    }
  );

  // Store original href in data-original-href for anchor tags so we can extract them
  rewritten = rewritten.replace(
    /<a([^>]*)\shref=["']([^"']+)["']/gi,
    (match, attrs, href) => {
      // Skip javascript:, data:, blob:, and # links
      if (href.startsWith('javascript:') || href.startsWith('data:') ||
          href.startsWith('blob:') || href.startsWith('#')) {
        return match;
      }

      // Store original href in data attribute for our click handler to use
      const absoluteHref = makeAbsolute(href);
      return `<a${attrs} href="${href}" data-original-href="${absoluteHref}"`;
    }
  );

  logger.info('URL rewriting completed', {
    sessionId,
    originalLength: html.length,
    rewrittenLength: rewritten.length,
    hasBaseTag: rewritten.includes('<base'),
    hasInterceptScript: rewritten.includes('[Proxy Intercept]'),
  });

  return rewritten;
}

/**
 * Rewrite URLs in CSS to proxy through our server
 */
export function rewriteCssUrls(css: string, baseUrl: string, sessionId: string, token: string): string {
  // Create absolute URL helper
  const makeAbsolute = (url: string): string => {
    try {
      // Decode HTML entities first (e.g., &amp; -> &)
      const decodedUrl = decodeHtmlEntities(url);
      return new URL(decodedUrl, baseUrl).href;
    } catch {
      return url;
    }
  };

  // Create proxy URL with token for authentication
  // Token is now in the path to prevent URL corruption
  const makeProxyUrl = (url: string): string => {
    const absoluteUrl = makeAbsolute(url);
    // Use the same API origin as in HTML rewriting
    const apiOrigin = process.env.API_PUBLIC_URL || 'http://localhost:3000';
    return `${apiOrigin}/api/proxy/session/${sessionId}/${encodeURIComponent(token)}/resource?url=${encodeURIComponent(absoluteUrl)}`;
  };

  // Rewrite url() references in CSS
  return css.replace(
    /url\(["']?([^"')]+)["']?\)/gi,
    (match, url) => {
      // Skip data URLs and already proxied URLs
      if (url.startsWith('data:') || url.startsWith('blob:') || url.includes('/api/proxy/')) {
        return match;
      }
      return `url('${makeProxyUrl(url)}')`;
    }
  );
}
