// send.js
// Usage: node send.js 3   (3 = number of threads)

const fs = require('fs');
const path = require('path');
const nodemailer = require('nodemailer');
const { ProxyAgent } = require('proxy-agent');
const { promisify } = require('util');
const sleep = promisify(setTimeout);
const puppeteer = require('puppeteer');

// ===== CLI threads =====
let numberOfThreads = parseInt(process.argv[2], 10) || 1;

// ===== Helper: safe file read (single-line) =====
function readSingleLineFile(p, fallback = null) {
  try {
    if (fs.existsSync(p)) {
      const v = fs.readFileSync(p, 'utf8').trim();
      return v || fallback;
    }
  } catch (_) {}
  return fallback;
}

// ===== Load From / Reply-To (overrides per run) =====
const OVERRIDE_FROM_EMAIL = readSingleLineFile('from_email.txt', null);
const OVERRIDE_REPLY_TO   = readSingleLineFile('reply_to.txt', null);

// ===== Load SMTP list =====
let smtpServers = [];
if (fs.existsSync('smtp_servers.txt')) {
  smtpServers = fs.readFileSync('smtp_servers.txt', 'utf8')
    .split('\n')
    .map(s => s.trim())
    .filter(Boolean)
    .map(line => {
      const [host, port, user, pass] = line.split('|');
      return { host, port: parseInt(port, 10), user, pass };
    });
}
if (smtpServers.length === 0) {
  console.log('[Info] No smtp_servers.txt found or empty. Using localhost:25 without auth.');
  smtpServers = [{ host: '127.0.0.1', port: 25, user: null, pass: null, local: true }];
}

let currentSmtpIndex = 0;
let emailCounter = 0;
const maxEmailsPerSmtp = 1;

// ===== Rotation =====
const getNextSmtpServer = () => {
  currentSmtpIndex = (currentSmtpIndex + 1) % smtpServers.length;
  emailCounter = 0;
  return smtpServers[currentSmtpIndex];
};

// ===== Transport factory =====
const createTransporter = (smtpConfig, proxy) => {
  const base = {
    host: smtpConfig.host,
    port: smtpConfig.port,
    secure: smtpConfig.port === 465
  };
  if (smtpConfig.user && smtpConfig.pass) {
    base.auth = { user: smtpConfig.user, pass: smtpConfig.pass };
  }
  if (proxy) base.agent = new ProxyAgent(proxy);
  return nodemailer.createTransport(base);
};

// ===== Proxies (optional) =====
let proxies = [];
if (fs.existsSync('proxies.txt')) {
  proxies = fs.readFileSync('proxies.txt', 'utf8')
    .split('\n').map(s => s.trim()).filter(Boolean);
}
const getRandomProxy = () => {
  if (!proxies.length) return null;
  const proxy = proxies[Math.floor(Math.random() * proxies.length)];
  const [host, port, user, pass] = proxy.split(':');
  if (user && pass) return `socks5://${user}:${pass}@${host}:${port}`;
  return `socks5://${host}:${port}`;
};

