import DOMPurify from 'dompurify';
import { configApi } from './api';
import { debug } from './debug';

interface SanitizationConfig {
  allowedTags: string[];
  allowedAttributes: Record<string, string[]>;
  allowedCssProperties: string[];
  removedAttributes?: Record<string, string[]>;
  allowedProtocols?: Record<string, Record<string, string[]>>;
  description: string;
}

let cachedConfig: SanitizationConfig | null = null;
let configPromise: Promise<SanitizationConfig> | null = null;

/**
 * Filter CSS properties in a style attribute value
 */
function filterCssProperties(styleValue: string, allowedProperties: string[]): string {
  // Parse CSS declarations
  const declarations = styleValue.split(';').map(decl => decl.trim()).filter(decl => decl);
  
  const filteredDeclarations = declarations.filter(declaration => {
    const colonIndex = declaration.indexOf(':');
    if (colonIndex === -1) return false;
    
    const property = declaration.substring(0, colonIndex).trim().toLowerCase();
    const isAllowed = allowedProperties.includes(property);
    
    if (!isAllowed) {
      debug.log(`CSS property "${property}" was filtered out (not in allowed list)`);
    }
    
    return isAllowed;
  });
  
  const result = filteredDeclarations.join('; ');
  
  if (declarations.length !== filteredDeclarations.length) {
    debug.log(`CSS filtering: ${declarations.length} -> ${filteredDeclarations.length} properties`);
    debug.log('Original:', styleValue);
    debug.log('Filtered:', result);
  }
  
  return result;
}

/**
 * Fetch sanitization configuration from backend
 */
async function fetchSanitizationConfig(): Promise<SanitizationConfig> {
  if (cachedConfig) {
    return cachedConfig;
  }

  if (configPromise) {
    return configPromise;
  }

  configPromise = configApi.getHtmlSanitizationConfig()
    .then(config => {
      cachedConfig = config;
      configPromise = null;
      return config;
    })
    .catch(error => {
      console.error('Failed to fetch sanitization config, using fallback:', error);
      configPromise = null;
      // Return fallback configuration
      const fallbackConfig: SanitizationConfig = {
        allowedTags: [
          'p', 'br', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
          'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del', 'ins',
          'sup', 'sub', 'small', 'big', 'mark', 'pre', 'code',
          'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'a', 'img',
          'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
          'blockquote', 'cite', 'q', 'hr'
        ],
        allowedAttributes: {
          'p': ['class', 'style'],
          'div': ['class', 'style'],
          'span': ['class', 'style'],
          'h1': ['class', 'style'],
          'h2': ['class', 'style'],
          'h3': ['class', 'style'],
          'h4': ['class', 'style'],
          'h5': ['class', 'style'],
          'h6': ['class', 'style'],
          'a': ['class'],
          'img': ['src', 'alt', 'width', 'height', 'class', 'style'],
          'table': ['class'],
          'td': ['class', 'colspan', 'rowspan'],
          'th': ['class', 'colspan', 'rowspan']
        },
        allowedCssProperties: [
          'color', 'background-color', 'font-size', 'font-weight', 
          'font-style', 'text-align', 'text-decoration', 'margin', 
          'padding', 'text-indent', 'line-height'
        ],
        allowedProtocols: {
          'a': {
            'href': ['http', 'https', '#', '/']
          },
          'img': {
            'src': ['http', 'https', 'data', '/']
          }
        },
        description: 'Fallback sanitization configuration'
      };
      cachedConfig = fallbackConfig;
      return fallbackConfig;
    });

  return configPromise;
}

/**
 * Create DOMPurify configuration from backend sanitization config
 */
