export enum UrlVerdict {
  SAFE = 'safe',
  SUSPICIOUS = 'suspicious',
  PHISHING = 'phishing',
  SPAM = 'spam',
  EXPOSED_CREDENTIALS = 'exposed_credentials',
  UNKNOWN = 'unknown',
}

export enum ThreatType {
  PHISHING_KNOWN = 'phishing_known',
  PHISHING_HEURISTIC = 'phishing_heuristic',
  DOMAIN_AGE = 'domain_age',
  SSL_ANOMALY = 'ssl_anomaly',
  URL_ENTROPY = 'url_entropy',
  TYPOSQUAT = 'typosquat',
  CREDENTIAL_EXPOSURE = 'credential_exposure',
  SPAM_SOURCE = 'spam_source',
  REDIRECT_CHAIN = 'redirect_chain',
  MIXED_CONTENT = 'mixed_content',
}

export interface ThreatInfo {
  type: ThreatType;
  severity: number;
  source: string;
  description: string;
}

export class PhishingDetector {
  private knownSuspiciousTlds = new Set([
    '.tk', '.ml', '.ga', '.cf', '.gq', '.xyz', '.top', '.click', '.link', '.work',
  ]);

  private commonBrands = new Map<string, string[]>([
    ['google', ['gmail', 'drive', 'docs', 'maps', 'play', 'chrome', 'youtube']],
    ['apple', ['icloud', 'appstore', 'icloud_content', 'appleid']],
    ['amazon', ['aws', 'amazonaws', 'amazon-adsystem', 'prime-video']],
    ['microsoft', ['office', 'outlook', 'onedrive', 'teams', 'azure', 'windows']],
    ['facebook', ['fb', 'fbcdn', 'instagram', 'whatsapp', 'messenger']],
    ['paypal', ['paypalobjects', 'paypal-web', 'xoom']],
    ['netflix', ['nflximg', 'nflxso', 'nflxvideo', 'nflxext']],
  ]);

  analyzeUrl(url: string): { verdict: UrlVerdict; threats: ThreatInfo[]; score: number } {
    const threats: ThreatInfo[] = [];
    let score = 0;

    try {
      const parsed = new URL(url);
      const hostname = parsed.hostname.toLowerCase();
      const domainParts = hostname.split('.');
      const tld = domainParts[domainParts.length - 1];

      score += this.checkTld(tld, threats);
      score += this.checkEntropy(parsed.pathname + parsed.search, threats);
      score += this.checkTyposquatting(hostname, threats);
      score += this.checkIpAddress(hostname, threats);
      score += this.checkLongUrl(url, threats);
      score += this.checkSubdomainDepth(domainParts, threats);
      score += this.checkHttpsProtocol(parsed.protocol, threats);
      score += this.checkRedirectPatterns(parsed.search, threats);
      score += this.checkEncodedChars(url, threats);
      score += this.checkBrandImpersonation(hostname, threats);
    } catch {
      return {
        verdict: UrlVerdict.UNKNOWN,
        threats: [{ type: ThreatType.PHISHING_HEURISTIC, severity: 3, source: 'heuristic', description: 'Malformed URL' }],
        score: 30,
      };
    }

    const verdict = score >= 70 ? UrlVerdict.PHISHING
      : score >= 40 ? UrlVerdict.SUSPICIOUS
      : score >= 20 ? UrlVerdict.SPAM
      : UrlVerdict.SAFE;

    return { verdict, threats, score };
  }

  private checkTld(tld: string, threats: ThreatInfo[]): number {
    if (this.knownSuspiciousTlds.has(`.${tld}`)) {
      threats.push({ type: ThreatType.DOMAIN_AGE, severity: 4, source: 'heuristic', description: `Suspicious TLD: .${tld}` });
      return 25;
    }
    return 0;
  }

  private checkEntropy(pathname: string, threats: ThreatInfo[]): number {
    if (!pathname || pathname.length < 20) return 0;
    const entropy = this.calculateEntropy(pathname);
    if (entropy > 4.5) {
      threats.push({ type: ThreatType.URL_ENTROPY, severity: 4, source: 'heuristic', description: `High URL path entropy (${entropy.toFixed(2)})` });
      return 20;
    }
    return 0;
  }