// ===== Inputs =====
const emailAddresses = fs.readFileSync('emails.txt', 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
const subjects       = fs.readFileSync('subjects.txt', 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
const names          = fs.readFileSync('names.txt', 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
const links          = fs.readFileSync('links.txt', 'utf8').split('\n').map(s => s.trim()).filter(Boolean);

// ===== Message templates loader (body email) =====
function loadMessageTemplates() {
  const dir = path.resolve('./messages');
  let templates = [];

  // 1) Prefer folder ./messages/*.(txt|html)
  try {
    if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
      const files = fs.readdirSync(dir)
        .filter(f => /\.(txt|html)$/i.test(f))
        .map(f => path.join(dir, f));
      for (const f of files) {
        try {
          const content = fs.readFileSync(f, 'utf8').trim();
          if (content) templates.push(content);
        } catch {}
      }
    }
  } catch {}

  // 2) Fallback: message.txt with '---' separator lines
  if (templates.length === 0 && fs.existsSync('message.txt')) {
    try {
      const raw = fs.readFileSync('message.txt', 'utf8');
      const parts = raw.split(/^\s*-{3,}\s*$/m)
        .map(s => s.trim())
        .filter(Boolean);
      if (parts.length) templates = parts;
      else if (raw.trim()) templates = [raw.trim()];
    } catch {}
  }

  if (templates.length === 0) {
    templates = ['Hello']; // last resort
  }
  console.log(`[Info] Loaded ${templates.length} message template(s).`);
  return templates;
}
const MESSAGE_TEMPLATES = loadMessageTemplates();

// ===== Attach templates loader (HTML ? PDF) =====
function loadAttachTemplates() {
  const dir = path.resolve('./attach');
  let templates = [];
  try {
    if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
      const files = fs.readdirSync(dir)
        .filter(f => /\.(txt|html)$/i.test(f))
        .map(f => path.join(dir, f));
      for (const f of files) {
        try {
          const content = fs.readFileSync(f, 'utf8').trim();
          if (content) templates.push({ name: path.basename(f), content });
        } catch {}
      }
    }
  } catch {}
  console.log(`[Info] Loaded ${templates.length} attach template(s) from /attach.`);
  return templates;
}
const ATTACH_TEMPLATES = loadAttachTemplates();

// ===== Helpers for placeholders =====
const generateRandomNumber = (n) => {
  let str = '';
  for (let i = 0; i < n; i++) str += Math.floor(Math.random() * 10);
  return str;
};
const generateRandomString = (n) =>
  Array.from({ length: n }, () => String.fromCharCode(97 + Math.floor(Math.random() * 26))).join('');

const extractDomain = (email) => (email.split('@')[1] || '');
const formatDate = () => {
  const now = new Date();
  const dd = String(now.getDate()).padStart(2, '0');
  const mm = String(now.getMonth() + 1).toString().padStart(2, '0');
  const yyyy = now.getFullYear();
  return `${dd}.${mm}.${yyyy}`;
};

// Replace placeholders in message/subject
function expandPlaceholders(str, { recipient, domain, date, link }) {
  return str
    .replace(/#RANDOMNUM\((\d+)\)#/g, (_, n) => generateRandomNumber(parseInt(n, 10)))
    .replace(/#RANDOM\((\d+)\)#/g,   (_, n) => generateRandomString(parseInt(n, 10)))
    .replace(/#EMAIL#/g, recipient)
    .replace(/#DOMAIN#/g, domain)
    .replace(/#DATE#/g, date)
    // accept both #LINK# and #LINKS#
    .replace(/#LINKS#/g, link)
    .replace(/#LINK#/g, link);
}

// Create a per-email PDF from an attach template
async function renderPdfFromHtml(htmlString, outPath) {
  // minimal HTML wrapper if plain text
  const isLikelyHtml = /<\s*html[\s>]/i.test(htmlString) || /<\s*body[\s>]/i.test(htmlString);
  const html = isLikelyHtml ? htmlString : `<!doctype html><html><head><meta charset="utf-8"></head><body><pre style="font-family:system-ui,Arial,Helvetica,sans-serif;white-space:pre-wrap">${htmlEscape(htmlString)}</pre></body></html>`;

  const browser = await puppeteer.launch({
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'domcontentloaded' });
    await page.emulateMediaType('screen');
    await page.pdf({
      path: outPath,
      format: 'A4',
      printBackground: true,
      margin: { top: '12mm', right: '12mm', bottom: '12mm', left: '12mm' }
    });
  } finally {
    await browser.close();
  }
}

function htmlEscape(s) {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

// Prepare attachment PDF for a recipient (returns {filename, path} or null)
async function buildPdfAttachmentFor(recipient, link) {
  if (!ATTACH_TEMPLATES.length) return null;

  const choice = ATTACH_TEMPLATES[Math.floor(Math.random() * ATTACH_TEMPLATES.length)];
  const domain = extractDomain(recipient);
  const date = formatDate();
  const filled = expandPlaceholders(choice.content, {
    recipient,
    domain,
    date,
    link
  });

  // ensure tmp dir
  const tmpDir = path.resolve('./tmp_pdfs');
  if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });

  const safeEmail = recipient.replace(/[^a-zA-Z0-9_.-]/g, '_');
  const pdfName = `Info_${safeEmail}.pdf`;
  const pdfPath = path.join(tmpDir, pdfName);

  await renderPdfFromHtml(filled, pdfPath);
  return { filename: pdfName, path: pdfPath, contentType: 'application/pdf' };
}

// ===== Core sender per thread =====
const sendEmail = async (threadIndex) => {
  let smtpConfig = smtpServers[currentSmtpIndex];
  let proxy = getRandomProxy();
  let transporter = createTransporter(smtpConfig, proxy);

  for (let j = threadIndex; j < emailAddresses.length; j += numberOfThreads) {
    const recipient = emailAddresses[j];
    if (!recipient) continue;

    const domain = extractDomain(recipient);
    const date = formatDate();
    const link = links[Math.floor(Math.random() * links.length)] || '';

    // Subject / name
    let subject = subjects[Math.floor(Math.random() * subjects.length)] || '(no subject)';
    subject = expandPlaceholders(subject, { recipient, domain, date, link });

    let name = names[Math.floor(Math.random() * names.length)] || 'Support';
    name = expandPlaceholders(name, { recipient, domain, date, link });

    // Pick a random template each email (body)
    let messageTemplate = MESSAGE_TEMPLATES[Math.floor(Math.random() * MESSAGE_TEMPLATES.length)];
    let message = expandPlaceholders(messageTemplate, { recipient, domain, date, link });

    // From/Reply-To logic (file overrides if present; otherwise SMTP user)
    const fromEmail = OVERRIDE_FROM_EMAIL || smtpConfig.user || 'no-reply@localhost';
    const replyToEmail = OVERRIDE_REPLY_TO || fromEmail;

    // Build PDF attachment from /attach
    let attachments = [];
    try {
      const pdfAtt = await buildPdfAttachmentFor(recipient, link);
      if (pdfAtt) attachments.push(pdfAtt);
    } catch (e) {
      console.log(`[Warn] PDF render failed for ${recipient}: ${e && e.message ? e.message : e}`);
    }

    const mailOptions = {
      from: `${name} <${fromEmail}>`,
      to: recipient,
      subject: subject,
      html: message,
      replyTo: replyToEmail,
      attachments,
      headers: {
        'List-Unsubscribe':
          `<mailto:${fromEmail}?subject=unsubscribe>, <https://rowing-shop.de/unsubscribe?email=${encodeURIComponent(recipient)}>`
      }
    };

    try {
      await transporter.sendMail(mailOptions);
      const via = smtpConfig.local ? 'localhost' : smtpConfig.host;
      console.log(`[Sent] Email sent to ${recipient} by thread ${threadIndex} using ${via}${attachments.length ? ' (+PDF)' : ''}`);
      fs.appendFileSync('sent.txt', recipient + '\n');
      emailCounter++;

      if (emailCounter >= maxEmailsPerSmtp) {
        smtpConfig = getNextSmtpServer();
        proxy = getRandomProxy();
        transporter = createTransporter(smtpConfig, proxy);
        console.log(`[Info] Switched SMTP to ${smtpConfig.host || 'localhost'} after ${maxEmailsPerSmtp} emails`);
      }
    } catch (error) {
      console.log(`[Error] Error sending email to ${recipient}: ${error && error.message ? error.message : error}`);
      fs.appendFileSync('failed.txt', recipient + '\n');
      smtpConfig = getNextSmtpServer();
      proxy = getRandomProxy();
      transporter = createTransporter(smtpConfig, proxy);
      console.log(`[Info] Switched SMTP to ${smtpConfig.host || 'localhost'} due to an error`);
    }

    // small delay to avoid hammering
    await sleep(1000);
  }
};

// ===== Kick off threads =====
(async () => {
  for (let i = 0; i < numberOfThreads; i++) {
    sendEmail(i);
  }
})();
