// send_domains.js
// Usage: node send_domains.js 4
// ENV: DEBUG_ATTACH=1  -> salveaza HTML/PDF temporar pentru debug.

const fs = require('fs');
const path = require('path');
const os = require('os');
const { promisify } = require('util');
const sleep = promisify(setTimeout);

const DEBUG_ATTACH = process.env.DEBUG_ATTACH === '1';

// ---------- Config ----------
const MAX_PER_DOMAIN_PER_HOUR = 500;            // fallback daca nu exista domain_limit.txt
const PER_DOMAIN_LIMIT_FILE   = 'domain_limit.txt';
const THREADS = parseInt(process.argv[2], 10) || 1;
const MAX_ENDPOINT_FAILS = 3;                   // dupa 3 e?ecuri, endpoint-ul iese din rota?ie
const MAX_ATTEMPTS_PER_EMAIL = 3;               // încercari pe email, pe endpoint-uri diferite

// ---------- Helpers ----------
function readLines(file) {
  if (!fs.existsSync(file)) return [];
  return fs.readFileSync(file, 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
}
function readSingle(file, fallback = null) {
  if (!fs.existsSync(file)) return fallback;
  const v = fs.readFileSync(file, 'utf8').trim();
  return v || fallback;
}
function extractDomain(email) {
  const p = (email || '').split('@');
  return p[1] || '';
}
function generateRandomNumber(n) {
  let s = '';
  for (let i = 0; i < n; i++) s += Math.floor(Math.random() * 10);
  return s;
}
function generateRandomString(n) {
  const a = [];
  for (let i = 0; i < n; i++) a.push(String.fromCharCode(97 + Math.floor(Math.random() * 26)));
  return a.join('');
}
function expandPlaceholders(str, { recipient, domain, date, link }) {
  const d = date || new Date();
  const dd = String(d.getDate()).padStart(2, '0');
  const mm = String(d.getMonth() + 1).toString().padStart(2, '0');
  const yyyy = d.getFullYear();
  const dateStr = `${dd}.${mm}.${yyyy}`;
  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, dateStr)
    .replace(/#LINKS?#/g, link);
}
function shuffle(arr) {
  const a = arr.slice();
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

// ---------- Inputs ----------
const domainEndpoints = readLines('hosts.txt'); // fiecare linie: https://domeniu.tld/path/send.php
if (domainEndpoints.length === 0) {
  console.error('[Error] hosts.txt este gol.');
  process.exit(1);
}
const emails   = readLines('emails.txt');
const subjects = readLines('subjects.txt');
const names    = readLines('names.txt');
const links    = readLines('links.txt');

// mesaje
function loadMessageTemplates() {
  const dir = path.resolve('./messages');
  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(content);
        } catch {}
      }
    }
  } catch {}
  if (templates.length === 0 && fs.existsSync('message.txt')) {
    const raw = fs.readFileSync('message.txt', 'utf8');
    const parts = raw.split(/^\s*-{3,}\s*$/m).map(s => s.trim()).filter(Boolean);
    templates = parts.length ? parts : (raw.trim() ? [raw.trim()] : []);
  }
  if (templates.length === 0) templates = ['Hello'];
  console.log(`[Info] Message templates: ${templates.length}`);
  return templates;
}
const MESSAGE_TEMPLATES = loadMessageTemplates();

// attach
function loadAttachTemplates() {
  const dir = path.resolve('./attach');
  const templates = [];
  try {
    if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
      const files = fs.readdirSync(dir)
        .filter(f => /\.(txt|html|xhtml)$/i.test(f))
        .map(f => path.join(dir, f));
      for (const f of files) {
        try {
          const content = fs.readFileSync(f, 'utf8');
          const isHtml = /\.(html|xhtml)$/i.test(f);
          templates.push({ content, isHtml, file: f });
        } catch {}
      }
    }
  } catch {}
  console.log(`[Info] Attach templates: ${templates.length}`);
  return templates;
}
const ATTACH_TEMPLATES = loadAttachTemplates();

// limita per domeniu (din fi?ier sau fallback)
let PER_DOMAIN_LIMIT = MAX_PER_DOMAIN_PER_HOUR;
const limitFromFile = readSingle(PER_DOMAIN_LIMIT_FILE, null);
if (limitFromFile && /^\d+$/.test(limitFromFile)) {
  PER_DOMAIN_LIMIT = parseInt(limitFromFile, 10);
}

