// Typosquatting Detector - Detects near-miss and brand+keyword domains
// Browser Techniques Line 6: Typosquatting / combo-squatting
// Misspellings or brand-support-login type domains lure users from email/search

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

export class TyposquattingDetector extends BaseDetector {
  constructor(config = {}) {
    super({
      name: 'typosquatting',
      category: 'social-engineering',
      version: '1.0.0',
      description: 'Detects typosquatting and combo-squatting domains',
      weight: 3,
      severity: 'high',
      ...config
    });
  }

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

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

    const rules = ruleRegistry.getRules('social-engineering', 'typosquatting');

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

    // Remove TLD for analysis
    const domainWithoutTLD = domain.split('.').slice(0, -1).join('.');

    // Check each brand domain
    for (const brandDomain of rules.brandDomains) {
      const brand = brandDomain.split('.')[0];

      // 1. Keyboard adjacency typos
      if (this.isKeyboardAdjacencyTypo(domainWithoutTLD, brand)) {
        findings.push(this.createFinding({
          pattern: 'keyboard-adjacency',
          matched: domain,
          description: `Keyboard adjacency typo of ${brandDomain}`,
          severity: 'high',
          weight: 3,
          metadata: {
            domain,
            brandDomain,
            technique: 'keyboard-adjacency'
          }
        }));
      }

      // 2. Character omission/insertion
      if (this.isCharacterTypo(domainWithoutTLD, brand)) {
        findings.push(this.createFinding({
          pattern: 'character-typo',
          matched: domain,
          description: `Character typo of ${brandDomain}`,
          severity: 'high',
          weight: 3,
          metadata: {
            domain,
            brandDomain,
            technique: 'character-typo'
          }
        }));
      }

      // 3. Combo-squatting (brand + keyword)
      if (rules.suspiciousKeywords) {
        for (const keyword of rules.suspiciousKeywords) {
          const comboPatterns = [
            `${brand}-${keyword}`,
            `${brand}${keyword}`,
            `${keyword}-${brand}`,
            `${keyword}${brand}`
          ];

          for (const pattern of comboPatterns) {
            if (domainWithoutTLD.includes(pattern)) {
              findings.push(this.createFinding({
                pattern: 'combo-squatting',
                matched: domain,
                description: `Combo-squatting: ${brand} + ${keyword}`,
                severity: 'critical',
                weight: 4,
                metadata: {
                  domain,
                  brandDomain,
                  keyword,
                  technique: 'combo-squatting'
                }
              }));
            }
          }
        }
      }

      // 4. Levenshtein distance check
      const distance = this.levenshteinDistance(domainWithoutTLD, brand);
      if (distance > 0 && distance <= 2 && domainWithoutTLD !== brand) {
        findings.push(this.createFinding({
          pattern: 'edit-distance',
          matched: domain,
          description: `Close match to ${brandDomain} (distance: ${distance})`,
          severity: 'high',
          weight: 3,
          metadata: {
            domain,
            brandDomain,
            editDistance: distance,
            technique: 'typosquatting'
          }
        }));
      }
    }

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

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

  isKeyboardAdjacencyTypo(domain, brand) {
    // Keyboard layout (QWERTY)
    const keyboard = {
      'q': ['w', 'a'],
      'w': ['q', 'e', 's'],
      'e': ['w', 'r', 'd'],
      'r': ['e', 't', 'f'],
      't': ['r', 'y', 'g'],
      'y': ['t', 'u', 'h'],
      'u': ['y', 'i', 'j'],
      'i': ['u', 'o', 'k'],
      'o': ['i', 'p', 'l'],
      'p': ['o', 'l'],
      'a': ['q', 's', 'z'],
      's': ['a', 'w', 'd', 'x'],
      'd': ['s', 'e', 'f', 'c'],
      'f': ['d', 'r', 'g', 'v'],
      'g': ['f', 't', 'h', 'b'],
      'h': ['g', 'y', 'j', 'n'],
      'j': ['h', 'u', 'k', 'm'],
      'k': ['j', 'i', 'l'],
      'l': ['k', 'o', 'p'],
      'z': ['a', 'x'],
      'x': ['z', 's', 'c'],
      'c': ['x', 'd', 'v'],
      'v': ['c', 'f', 'b'],
      'b': ['v', 'g', 'n'],
      'n': ['b', 'h', 'm'],
      'm': ['n', 'j']
    };

    if (domain.length !== brand.length) return false;

    let typoCount = 0;
    for (let i = 0; i < domain.length; i++) {
      if (domain[i] !== brand[i]) {
        const adjacent = keyboard[brand[i]] || [];
        if (!adjacent.includes(domain[i])) {
          return false;
        }
        typoCount++;
      }
    }

    return typoCount === 1;
  }

  isCharacterTypo(domain, brand) {
    // Check for single character omission, insertion, or substitution
    const lengthDiff = Math.abs(domain.length - brand.length);

    if (lengthDiff > 1) return false;

    // Omission (gogle vs google)
    if (domain.length === brand.length - 1) {
      for (let i = 0; i < brand.length; i++) {
        const without = brand.slice(0, i) + brand.slice(i + 1);
        if (without === domain) return true;
      }
    }

    // Insertion (googgle vs google)
    if (domain.length === brand.length + 1) {
      for (let i = 0; i < domain.length; i++) {
        const without = domain.slice(0, i) + domain.slice(i + 1);
        if (without === brand) return true;
      }
    }

    // Substitution (goagle vs google)
    if (domain.length === brand.length) {
      let diffCount = 0;
      for (let i = 0; i < domain.length; i++) {
        if (domain[i] !== brand[i]) diffCount++;
      }
      return diffCount === 1;
    }

    return false;
  }

  levenshteinDistance(str1, str2) {
    const matrix = [];

    for (let i = 0; i <= str2.length; i++) {
      matrix[i] = [i];
    }

    for (let j = 0; j <= str1.length; j++) {
      matrix[0][j] = j;
    }

    for (let i = 1; i <= str2.length; i++) {
      for (let j = 1; j <= str1.length; j++) {
        if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1,
            matrix[i][j - 1] + 1,
            matrix[i - 1][j] + 1
          );
        }
      }
    }

    return matrix[str2.length][str1.length];
  }
}
