import * as cheerio from 'cheerio';
import { ALL_SPAM_TRIGGERS } from './triggers';

export interface SpamFinding {
  rule: string;
  severity: 'info' | 'warning' | 'error';
  points: number;
  message: string;
  detail?: string;
}

export interface SpamReport {
  score: number;
  verdict: 'ship' | 'fix' | 'reject';
  findings: SpamFinding[];
  metrics: {
    htmlBytes: number;
    textBytes: number;
    imageCount: number;
    linkCount: number;
    textWords: number;
    imageTextRatio: number;
    subjectLength: number;
    subjectAllCapsWords: number;
    subjectExclamations: number;
    spamTriggerHits: string[];
    missingAltTags: number;
    hasListUnsubscribe: boolean;
    hasPlainText: boolean;
  };
}

export interface PreflightInput {
  html: string;
  text?: string;
  subject: string;
  preheader?: string;
  hasListUnsubscribeHeader?: boolean;
  brandTerms?: string[]; // Wörter die ALL-CAPS sein dürfen (z.B. Workspace-Namen)
}

const GMAIL_CLIPPING_BYTES = 102_000;
const SHIP_THRESHOLD = 14;
const REJECT_THRESHOLD = 30;

export function runPreflight(input: PreflightInput): SpamReport {
  const $ = cheerio.load(input.html);
  const findings: SpamFinding[] = [];
  const brandSet = new Set((input.brandTerms ?? []).map((s) => s.toUpperCase()));

  const images = $('img').toArray();
  const links = $('a[href]').toArray();
  const text = $('body').text().replace(/\s+/g, ' ').trim();
  const words = text.split(/\s+/).filter(Boolean);
  const wordCount = words.length;
  const imageCount = images.length;
  const linkCount = links.length;
  const htmlBytes = Buffer.byteLength(input.html, 'utf8');
  const textBytes = Buffer.byteLength(input.text ?? '', 'utf8');
  const imageTextRatio = imageCount === 0 ? 1 : Math.min(1, wordCount / (imageCount * 50));

  // 1. Text/Bild-Verhältnis
  if (imageTextRatio < 0.6 && imageCount > 0) {
    findings.push({
      rule: 'image-text-ratio',
      severity: 'error',
      points: 30,
      message: 'Bild-Anteil zu hoch — mindestens 50 Wörter pro Bild empfohlen',
      detail: `Aktuelle Ratio: ${(imageTextRatio * 100).toFixed(0)}% (${wordCount} Wörter, ${imageCount} Bilder)`,
    });
  }

  // 2. Alt-Texte fehlen
  const missingAlt = images.filter((el) => {
    const alt = $(el).attr('alt');
    return alt === undefined || alt.trim() === '';
  }).length;
  if (missingAlt > 0) {
    findings.push({
      rule: 'missing-alt',
      severity: 'error',
      points: missingAlt * 5,
      message: `${missingAlt} Bild(er) ohne Alt-Text`,
    });
  }

  // 3. One-Image-Mail
  if (imageCount > 0 && wordCount < 50) {
    findings.push({
      rule: 'one-image-mail',
      severity: 'error',
      points: 25,
      message: 'Kein nennenswerter Text-Anteil — wahrscheinlich Bild-only-Email',
      detail: `Nur ${wordCount} Wörter im Body`,
    });
  }

  // 4. Spam-Trigger-Wörter (case-insensitive, in Body und Subject)
  const haystack = `${input.subject}\n${input.preheader ?? ''}\n${text}`.toLowerCase();
  const triggerHits: string[] = [];
  for (const trig of ALL_SPAM_TRIGGERS) {
    if (haystack.includes(trig.toLowerCase())) triggerHits.push(trig);
  }
  if (triggerHits.length > 0) {
    findings.push({
      rule: 'spam-triggers',
      severity: triggerHits.length > 2 ? 'error' : 'warning',
      points: triggerHits.length * 5,
      message: `${triggerHits.length} Spam-Trigger-Wort(e) gefunden`,
      detail: triggerHits.join(', '),
    });
  }

  // 5. Subject-Length
  const subjectLen = input.subject.length;
  if (subjectLen > 60) {
    findings.push({
      rule: 'subject-length',
      severity: 'warning',
      points: 5,
      message: `Subject zu lang (${subjectLen} Zeichen, Empfehlung ≤ 50)`,
    });
  } else if (subjectLen < 10) {
    findings.push({
      rule: 'subject-too-short',
      severity: 'warning',
      points: 8,
      message: `Subject sehr kurz (${subjectLen} Zeichen)`,
    });
  }

  // 6. ALL-CAPS-Wörter im Subject (außer Workspace-Terms)
  const subjectCapsWords = (input.subject.match(/\b[A-Z]{4,}\b/g) ?? []).filter(
    (w) => !brandSet.has(w),
  );
  if (subjectCapsWords.length > 0) {
    findings.push({
      rule: 'subject-allcaps',
      severity: 'error',
      points: subjectCapsWords.length * 8,
      message: `ALL-CAPS-Wort(e) im Subject: ${subjectCapsWords.join(', ')}`,
    });
  }

  // 7. Exclamation marks im Subject
  const exclamations = (input.subject.match(/!/g) ?? []).length;
  if (exclamations > 1) {
    findings.push({
      rule: 'subject-exclamations',
      severity: 'warning',
      points: exclamations * 4,
      message: `${exclamations} Ausrufezeichen im Subject`,
    });
  }

  // 8. Gmail-Clipping (>102KB)
  if (htmlBytes > GMAIL_CLIPPING_BYTES) {
    findings.push({
      rule: 'gmail-clipping',
      severity: 'error',
      points: 20,
      message: `HTML-Größe ${(htmlBytes / 1024).toFixed(0)} KB überschreitet Gmail-Clipping-Grenze (102 KB)`,
    });
  }

  // 9. Plain-Text-Fallback
  const hasPlainText = !!(input.text && input.text.trim().length > 50);
  if (!hasPlainText) {
    findings.push({
      rule: 'missing-plaintext',
      severity: 'error',
      points: 15,
      message: 'Plain-Text-Variante fehlt oder zu kurz',
    });
  }

  // 10. List-Unsubscribe
  if (input.hasListUnsubscribeHeader === false) {
    findings.push({
      rule: 'missing-list-unsubscribe',
      severity: 'error',
      points: 20,
      message: 'List-Unsubscribe-Header fehlt — Pflicht für Bulk-Mails (RFC 8058)',
    });
  }

  // 11. Zu viele Links
  if (linkCount > 15) {
    findings.push({
      rule: 'too-many-links',
      severity: 'warning',
      points: 10,
      message: `${linkCount} Links — viele Links sind ein Spam-Signal`,
    });
  }

  // 12. Relative URLs (Mustache-Platzhalter {{...}} sind erlaubt — werden vor Versand substituiert)
  const relativeLinks = links.filter((el) => {
    const href = $(el).attr('href') ?? '';
    if (href.startsWith('http')) return false;
    if (href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('#')) return false;
    if (/^\{\{[\w_]+\}\}/.test(href)) return false; // Platzhalter
    return true;
  }).length;
  if (relativeLinks > 0) {
    findings.push({
      rule: 'relative-urls',
      severity: 'error',
      points: relativeLinks * 10,
      message: `${relativeLinks} relative URL(s) — Email braucht absolute URLs`,
    });
  }

  // 13. cid:-Inline-Attachments
  const cidImages = images.filter((el) => ($(el).attr('src') ?? '').startsWith('cid:')).length;
  if (cidImages > 0) {
    findings.push({
      rule: 'cid-inline',
      severity: 'error',
      points: 15,
      message: `${cidImages} cid:-Image(s) — wir hosten alle Bilder absolut`,
    });
  }

  const score = findings.reduce((s, f) => s + f.points, 0);
  const verdict: 'ship' | 'fix' | 'reject' =
    score >= REJECT_THRESHOLD ? 'reject' : score >= SHIP_THRESHOLD ? 'fix' : 'ship';

  return {
    score,
    verdict,
    findings,
    metrics: {
      htmlBytes,
      textBytes,
      imageCount,
      linkCount,
      textWords: wordCount,
      imageTextRatio,
      subjectLength: subjectLen,
      subjectAllCapsWords: subjectCapsWords.length,
      subjectExclamations: exclamations,
      spamTriggerHits: triggerHits,
      missingAltTags: missingAlt,
      hasListUnsubscribe: input.hasListUnsubscribeHeader !== false,
      hasPlainText,
    },
  };
}
