// XSS Detector - Detects Cross-Site Scripting patterns
// Browser Techniques Line 44-56: XSS (Stored, Reflected, DOM-based)
// Attacker-controlled scripts execute within vulnerable site origin

import { BaseDetector } from '../base-detector.js';

export class XssDetector extends BaseDetector {
  constructor(config = {}) {
    super({
      name: 'xss-detector',
      category: 'code-injection',
      version: '1.0.0',
      description: 'Detects Cross-Site Scripting (XSS) attempts',
      weight: 3,
      severity: 'high',
      ...config
    });
  }

  async detect(target, ruleRegistry) {
    const findings = [];
    const url = target.url;

    if (!url) {
      return this.createResult(false, [], 0);
    }

    const rules = ruleRegistry.getRules('code-injection', 'xss');

    if (!rules) {
      return this.createResult(false, [], 0);
    }

    const urlParams = target.urlParams || this.extractUrlParams(url);
    const domManipulations = target.domManipulations || [];
    const evalCalls = target.evalCalls || [];
    const postMessageEvents = target.postMessageEvents || [];
    const reflectedInputs = target.reflectedInputs || [];

    // 1. Check URL parameters for XSS payloads
    for (const [paramName, paramValue] of Object.entries(urlParams)) {
      const xssPayload = this.detectXssPayload(paramValue, rules);

      if (xssPayload) {
        findings.push(this.createFinding({
          pattern: 'url-param-xss',
          matched: `${paramName}=${paramValue}`,
          description: `XSS payload detected in URL parameter: ${paramName}`,
          severity: xssPayload.severity,
          weight: xssPayload.weight,
          metadata: {
            parameter: paramName,
            value: paramValue,
            payloadType: xssPayload.type,
            technique: 'xss-reflected'
          }
        }));
      }
    }

    // 2. Detect dangerous DOM manipulations
    for (const manipulation of domManipulations) {
      if (manipulation.method === 'innerHTML' || manipulation.method === 'outerHTML') {
        const containsUnsanitized = this.containsUnsanitizedInput(manipulation.value, rules);

        if (containsUnsanitized) {
          findings.push(this.createFinding({
            pattern: 'innerhtml-xss',
            matched: manipulation.method,
            description: `Unsanitized data in ${manipulation.method} (DOM XSS risk)`,
            severity: 'high',
            weight: 4,
            metadata: {
              method: manipulation.method,
              source: manipulation.source,
              technique: 'xss-dom-based'
            }
          }));
        }
      }

      if (manipulation.method === 'document.write' || manipulation.method === 'document.writeln') {
        findings.push(this.createFinding({
          pattern: 'document-write-xss',
          matched: manipulation.method,
          description: 'document.write() with dynamic content (XSS risk)',
          severity: 'high',
          weight: 4,
          metadata: {
            method: manipulation.method,
            technique: 'xss-dom-based'
          }
        }));
      }
    }

    // 3. Detect eval() with URL/hash data
    for (const evalCall of evalCalls) {
      if (this.usesUrlData(evalCall.source, rules)) {
        findings.push(this.createFinding({
          pattern: 'eval-url-data',
          matched: 'eval() with URL data',
          description: 'eval() called with URL or location hash data',
          severity: 'critical',
          weight: 5,
          metadata: {
            source: evalCall.source,
            technique: 'xss-dom-based'
          }
        }));
      }
    }

    // 4. Detect postMessage origin confusion
    for (const pmEvent of postMessageEvents) {
      if (!pmEvent.originChecked || pmEvent.acceptsAnyOrigin) {
        findings.push(this.createFinding({
          pattern: 'postmessage-no-origin-check',
          matched: 'postMessage without origin validation',
          description: 'postMessage handler without proper origin validation',
          severity: 'high',
          weight: 4,
          metadata: {
            acceptsAnyOrigin: pmEvent.acceptsAnyOrigin,
            technique: 'xss-postmessage'
          }
        }));
      }

      // Check if postMessage data is used in dangerous sinks
      if (pmEvent.usedInDangerousSink) {
        findings.push(this.createFinding({
          pattern: 'postmessage-dangerous-sink',
          matched: pmEvent.sink,
          description: `postMessage data used in dangerous sink: ${pmEvent.sink}`,
          severity: 'critical',
          weight: 5,
          metadata: {
            sink: pmEvent.sink,
            technique: 'xss-postmessage'
          }
        }));
      }
    }

    // 5. Check for reflected input in page content
    for (const reflection of reflectedInputs) {
      if (reflection.isUnsanitized) {
        findings.push(this.createFinding({
          pattern: 'reflected-input',
          matched: reflection.input,
          description: 'Unsanitized user input reflected in page content',
          severity: 'high',
          weight: 4,
          metadata: {
            input: reflection.input,
            location: reflection.location,
            technique: 'xss-reflected'
          }
        }));
      }
    }

    // 6. Detect DOM clobbering attempts
    if (target.domClobbering && target.domClobbering.length > 0) {
      findings.push(this.createFinding({
        pattern: 'dom-clobbering',
        matched: `${target.domClobbering.length} clobbering candidates`,
        description: 'DOM clobbering patterns detected',
        severity: 'medium',
        weight: 3,
        metadata: {
          clobberedElements: target.domClobbering,
          technique: 'xss-dom-clobbering'
        }
      }));
    }

    // 7. Detect dangerous functions with user input
    if (target.dangerousFunctions && target.dangerousFunctions.length > 0) {
      for (const dangerousFunc of target.dangerousFunctions) {
        findings.push(this.createFinding({
          pattern: 'dangerous-function',
          matched: dangerousFunc.name,
          description: `Dangerous function ${dangerousFunc.name}() called with user input`,
          severity: 'critical',
          weight: 5,
          metadata: {
            function: dangerousFunc.name,
            source: dangerousFunc.source,
            technique: 'xss-dom-based'
          }
        }));
      }
    }

    // 8. Check for href javascript: protocol
    if (target.javascriptHrefs && target.javascriptHrefs.length > 0) {
      findings.push(this.createFinding({
        pattern: 'javascript-protocol-href',
        matched: `${target.javascriptHrefs.length} javascript: hrefs`,
        description: 'Links with javascript: protocol detected',
        severity: 'medium',
        weight: 2,
        metadata: {
          count: target.javascriptHrefs.length,
          technique: 'xss-javascript-protocol'
        }
      }));
    }

    // 9. Detect inline event handlers with user input
    if (target.inlineEventHandlers && target.inlineEventHandlers.length > 0) {
      const suspiciousHandlers = target.inlineEventHandlers.filter(handler =>
        this.containsUnsanitizedInput(handler.code, rules)
      );

      if (suspiciousHandlers.length > 0) {
        findings.push(this.createFinding({
          pattern: 'inline-event-handler-xss',
          matched: `${suspiciousHandlers.length} suspicious handlers`,
          description: 'Inline event handlers with unsanitized user input',
          severity: 'high',
          weight: 4,
          metadata: {
            count: suspiciousHandlers.length,
            technique: 'xss-event-handler'
          }
        }));
      }
    }

    const matched = findings.length > 0;
    const riskScore = findings.reduce((sum, f) => sum + (f.weight || 1), 0);

    return this.createResult(matched, findings, riskScore);
  }

