// E-Mail transport: authenticated SMTP submission (send) + IMAP (receive).
//
// Deliverability by design: all mail is sent through the account's own
// submission server (MAIL_HOST) with SMTP AUTH, so the server applies its
// DKIM signature and uses its reputable IP/PTR. Combined with a From address
// on the same domain, this satisfies SPF and DKIM alignment — required here
// because the domain publishes SPF "-all" and DMARC "p=reject". Sending mail
// directly to the recipient MX (bypassing the submission server) would fail
// both and land in spam / get rejected, so we never do that.

const nodemailer = require('nodemailer');
const { ImapFlow } = require('imapflow');
const { simpleParser } = require('mailparser');
const config = require('./config');

// Read fresh on every call so an edit on the /einstellungen page takes effect
// immediately (values live in the DB now, not in process.env/.env).
function cfg() {
  const smtpPort = Number(config.get('MAIL_SMTP_PORT') || 587);
  return {
    host: config.get('MAIL_HOST') || '',
    smtpPort,
    imapPort: Number(config.get('MAIL_IMAP_PORT') || 993),
    user: config.get('MAIL_USER') || '',
    pass: config.get('MAIL_PASSWORD') || '',
    fromName: config.get('MAIL_FROM_NAME') || '',
    fromAddr: config.get('MAIL_FROM') || config.get('MAIL_USER') || '',
    mailbox: config.get('MAIL_IMAP_MAILBOX') || 'INBOX',
    // secure=true means implicit TLS (465); 587 uses STARTTLS (requireTLS).
    smtpSecure: smtpPort === 465,
  };
}

function isConfigured() {
  const c = cfg();
  return Boolean(c.host && c.user && c.pass);
}

// "Anzeigename <adresse>" for the From header.
function fromField() {
  const c = cfg();
  return c.fromName ? `${c.fromName} <${c.fromAddr}>` : c.fromAddr;
}
function fromAddress() {
  return cfg().fromAddr;
}

function buildTransport() {
  const c = cfg();
  return nodemailer.createTransport({
    host: c.host,
    port: c.smtpPort,
    secure: c.smtpSecure,
    requireTLS: !c.smtpSecure,          // enforce STARTTLS on 587
    auth: { user: c.user, pass: c.pass },
    // Present a sane FQDN in EHLO (matches the sending domain).
    name: (c.fromAddr.split('@')[1] || c.host || undefined),
  });
}

// Verify SMTP connectivity + credentials (used by a health check / startup log).
async function verify() {
  const t = buildTransport();
  try { await t.verify(); return true; } finally { t.close(); }
}

// Minimal HTML rendering of a plain-text body: escape, then turn newlines into
// explicit <br> tags. Sending multipart/alternative (text + html) reads as
// normal personal mail and avoids the "text-only, no html" heuristic some
// filters apply.
//
// The line breaks MUST be real <br> tags, not merely CSS white-space:pre-wrap
// on literal newlines: pre-wrap renders correctly on first receipt, but when
// the recipient's client (e.g. Outlook) quotes the message back to us it
// re-parses the HTML, drops the pre-wrap style and collapses the bare newlines
// into spaces — turning our carefully formatted reply into a run-on wall of
// text in the quoted thread. Explicit <br> survives that round-trip.
function textToHtml(text) {
  const esc = String(text || '')
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/\r\n?/g, '\n')
    .replace(/\n/g, '<br>\n');
  return `<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;color:#1a1a1a">${esc}</div>`;
}

// Send one message via authenticated submission. Returns nodemailer info
// (includes messageId). `attachments` is an array of {filename, path|content, contentType}.
async function sendMail({ to, subject, text, html, attachments, inReplyTo, references, cc, bcc }) {
  const t = buildTransport();
  try {
    const headers = {};
    if (inReplyTo) headers['In-Reply-To'] = inReplyTo;
    if (references) headers['References'] = references;
    return await t.sendMail({
      from: fromField(),
      to,
      cc: cc || undefined,
      bcc: bcc || undefined,
      subject: subject || '',
      text: text || '',
      html: html || textToHtml(text),
      attachments: attachments || [],
      headers,
      // Message-ID auto-generated on the From domain → clean threading + DMARC.
    });
  } finally {
    t.close();
  }
}

// Fetch messages from the mailbox with a UID strictly greater than `sinceUid`.
// Returns { messages: [...parsed...], maxUid }. Each parsed message exposes the
// UID plus the fields we persist. Guards the classic IMAP "N:*" quirk (a range
// whose start exceeds the highest UID still returns the last message) by
// filtering uid > sinceUid in code.
async function fetchSince(sinceUid = 0) {
  const c = cfg();
  const client = new ImapFlow({
    host: c.host, port: c.imapPort, secure: true,
    auth: { user: c.user, pass: c.pass },
    logger: false,
  });
  const messages = [];
  let maxUid = sinceUid;
  await client.connect();
  const lock = await client.getMailboxLock(c.mailbox);
  try {
    const box = client.mailbox;
    if (box && box.exists > 0) {
      const from = Math.max(1, Number(sinceUid) + 1);
      for await (const msg of client.fetch(`${from}:*`, { uid: true, source: true, flags: true }, { uid: true })) {
        if (msg.uid <= sinceUid) continue;            // dodge the N:* quirk
        if (msg.uid > maxUid) maxUid = msg.uid;
        let parsed = null;
        try { parsed = await simpleParser(msg.source); } catch (e) { parsed = null; }
        if (!parsed) continue;
        const fromV = (parsed.from && parsed.from.value && parsed.from.value[0]) || {};
        const toText = parsed.to && parsed.to.text ? parsed.to.text : '';
        messages.push({
          uid: msg.uid,
          messageId: stripBrackets(parsed.messageId),
          inReplyTo: stripBrackets(parsed.inReplyTo),
          references: Array.isArray(parsed.references)
            ? parsed.references.map(stripBrackets).join(' ')
            : stripBrackets(parsed.references),
          fromAddr: (fromV.address || '').toLowerCase(),
          fromName: fromV.name || '',
          toAddr: toText,
          subject: parsed.subject || '',
          text: parsed.text || (parsed.html ? htmlToText(parsed.html) : ''),
          html: parsed.html || '',
          date: parsed.date ? new Date(parsed.date) : new Date(),
          seen: !!(msg.flags && msg.flags.has && msg.flags.has('\\Seen')),
          attachments: (parsed.attachments || [])
            .filter((a) => a.content && a.filename)
            .map((a) => ({ filename: a.filename, contentType: a.contentType || 'application/octet-stream', content: a.content })),
        });
      }
    }
  } finally {
    lock.release();
    await client.logout().catch(() => {});
  }
  return { messages, maxUid };
}

function stripBrackets(v) {
  if (!v) return '';
  return String(v).replace(/[<>]/g, '').trim();
}

// Very small HTML→text fallback for html-only mails.
function htmlToText(html) {
  return String(html || '')
    .replace(/<\s*br\s*\/?>/gi, '\n')
    .replace(/<\s*\/p\s*>/gi, '\n\n')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

module.exports = {
  isConfigured,
  verify,
  sendMail,
  fetchSince,
  fromField,
  fromAddress,
  textToHtml,
  htmlToText,
  config: cfg,
};