function createDOMPurifyConfig(config: SanitizationConfig) {
  const allowedTags = config.allowedTags;
  const allowedAttributes: Record<string, string[]> = { ...config.allowedAttributes };
  
  // Remove attributes that should be stripped (deprecated, keeping for backward compatibility)
  if (config.removedAttributes) {
    Object.keys(config.removedAttributes).forEach(tag => {
      const attributesToRemove = config.removedAttributes![tag];
      if (allowedAttributes[tag]) {
        allowedAttributes[tag] = allowedAttributes[tag].filter(
          attr => !attributesToRemove.includes(attr)
        );
      }
    });
  }

  // Create a proper DOMPurify configuration
  // DOMPurify expects ALLOWED_ATTR to be an array of allowed attributes
  // We need to flatten the tag-specific attributes into a global list
  const flattenedAttributes = Object.values(allowedAttributes).flat();
  const uniqueAttributes = Array.from(new Set(flattenedAttributes));
  
  // Configure allowed protocols for URL validation
  const allowedSchemes: string[] = [];
  if (config.allowedProtocols) {
    Object.values(config.allowedProtocols).forEach(attributeProtocols => {
      Object.values(attributeProtocols).forEach(protocols => {
        allowedSchemes.push(...protocols);
      });
    });
  }

  const domPurifyConfig: DOMPurify.Config = {
    ALLOWED_TAGS: allowedTags,
    ALLOWED_ATTR: uniqueAttributes,
    // More permissive URL regex to allow complex query strings and tokens
    ALLOWED_URI_REGEXP: /^(?:(?:https?|data|#|\/):)?[\s\S]*$/i,
    ALLOW_UNKNOWN_PROTOCOLS: false,
    SANITIZE_DOM: true,
    KEEP_CONTENT: true,
    ALLOW_DATA_ATTR: false,
  };

  // Clear any existing hooks and add CSS property filtering
  DOMPurify.removeAllHooks();
  DOMPurify.addHook('afterSanitizeAttributes', function (node) {
    // Only process elements with style attributes
    if (node.hasAttribute && node.hasAttribute('style')) {
      const styleValue = node.getAttribute('style');
      if (styleValue) {
        const filteredStyle = filterCssProperties(styleValue, config.allowedCssProperties);
        if (filteredStyle) {
          node.setAttribute('style', filteredStyle);
        } else {
          node.removeAttribute('style');
        }
      }
    }
  });

  return domPurifyConfig;
}

/**
 * Preprocess HTML to extract images from figure tags before sanitization
 */
function preprocessFigureTags(html: string): string {
  if (!html || html.trim() === '') {
    return html;
  }

  try {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const figures = doc.querySelectorAll('figure');

    figures.forEach((figure) => {
      // Find img tags anywhere within the figure (deep search)
      const images = figure.querySelectorAll('img');

      if (images.length > 0) {
        // Extract the first image
        const img = images[0];

        // Get the src attribute - it might be in the src attribute or data-src
        const imgSrc = img.getAttribute('src') || img.getAttribute('data-src') || img.src || '';

        if (!imgSrc || imgSrc.trim() === '') {
          figure.remove();
          return;
        }

        // Create a clean img element with just the essential attributes
        const cleanImg = doc.createElement('img');
        cleanImg.setAttribute('src', imgSrc);

        // Preserve alt text
        const existingAlt = img.getAttribute('alt') || img.alt;
        if (existingAlt) {
          cleanImg.setAttribute('alt', existingAlt);
        } else {
          // Check if there's a figcaption to use as alt text
          const figcaption = figure.querySelector('figcaption');
          if (figcaption) {
            const captionText = figcaption.textContent?.trim();
            if (captionText) {
              cleanImg.setAttribute('alt', captionText);
            }
          }
        }

        // Preserve other useful attributes if they exist
        const width = img.getAttribute('width') || img.width;
        const height = img.getAttribute('height') || img.height;
        if (width) cleanImg.setAttribute('width', width.toString());
        if (height) cleanImg.setAttribute('height', height.toString());

        // Replace the figure element with just the clean img
        figure.replaceWith(cleanImg);
      } else {
        // No images in figure, remove it entirely
        figure.remove();
      }
    });

    return doc.body.innerHTML;
  } catch (error) {
    console.warn('Failed to preprocess figure tags, returning original HTML:', error);
    return html;
  }
}

/**
 * Sanitize HTML content using shared configuration from backend
 */
export async function sanitizeHtml(html: string): Promise<string> {
  if (!html || html.trim() === '') {
    return '';
  }

  try {
    // Preprocess to extract images from figure tags
    const preprocessed = preprocessFigureTags(html);

    const config = await fetchSanitizationConfig();
    const domPurifyConfig = createDOMPurifyConfig(config);

    // Configure DOMPurify with our settings
    const cleanHtml = DOMPurify.sanitize(preprocessed, domPurifyConfig as any);

    return cleanHtml.toString();
  } catch (error) {
    console.error('Error during HTML sanitization:', error);
    // Fallback to basic DOMPurify sanitization
    return DOMPurify.sanitize(html).toString();
  }
}

/**
 * Synchronous sanitization using cached config (for cases where async is not possible)
 * Falls back to a safe configuration if no config is cached
 */
export function sanitizeHtmlSync(html: string): string {
  if (!html || html.trim() === '') {
    return '';
  }

  // Preprocess to extract images from figure tags
  const preprocessed = preprocessFigureTags(html);

  // If we have cached config, use it
  if (cachedConfig) {
    const domPurifyConfig = createDOMPurifyConfig(cachedConfig);
    return DOMPurify.sanitize(preprocessed, domPurifyConfig as any).toString();
  }

  // If we don't have cached config but there's an ongoing request, wait for it
  if (configPromise) {
    debug.log('Sanitization config loading in progress, using fallback for now');
  } else {
    // No config and no ongoing request - try to load it for next time
    console.warn('No cached sanitization config available, triggering load for future use');
    fetchSanitizationConfig().catch(error => {
      console.error('Failed to load sanitization config:', error);
    });
  }

  // Use comprehensive fallback configuration that preserves formatting
  debug.log('Using fallback sanitization configuration with formatting support');
  const fallbackAllowedCssProperties = [
    'color', 'font-size', 'font-weight', 
    'font-style', 'text-align', 'text-decoration', 'margin', 
    'padding', 'text-indent', 'line-height'
  ];

  const fallbackConfig: DOMPurify.Config = {
    ALLOWED_TAGS: [
      'p', 'br', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
      'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del', 'ins',
      'sup', 'sub', 'small', 'big', 'mark', 'pre', 'code', 'kbd', 'samp', 'var',
      'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'a', 'img',
      'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col',
      'blockquote', 'cite', 'q', 'hr', 'details', 'summary'
    ],
    ALLOWED_ATTR: [
      'class', 'style', 'colspan', 'rowspan', 'src', 'alt', 'width', 'height', 'href', 'title'
    ],
    // More permissive URL regex to allow complex query strings and tokens
    ALLOWED_URI_REGEXP: /^(?:(?:https?|data|#|\/):)?[\s\S]*$/i,
    ALLOW_UNKNOWN_PROTOCOLS: false,
    SANITIZE_DOM: true,
    KEEP_CONTENT: true,
    ALLOW_DATA_ATTR: false,
  };

  // Clear hooks and add CSS property filtering for fallback config
  DOMPurify.removeAllHooks();
  DOMPurify.addHook('afterSanitizeAttributes', function (node) {
    if (node.hasAttribute && node.hasAttribute('style')) {
      const styleValue = node.getAttribute('style');
      if (styleValue) {
        const filteredStyle = filterCssProperties(styleValue, fallbackAllowedCssProperties);
        if (filteredStyle) {
          node.setAttribute('style', filteredStyle);
        } else {
          node.removeAttribute('style');
        }
      }
    }
  });

  return DOMPurify.sanitize(preprocessed, fallbackConfig as any).toString();
}

/**
 * Preload sanitization configuration (call early in app lifecycle)
 */
export function preloadSanitizationConfig(): Promise<SanitizationConfig> {
  return fetchSanitizationConfig();
}

/**
 * Clear cached configuration (useful for testing or config updates)
 */
export function clearSanitizationCache(): void {
  cachedConfig = null;
  configPromise = null;
}