// Formjacking Detector - Detects payment card field interception
// Browser Techniques Line 59: Card field interception
// Hook input events on payment fields; exfiltrate via fetch/image beacons

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

export class FormjackingDetector extends BaseDetector {
  constructor(config = {}) {
    super({
      name: 'formjacking',
      category: 'code-injection',
      version: '1.0.0',
      description: 'Detects formjacking attacks targeting payment and credential forms',
      weight: 4,
      severity: 'critical',
      ...config
    });
  }

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

    // This detector requires DOM data from content script
    const forms = target.forms || [];
    const inputs = target.inputs || [];
    const scripts = target.scripts || [];
    const eventListeners = target.eventListeners || [];

    if (forms.length === 0 && inputs.length === 0) {
      return this.createResult(false, [], 0);
    }

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

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

    // 1. Detect payment forms with suspicious event listeners
    for (const form of forms) {
      if (this.isPaymentForm(form, rules)) {
        // Check for excessive event listeners on card fields
        const cardInputListeners = this.detectSuspiciousListeners(
          form.cardInputs || [],
          eventListeners,
          rules
        );

        if (cardInputListeners.length > 0) {
          findings.push(this.createFinding({
            pattern: 'payment-form-listeners',
            matched: `${cardInputListeners.length} suspicious listeners on card fields`,
            description: 'Payment form has suspicious event listeners on card inputs',
            severity: 'critical',
            weight: 5,
            metadata: {
              formAction: form.action,
              listenerTypes: cardInputListeners.map(l => l.type),
              listenerCount: cardInputListeners.length,
              technique: 'formjacking-listeners'
            }
          }));
        }
      }
    }

    // 2. Detect scripts accessing payment input fields
    const paymentInputs = inputs.filter(input => this.isPaymentInput(input, rules));

    for (const input of paymentInputs) {
      if (input.accessedByScripts && input.accessedByScripts.length > 0) {
        // Filter to suspicious script sources
        const suspiciousScripts = input.accessedByScripts.filter(scriptSrc =>
          this.isSuspiciousScriptSource(scriptSrc, target.currentOrigin, rules)
        );

        if (suspiciousScripts.length > 0) {
          findings.push(this.createFinding({
            pattern: 'cross-origin-card-access',
            matched: `${suspiciousScripts.length} suspicious scripts accessing card field`,
            description: 'Payment input field accessed by cross-origin scripts',
            severity: 'critical',
            weight: 5,
            metadata: {
              inputName: input.name,
              inputType: input.type,
              suspiciousScripts,
              technique: 'formjacking-cross-origin'
            }
          }));
        }
      }
    }

    // 3. Detect obfuscated scripts on payment pages
    for (const script of scripts) {
      if (this.isObfuscatedScript(script, rules)) {
        const hasPaymentForms = forms.some(f => this.isPaymentForm(f, rules));

        if (hasPaymentForms) {
          findings.push(this.createFinding({
            pattern: 'obfuscated-script-on-payment-page',
            matched: script.src || 'inline script',
            description: 'Obfuscated script found on page with payment forms',
            severity: 'high',
            weight: 4,
            metadata: {
              scriptSrc: script.src,
              obfuscationScore: script.obfuscationScore,
              technique: 'formjacking-obfuscation'
            }
          }));
        }
      }
    }

    // 4. Detect dynamic script injection after page load
    if (target.dynamicScripts && target.dynamicScripts.length > 0) {
      const hasPaymentForms = forms.some(f => this.isPaymentForm(f, rules));

      if (hasPaymentForms) {
        const suspiciousDynamic = target.dynamicScripts.filter(scriptSrc =>
          this.isSuspiciousScriptSource(scriptSrc, target.currentOrigin, rules)
        );

        if (suspiciousDynamic.length > 0) {
          findings.push(this.createFinding({
            pattern: 'dynamic-script-injection',
            matched: `${suspiciousDynamic.length} dynamically injected scripts`,
            description: 'Scripts injected after page load on payment page',
            severity: 'high',
            weight: 4,
            metadata: {
              dynamicScripts: suspiciousDynamic,
              technique: 'formjacking-dynamic-injection'
            }
          }));
        }
      }
    }

    // 5. Detect input value exfiltration
    for (const input of paymentInputs) {
      if (input.valueExfiltrated) {
        findings.push(this.createFinding({
          pattern: 'card-data-exfiltration',
          matched: `${input.name} value sent to ${input.exfilDestination}`,
          description: 'Payment card data sent to external destination',
          severity: 'critical',
          weight: 5,
          metadata: {
            inputName: input.name,
            destination: input.exfilDestination,
            method: input.exfilMethod,
            technique: 'formjacking-exfiltration'
          }
        }));
      }
    }

    // 6. Detect clipboard access on payment pages
    if (target.clipboardAccess && target.clipboardAccess.length > 0) {
      const hasPaymentForms = forms.some(f => this.isPaymentForm(f, rules));

      if (hasPaymentForms) {
        findings.push(this.createFinding({
          pattern: 'clipboard-access-on-payment-page',
          matched: `${target.clipboardAccess.length} clipboard access events`,
          description: 'Clipboard accessed on page with payment forms',
          severity: 'high',
          weight: 3,
          metadata: {
            accessCount: target.clipboardAccess.length,
            technique: 'formjacking-clipboard'
          }
        }));
      }
    }