  private checkTyposquatting(hostname: string, threats: ThreatInfo[]): number {
    for (const [brand, subdomains] of this.commonBrands) {
      const parts = hostname.split('.');
      const main = parts[0];
      if (main.includes(brand) && main !== brand) {
        const dist = this.levenshteinDistance(main, brand);
        if (dist <= 2 && dist > 0) {
          threats.push({ type: ThreatType.TYPOSQUAT, severity: 5, source: 'heuristic', description: `Possible typosquat of "${brand}"` });
          return 35;
        }
      }
      const dist = this.levenshteinDistance(main, brand);
      if (dist <= 2 && dist > 0 && main.length >= brand.length - 1) {
        threats.push({ type: ThreatType.TYPOSQUAT, severity: 5, source: 'heuristic', description: `Possible typosquat of "${brand}"` });
        return 35;
      }
      for (const sub of subdomains) {
        if (hostname.includes(sub) && !hostname.startsWith(`${sub}.`)) {
          threats.push({ type: ThreatType.TYPOSQUAT, severity: 3, source: 'heuristic', description: `Contains "${sub}" but not official ${brand}` });
          return 15;
        }
      }
    }
    return 0;
  }

  private checkIpAddress(hostname: string, threats: ThreatInfo[]): number {
    if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) && hostname !== '127.0.0.1') {
      threats.push({ type: ThreatType.PHISHING_HEURISTIC, severity: 4, source: 'heuristic', description: `IP address hostname: ${hostname}` });
      return 25;
    }
    return 0;
  }

  private checkLongUrl(url: string, threats: ThreatInfo[]): number {
    if (url.length > 200) {
      threats.push({ type: ThreatType.PHISHING_HEURISTIC, severity: 3, source: 'heuristic', description: `Long URL (${url.length} chars)` });
      return 15;
    }
    return 0;
  }

  private checkSubdomainDepth(parts: string[], threats: ThreatInfo[]): number {
    if (parts.length > 5) {
      threats.push({ type: ThreatType.PHISHING_HEURISTIC, severity: 3, source: 'heuristic', description: `Deep subdomains (${parts.length} levels)` });
      return 15;
    }
    return 0;
  }

  private checkHttpsProtocol(protocol: string, threats: ThreatInfo[]): number {
    if (protocol === 'http:') {
      threats.push({ type: ThreatType.MIXED_CONTENT, severity: 2, source: 'heuristic', description: 'HTTP (not HTTPS)' });
      return 10;
    }
    return 0;
  }

  private checkRedirectPatterns(query: string, threats: ThreatInfo[]): number {
    const params = ['redirect', 'url', 'dest', 'return', 'next', 'target'];
    const count = params.filter((p) => query.includes(`${p}=`)).length;
    if (count >= 2) {
      threats.push({ type: ThreatType.REDIRECT_CHAIN, severity: 3, source: 'heuristic', description: `Multiple redirect params (${count})` });
      return 15;
    }
    return 0;
  }

  private checkEncodedChars(url: string, threats: ThreatInfo[]): number {
    if (/(%[0-9a-fA-F]{2}){3,}/.test(url)) {
      threats.push({ type: ThreatType.URL_ENTROPY, severity: 3, source: 'heuristic', description: 'Excessive URL encoding' });
      return 15;
    }
    return 0;
  }

  private checkBrandImpersonation(hostname: string, threats: ThreatInfo[]): number {
    const patterns = [/login[-_]?(secure|portal|page|form)/i, /account[-_]?(verify|confirm|update)/i, /secure[-_]?(signin|auth|login)/i];
    for (const pattern of patterns) {
      if (pattern.test(hostname)) {
        threats.push({ type: ThreatType.PHISHING_HEURISTIC, severity: 4, source: 'heuristic', description: `Phishing pattern: ${hostname}` });
        return 20;
      }
    }
    return 0;
  }

  private calculateEntropy(str: string): number {
    const freq: Record<string, number> = {};
    for (const c of str) freq[c] = (freq[c] || 0) + 1;
    let entropy = 0;
    const len = str.length;
    for (const count of Object.values(freq)) {
      const p = count / len;
      entropy -= p * Math.log2(p);
    }
    return entropy;
  }

  private levenshteinDistance(a: string, b: string): number {
    const m: number[][] = [];
    for (let i = 0; i <= b.length; i++) m[i] = [i];
    for (let j = 0; j <= a.length; j++) m[0][j] = j;
    for (let i = 1; i <= b.length; i++)
      for (let j = 1; j <= a.length; j++)
        m[i][j] = b[i-1] === a[j-1] ? m[i-1][j-1] : Math.min(m[i-1][j-1]+1, m[i][j-1]+1, m[i-1][j]+1);
    return m[b.length][a.length];
  }
}

export const phishingDetector = new PhishingDetector();