  extractUrlParams(url) {
    try {
      const urlObj = new URL(url);
      const params = {};

      for (const [key, value] of urlObj.searchParams.entries()) {
        params[key] = value;
      }

      return params;
    } catch {
      return {};
    }
  }

  detectXssPayload(value, rules) {
    const xssPatterns = rules.xssPatterns || [
      { pattern: /<script[^>]*>.*?<\/script>/gi, type: 'script-tag', severity: 'critical', weight: 5 },
      { pattern: /<iframe[^>]*>/gi, type: 'iframe-tag', severity: 'critical', weight: 5 },
      { pattern: /javascript:/gi, type: 'javascript-protocol', severity: 'high', weight: 4 },
      { pattern: /on\w+\s*=\s*["'][^"']*["']/gi, type: 'event-handler', severity: 'high', weight: 4 },
      { pattern: /<img[^>]*onerror/gi, type: 'img-onerror', severity: 'high', weight: 4 },
      { pattern: /<svg[^>]*onload/gi, type: 'svg-onload', severity: 'high', weight: 4 },
      { pattern: /eval\s*\(/gi, type: 'eval-call', severity: 'critical', weight: 5 },
      { pattern: /expression\s*\(/gi, type: 'css-expression', severity: 'medium', weight: 3 }
    ];

    for (const { pattern, type, severity, weight } of xssPatterns) {
      if (pattern.test(value)) {
        return { type, severity, weight };
      }
    }

    return null;
  }

  containsUnsanitizedInput(value, rules) {
    if (!value) return false;

    const unsanitizedSources = rules.unsanitizedSources || [
      'location.', 'document.URL', 'document.referrer',
      'window.name', 'document.cookie', 'location.hash',
      'location.search', 'location.href'
    ];

    const valueLower = value.toLowerCase();
    return unsanitizedSources.some(source => valueLower.includes(source.toLowerCase()));
  }

  usesUrlData(source, rules) {
    if (!source) return false;

    const urlDataSources = rules.urlDataSources || [
      'location.search', 'location.hash', 'location.href',
      'document.URL', 'document.referrer', 'window.name'
    ];

    const sourceLower = source.toLowerCase();
    return urlDataSources.some(urlSource => sourceLower.includes(urlSource.toLowerCase()));
  }
}