// Starea endpoint-urilor
const endpointState = {}; // { [ep]: {count:0, fails:0, active:true} }
for (const ep of domainEndpoints) {
  endpointState[ep] = { count: 0, fails: 0, active: true };
}
let activeEndpoints = shuffle(domainEndpoints.slice());

// ---------- Puppeteer ----------
let BROWSER = null;
async function ensureBrowser() {
  if (BROWSER) return BROWSER;
  const puppeteer = require('puppeteer');
  BROWSER = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });
  return BROWSER;
}

async function createPdfAttachment(recipient, link) {
  if (ATTACH_TEMPLATES.length === 0) {
    console.log('[Info] Fara /attach — trimit fara ata?ament.');
    return null;
  }
  const tpl = ATTACH_TEMPLATES[Math.floor(Math.random() * ATTACH_TEMPLATES.length)];
  const domain = extractDomain(recipient);
  let body = expandPlaceholders(tpl.content, { recipient, domain, date: new Date(), link });

  let html;
  if (tpl.isHtml) {
    html = body;
  } else {
    html = `<!doctype html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<style>body{font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#111}.wrap{max-width:800px;margin:40px auto;line-height:1.5}img{max-width:100%}</style>
</head>
<body><div class="wrap">${body.split('\n').map(l=>l.trim()).join('<br>')}</div></body>
</html>`;
  }

  let tempDir = null;
  try {
    const browser = await ensureBrowser();
    const page = await browser.newPage();
    await page.setViewport({ width: 1200, height: 900, deviceScaleFactor: 1 });
    await page.setContent(
      /^<!doctype|<html[\s>]/i.test(html) ? html : `<!doctype html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`,
      { waitUntil: 'load' }
    );
    await page.emulateMediaType('screen');

    const rawPdf = await page.pdf({
      format: 'A4',
      printBackground: true,
      margin: { top: '10mm', right: '10mm', bottom: '12mm', left: '10mm' }
    });
    await page.close();

    const buf = Buffer.isBuffer(rawPdf) ? rawPdf : Buffer.from(rawPdf);

    console.log('[debug] pdf bytes =', buf.length);
    console.log('[debug] pdf base64 (first 20) =', buf.toString('base64').slice(0, 20), '...');

    if (DEBUG_ATTACH) {
      tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'attachdbg-'));
      fs.writeFileSync(path.join(tempDir, `preview_${Date.now()}.pdf`), buf);
      fs.writeFileSync(path.join(tempDir, `preview_${Date.now()}.html`), html, 'utf8');
      console.log('[Debug] Saved temp attach preview in:', tempDir);
    }

    return {
      filename: `Pago_${Date.now()}.pdf`,
      buffer: buf,
      mime: 'application/pdf',
      _tempDir: tempDir
    };
  } catch (err) {
    console.log('[Warn] PDF render failed:', err.message || err);
    if (tempDir) try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
    return null;
  }
}

// ---------- Endpoint rota?ie/utilitare ----------
function pruneInactive() {
  activeEndpoints = activeEndpoints.filter(ep => endpointState[ep]?.active);
}
function disableEndpoint(ep, reason) {
  if (!endpointState[ep] || !endpointState[ep].active) return;
  endpointState[ep].active = false;
  console.log(`[Info] Endpoint scos din rota?ie: ${ep} (${reason})`);
  pruneInactive();
}
function pickRandomEndpoint() {
  if (activeEndpoints.length === 0) return null;
  const idx = Math.floor(Math.random() * activeEndpoints.length);
  return activeEndpoints[idx];
}

