// Lookalike Domain Detector - Detects homoglyph and punycode domain attacks
// Browser Techniques Line 5: Lookalike domains (homoglyph/punycode)
// Unicode homoglyphs, punycode, deceptive subdomains to mimic brands

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

export class LookalikeDomainDetector extends BaseDetector {
  constructor(config = {}) {
    super({
      name: 'lookalike-domain',
      category: 'social-engineering',
      version: '1.0.0',
      description: 'Detects lookalike domains using homoglyphs, punycode, and visual deception',
      weight: 3,
      severity: 'high',
      ...config
    });
  }

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

    const findings = [];
    const domain = this.extractDomain(target.url);

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

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

    // Check for punycode (xn-- prefix)
    if (domain.startsWith('xn--') || domain.includes('.xn--')) {
      findings.push(this.createFinding({
        pattern: 'punycode',
        matched: domain,
        description: 'Punycode domain detected - potential homoglyph attack',
        severity: 'high',
        weight: 3,
        metadata: {
          domain,
          technique: 'punycode',
          decodedDomain: this.decodePunycode(domain)
        }
      }));
    }

    // Check for mixed scripts (potential homoglyph attack)
    if (this.hasMixedScripts(domain)) {
      findings.push(this.createFinding({
        pattern: 'mixed-scripts',
        matched: domain,
        description: 'Domain contains mixed character scripts - potential homoglyph attack',
        severity: 'high',
        weight: 3,
        metadata: {
          domain,
          technique: 'mixed-scripts'
        }
      }));
    }

    // Check for lookalike characters
    const homoglyphs = this.detectHomoglyphs(domain);
    if (homoglyphs.length > 0) {
      findings.push(this.createFinding({
        pattern: 'homoglyph',
        matched: domain,
        description: `Domain contains lookalike characters: ${homoglyphs.join(', ')}`,
        severity: 'high',
        weight: 3,
        metadata: {
          domain,
          technique: 'homoglyph',
          suspiciousChars: homoglyphs
        }
      }));
    }

    // Check against known brand domains if rules exist
    if (rules && rules.brandDomains) {
      const similarity = this.checkBrandSimilarity(domain, rules.brandDomains);
      if (similarity.matched) {
        findings.push(this.createFinding({
          pattern: 'brand-lookalike',
          matched: domain,
          description: `Domain is similar to ${similarity.brand}: ${similarity.domain}`,
          severity: 'critical',
          weight: 4,
          metadata: {
            domain,
            brandDomain: similarity.domain,
            brand: similarity.brand,
            technique: 'brand-impersonation',
            similarity: similarity.score
          }
        }));
      }
    }

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

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

  decodePunycode(domain) {
    try {
      return decodeURIComponent(domain);
    } catch (error) {
      return domain;
    }
  }

  hasMixedScripts(domain) {
    // Detect if domain mixes Latin with Cyrillic, Greek, etc.
    const latinRange = /[a-zA-Z]/;
    const cyrillicRange = /[\u0400-\u04FF]/;
    const greekRange = /[\u0370-\u03FF]/;

    const hasLatin = latinRange.test(domain);
    const hasCyrillic = cyrillicRange.test(domain);
    const hasGreek = greekRange.test(domain);

    const scriptCount = [hasLatin, hasCyrillic, hasGreek].filter(Boolean).length;
    return scriptCount > 1;
  }

  detectHomoglyphs(domain) {
    // Common homoglyph substitutions
    const homoglyphs = {
      'а': 'a', // Cyrillic a looks like Latin a
      'е': 'e', // Cyrillic e looks like Latin e
      'о': 'o', // Cyrillic o looks like Latin o
      'р': 'p', // Cyrillic r looks like Latin p
      'с': 'c', // Cyrillic s looks like Latin c
      'у': 'y', // Cyrillic u looks like Latin y
      'х': 'x', // Cyrillic h looks like Latin x
      'ı': 'i', // Turkish i without dot
      'ǀ': 'l', // Latin letter dental click looks like l
      '0': 'o', // Zero looks like o
      '1': 'l'  // One looks like l
    };

    const detected = [];
    for (const char of domain) {
      if (homoglyphs[char]) {
        detected.push(`${char}→${homoglyphs[char]}`);
      }
    }

    return detected;
  }

  checkBrandSimilarity(domain, brandDomains) {
    // Check if domain is suspiciously similar to known brands
    for (const brandDomain of brandDomains) {
      const brand = brandDomain.split('.')[0];

      // Exact subdomain match (e.g., google.com.evil.com)
      if (domain.includes(brandDomain) && domain !== brandDomain) {
        return {
          matched: true,
          brand,
          domain: brandDomain,
          score: 1.0,
          technique: 'subdomain-impersonation'
        };
      }

      // Levenshtein distance check for typosquatting
      const similarity = this.calculateSimilarity(domain, brandDomain);
      if (similarity > 0.8 && domain !== brandDomain) {
        return {
          matched: true,
          brand,
          domain: brandDomain,
          score: similarity,
          technique: 'typosquatting'
        };
      }
    }

    return { matched: false };
  }

  calculateSimilarity(str1, str2) {
    // Simple similarity calculation (Levenshtein-based)
    const longer = str1.length > str2.length ? str1 : str2;
    const shorter = str1.length > str2.length ? str2 : str1;

    if (longer.length === 0) return 1.0;

    const editDistance = this.levenshteinDistance(longer, shorter);
    return (longer.length - editDistance) / longer.length;
  }

  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];
  }
}