    // 7. Detect keylogging patterns
    if (eventListeners) {
      const keyloggers = this.detectKeylogging(eventListeners, paymentInputs, rules);

      if (keyloggers.length > 0) {
        findings.push(this.createFinding({
          pattern: 'keylogger-on-payment-fields',
          matched: `${keyloggers.length} keylogging listeners detected`,
          description: 'Keylogging event listeners on payment input fields',
          severity: 'critical',
          weight: 5,
          metadata: {
            keyloggerCount: keyloggers.length,
            targetedInputs: keyloggers.map(k => k.inputName),
            technique: 'formjacking-keylogging'
          }
        }));
      }
    }

    // 8. Detect hidden iframes on payment pages
    if (target.iframes) {
      const hiddenIframes = target.iframes.filter(iframe =>
        iframe.isHidden || iframe.width < 10 || iframe.height < 10
      );

      if (hiddenIframes.length > 0) {
        const hasPaymentForms = forms.some(f => this.isPaymentForm(f, rules));

        if (hasPaymentForms) {
          findings.push(this.createFinding({
            pattern: 'hidden-iframe-on-payment-page',
            matched: `${hiddenIframes.length} hidden iframes`,
            description: 'Hidden iframes detected on payment page',
            severity: 'medium',
            weight: 3,
            metadata: {
              iframeCount: hiddenIframes.length,
              technique: 'formjacking-hidden-iframe'
            }
          }));
        }
      }
    }

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

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

  isPaymentForm(form, rules) {
    const cardFieldNames = rules.cardFieldNames || [
      'cardnumber', 'card-number', 'ccnumber', 'cc-number',
      'creditcard', 'credit-card', 'pan', 'card_number',
      'cardNumber', 'creditCardNumber'
    ];

    const cvvFieldNames = rules.cvvFieldNames || [
      'cvv', 'cvc', 'cvv2', 'csc', 'securitycode', 'security-code',
      'card-cvc', 'card-cvv'
    ];

    const expiryFieldNames = rules.expiryFieldNames || [
      'expiry', 'expiration', 'exp-date', 'expdate', 'ccexp',
      'cc-exp', 'card-exp', 'expirydate'
    ];

    // Check if form has card-related input names
    const hasCardField = form.inputNames?.some(name =>
      cardFieldNames.some(pattern => name.toLowerCase().includes(pattern))
    );

    const hasCvvField = form.inputNames?.some(name =>
      cvvFieldNames.some(pattern => name.toLowerCase().includes(pattern))
    );

    const hasExpiryField = form.inputNames?.some(name =>
      expiryFieldNames.some(pattern => name.toLowerCase().includes(pattern))
    );

    return hasCardField || (hasCvvField && hasExpiryField);
  }

  isPaymentInput(input, rules) {
    const paymentInputNames = [
      ...(rules.cardFieldNames || []),
      ...(rules.cvvFieldNames || []),
      ...(rules.expiryFieldNames || [])
    ];

    const inputNameLower = (input.name || '').toLowerCase();
    return paymentInputNames.some(pattern => inputNameLower.includes(pattern));
  }

  detectSuspiciousListeners(cardInputs, eventListeners, rules) {
    const suspiciousListenerTypes = rules.suspiciousListenerTypes || [
      'keydown', 'keyup', 'keypress', 'input', 'change', 'blur', 'paste', 'copy'
    ];

    const maxListenersPerInput = rules.maxListenersPerInput || 3;

    const suspicious = [];

    for (const input of cardInputs) {
      const inputListeners = eventListeners.filter(listener =>
        listener.targetElement === input.id || listener.targetElement === input.name
      );

      const suspiciousTypes = inputListeners.filter(listener =>
        suspiciousListenerTypes.includes(listener.type)
      );

      if (suspiciousTypes.length > maxListenersPerInput) {
        suspicious.push(...suspiciousTypes);
      }
    }

    return suspicious;
  }

  isSuspiciousScriptSource(scriptSrc, currentOrigin, rules) {
    if (!scriptSrc) return false;

    try {
      const scriptUrl = new URL(scriptSrc, currentOrigin);
      const scriptOrigin = scriptUrl.origin;

      // Check if cross-origin
      if (scriptOrigin !== currentOrigin) {
        // Check against known payment processor origins (these are safe)
        const trustedPaymentOrigins = rules.trustedPaymentOrigins || [
          'https://js.stripe.com',
          'https://checkout.stripe.com',
          'https://www.paypal.com',
          'https://www.paypalobjects.com',
          'https://js.braintreegateway.com',
          'https://pay.google.com'
        ];

        if (trustedPaymentOrigins.some(trusted => scriptOrigin.startsWith(trusted))) {
          return false;
        }

        // Cross-origin and not trusted = suspicious
        return true;
      }

      return false;
    } catch {
      return false;
    }
  }

  isObfuscatedScript(script, rules) {
    if (!script.content && !script.src) return false;

    const obfuscationScore = script.obfuscationScore || 0;
    const threshold = rules.obfuscationThreshold || 3;

    return obfuscationScore >= threshold;
  }

  detectKeylogging(eventListeners, paymentInputs, rules) {
    const keyEventTypes = ['keydown', 'keyup', 'keypress'];
    const keyloggers = [];

    for (const input of paymentInputs) {
      const keyListeners = eventListeners.filter(listener =>
        (listener.targetElement === input.id || listener.targetElement === input.name) &&
        keyEventTypes.includes(listener.type)
      );

      // Having multiple key event listeners on card fields is suspicious
      if (keyListeners.length >= 2) {
        keyloggers.push({
          inputName: input.name,
          listeners: keyListeners
        });
      }
    }

    return keyloggers;
  }
}