// ---------- POST catre send.php ----------
async function postToEndpoint(endpointUrl, payload) {
  const res = await fetch(endpointUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const text = await res.text();
  let json = {};
  try { json = JSON.parse(text); } catch {}
  return { status: res.status, text, json };
}

// ---------- Worker ----------
async function worker(id) {
  for (let i = id; i < emails.length; i += THREADS) {
    const recipient = emails[i];
    if (!recipient) continue;

    // pâna la MAX_ATTEMPTS_PER_EMAIL încercari pe endpoint-uri diferite
    let attempts = 0;
    let delivered = false;
    const tried = new Set();

    while (attempts < MAX_ATTEMPTS_PER_EMAIL && !delivered) {
      pruneInactive();
      if (activeEndpoints.length === 0) {
        console.log('[Info] Nu mai exista endpoint-uri active. Worker se opre?te.');
        return;
      }

      // alege random un endpoint neîncercat în aceasta bucla
      let endpoint = null;
      for (let spin = 0; spin < activeEndpoints.length; spin++) {
        const cand = pickRandomEndpoint();
        if (!cand) break;
        if (!tried.has(cand)) { endpoint = cand; break; }
      }
      if (!endpoint) {
        // daca toate active au fost încercate deja în cadrul acestui email
        endpoint = pickRandomEndpoint();
      }
      if (!endpoint) break; // safety

      tried.add(endpoint);
      attempts += 1;

      const st = endpointState[endpoint];
      if (!st || !st.active) continue;

      // verifica limita
      if (st.count >= PER_DOMAIN_LIMIT) {
        disableEndpoint(endpoint, `limita atinsa (${PER_DOMAIN_LIMIT}/ora)`);
        continue;
      }

      const link = links[Math.floor(Math.random() * links.length)] || '';
      const subject = expandPlaceholders(subjects[Math.floor(Math.random() * subjects.length)] || '(no subject)', {
        recipient, domain: extractDomain(recipient), date: new Date(), link
      });
      const name = expandPlaceholders(names[Math.floor(Math.random() * names.length)] || 'Support', {
        recipient, domain: extractDomain(recipient), date: new Date(), link
      });
      const messageTpl = MESSAGE_TEMPLATES[Math.floor(Math.random() * MESSAGE_TEMPLATES.length)];
      const htmlBody = expandPlaceholders(messageTpl, {
        recipient, domain: extractDomain(recipient), date: new Date(), link
      });

      const pdf = await createPdfAttachment(recipient, link);

      const host = new URL(endpoint).host.replace(/^www\./i,'');
      const payload = {
        to: recipient,
        subject,
        html: htmlBody,
        text: '',
        from_name: name,
        reply_to: `support@${host}`,
        list_unsub: `<mailto:support@${host}?subject=unsubscribe>, <https://${host}/unsubscribe?email=${encodeURIComponent(recipient)}>`
      };

      if (pdf && pdf.buffer && pdf.buffer.length > 0) {
        const buf = Buffer.isBuffer(pdf.buffer) ? pdf.buffer : Buffer.from(pdf.buffer);
        const b64 = buf.toString('base64');
        payload.attach_b64  = b64;
        payload.attach_name = (pdf.filename && String(pdf.filename)) || 'attachment.pdf';
        payload.attach_mime = (pdf.mime && String(pdf.mime)) || 'application/pdf';
        console.log('[debug] attach_b64 length =', b64.length);
      } else {
        console.log('[Info] Trimit fara ata?ament (pdf null/zero).');
      }

      try {
        const { status, json, text } = await postToEndpoint(endpoint, payload);
        if (json && json.ok) {
          st.count += 1;
          st.fails = 0; // reset pe succes
          delivered = true;
          console.log(`[OK] ${recipient} via ${endpoint} (count=${st.count})`);
          fs.appendFileSync('sent.txt', recipient + '\n');

          if (st.count >= PER_DOMAIN_LIMIT) {
            disableEndpoint(endpoint, `limita atinsa (${PER_DOMAIN_LIMIT}/ora)`);
          }
        } else {
          st.fails += 1;
          console.log(`[ERR] ${recipient} via ${endpoint} -> status=${status} body=${text} (fails=${st.fails})`);
          fs.appendFileSync('failed.txt', recipient + '\n');

          if (st.fails >= MAX_ENDPOINT_FAILS) {
            disableEndpoint(endpoint, `prea multe e?ecuri (${st.fails})`);
          }
        }
      } catch (e) {
        st.fails += 1;
        console.log(`[ERR] ${recipient} via ${endpoint} -> ${e.message || e} (fails=${st.fails})`);
        fs.appendFileSync('failed.txt', recipient + '\n');

        if (st.fails >= MAX_ENDPOINT_FAILS) {
          disableEndpoint(endpoint, `prea multe e?ecuri (${st.fails})`);
        }
      } finally {
        if (pdf && pdf._tempDir) {
          try { fs.rmSync(pdf._tempDir, { recursive: true, force: true }); } catch {}
        }
      }
    }

    await sleep(400); // mic delay
  }
}

// ---------- Main ----------
(async () => {
  try {
    try { await ensureBrowser(); } catch (e) { console.log('[Warn] Puppeteer launch fail:', e.message || e); }
    const tasks = [];
    for (let t = 0; t < THREADS; t++) tasks.push(worker(t));
    await Promise.all(tasks);
  } finally {
    try { if (BROWSER) { await BROWSER.close(); BROWSER = null; } } catch {}
  }
})();
