import net from 'node:net';
import tls from 'node:tls';
import crypto from 'node:crypto';
import { getSetting } from './db.js';
import { getSecret } from './secrets.js';
import { nowIso, sanitizeText, uuid } from './utils.js';

function fail(message) {
  throw new Error(message);
}

function wrapBase64(value) {
  return Buffer.from(String(value ?? ''), 'utf8').toString('base64').match(/.{1,76}/g)?.join('\r\n') || '';
}

function sanitizeHeader(value) {
  return String(value ?? '').replace(/[\r\n]+/g, ' ').trim();
}

class SmtpChannel {
  constructor(socket) {
    this.socket = socket;
    this.buffer = '';
    this.waiters = [];
    this.socket.setEncoding('utf8');
    this.socket.on('data', (chunk) => {
      this.buffer += chunk;
      this.flush();
    });
    this.socket.on('error', (error) => {
      while (this.waiters.length) this.waiters.shift().reject(error);
    });
    this.socket.on('close', () => {
      while (this.waiters.length) this.waiters.shift().reject(new Error('SMTP bağlantısı kapandı.'));
    });
  }

  flush() {
    const lines = this.buffer.split(/\r?\n/);
    this.buffer = lines.pop() || '';
    for (const line of lines) {
      if (!line) continue;
      const waiter = this.waiters[0];
      if (!waiter) continue;
      waiter.lines.push(line);
      if (/^\d{3} /.test(line)) {
        this.waiters.shift();
        const code = Number(line.slice(0, 3));
        waiter.resolve({ code, lines: waiter.lines, text: waiter.lines.join('\n') });
      }
    }
  }

  reply(timeoutMs = 15000) {
    return new Promise((resolve, reject) => {
      const waiter = { lines: [], resolve, reject };
      this.waiters.push(waiter);
      const timer = setTimeout(() => {
        const index = this.waiters.indexOf(waiter);
        if (index >= 0) this.waiters.splice(index, 1);
        reject(new Error('SMTP yanıt zaman aşımına uğradı.'));
      }, timeoutMs);
      const originalResolve = waiter.resolve;
      const originalReject = waiter.reject;
      waiter.resolve = (value) => { clearTimeout(timer); originalResolve(value); };
      waiter.reject = (error) => { clearTimeout(timer); originalReject(error); };
      this.flush();
    });
  }

  async command(text, accepted = [250]) {
    this.socket.write(`${text}\r\n`);
    const reply = await this.reply();
    if (!accepted.includes(reply.code)) fail(`SMTP komutu reddedildi (${reply.code}): ${reply.text}`);
    return reply;
  }

  end() {
    try { this.socket.end(); } catch { /* no-op */ }
  }
}

function connectSocket({ host, port, secure }) {
  return new Promise((resolve, reject) => {
    const options = { host, port, servername: host, rejectUnauthorized: true };
    const socket = secure ? tls.connect(options) : net.connect({ host, port });
    socket.once('connect', () => resolve(socket));
    socket.once('secureConnect', () => resolve(socket));
    socket.once('error', reject);
  });
}

async function upgradeTls(channel, host) {
  await channel.command('STARTTLS', [220]);
  const upgraded = tls.connect({ socket: channel.socket, servername: host, rejectUnauthorized: true });
  await new Promise((resolve, reject) => {
    upgraded.once('secureConnect', resolve);
    upgraded.once('error', reject);
  });
  return new SmtpChannel(upgraded);
}

export async function sendSmtpMessageWithConfig(options, message) {
  const host = String(options.host || '').trim();
  const port = Number(options.port || 587);
  const secure = Boolean(options.secure);
  const startTls = options.startTls == null ? !secure : Boolean(options.startTls);
  const username = String(options.username || '').trim();
  const password = String(options.password || '');
  const fromEmail = String(options.fromEmail || '').trim();
  const fromName = sanitizeHeader(options.fromName || 'OnlyCoin');
  const ehloName = sanitizeHeader(options.ehloName || 'onlycoin.local');
  const to = String(message.to || '').trim().toLowerCase();
  if (!host || !fromEmail || !to) throw new Error('SMTP sunucusu, gönderen ve alıcı e-posta adresi tanımlanmalıdır.');
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('SMTP portu geçersiz.');

  let channel = new SmtpChannel(await connectSocket({ host, port, secure }));
  const greeting = await channel.reply();
  if (greeting.code !== 220) fail(`SMTP bağlantısı reddedildi: ${greeting.text}`);
  await channel.command(`EHLO ${ehloName}`);
  if (!secure && startTls) {
    channel = await upgradeTls(channel, host);
    await channel.command(`EHLO ${ehloName}`);
  }
  if (username) {
    if (!password) throw new Error('SMTP parolası tanımlı değil.');
    await channel.command('AUTH LOGIN', [334]);
    await channel.command(Buffer.from(username).toString('base64'), [334]);
    await channel.command(Buffer.from(password).toString('base64'), [235]);
  }
  await channel.command(`MAIL FROM:<${fromEmail}>`);
  await channel.command(`RCPT TO:<${to}>`, [250, 251]);
  await channel.command('DATA', [354]);
  const boundary = `oc_${crypto.randomBytes(12).toString('hex')}`;
  const headers = [
    `From: ${fromName} <${fromEmail}>`,
    `To: <${sanitizeHeader(to)}>`,
    `Subject: =?UTF-8?B?${Buffer.from(message.subject, 'utf8').toString('base64')}?=`,
    `Date: ${new Date().toUTCString()}`,
    `Message-ID: <${crypto.randomUUID()}@${sanitizeHeader(host)}>`,
    'MIME-Version: 1.0',
    `Content-Type: multipart/alternative; boundary="${boundary}"`,
    '',
    `--${boundary}`,
    'Content-Type: text/plain; charset=UTF-8',
    'Content-Transfer-Encoding: base64',
    '',
    wrapBase64(message.text || ''),
    `--${boundary}`,
    'Content-Type: text/html; charset=UTF-8',
    'Content-Transfer-Encoding: base64',
    '',
    wrapBase64(message.html || `<pre>${message.text || ''}</pre>`),
    `--${boundary}--`,
    ''
  ];
  const payload = headers.join('\r\n').replace(/^\./gm, '..');
  channel.socket.write(`${payload}\r\n.\r\n`);
  const accepted = await channel.reply(30000);
  if (accepted.code !== 250) fail(`SMTP mesajı kabul edilmedi: ${accepted.text}`);
  try { await channel.command('QUIT', [221]); } catch { /* best effort */ }
  channel.end();
  return { accepted: true };
}

export async function sendSmtpMessage(db, message) {
  const enabled = Boolean(getSetting(db, 'smtp_enabled', false));
  if (!enabled) throw new Error('SMTP aktif değil.');
  return sendSmtpMessageWithConfig({
    host: getSetting(db, 'smtp_host', ''),
    port: getSetting(db, 'smtp_port', 587),
    secure: getSetting(db, 'smtp_secure', false),
    startTls: getSetting(db, 'smtp_starttls', !Boolean(getSetting(db, 'smtp_secure', false))),
    username: getSetting(db, 'smtp_username', ''),
    password: getSecret(db, 'smtp_password', process.env.SMTP_PASSWORD || ''),
    fromEmail: getSetting(db, 'smtp_from_email', ''),
    fromName: getSetting(db, 'smtp_from_name', 'OnlyCoin'),
    ehloName: getSetting(db, 'smtp_ehlo_name', 'onlycoin.local')
  }, message);
}

export async function testSmtpConfiguration(options, recipient) {
  return sendSmtpMessageWithConfig(options, {
    to: recipient,
    subject: 'OnlyCoin SMTP bağlantı testi',
    text: 'OnlyCoin kurulum sihirbazı SMTP bağlantısını ve e-posta teslimini başarıyla doğruladı.',
    html: '<h2>OnlyCoin SMTP Testi Başarılı</h2><p>Kurulum sihirbazı bu e-postayı canlı SMTP ayarlarınızla gönderdi.</p>'
  });
}

export function queueEmail(db, { to, subject, text = '', html = '', template = 'generic', metadata = {} }) {
  const email = String(to || '').trim().toLowerCase();
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return null;
  const id = uuid();
  db.prepare(`INSERT INTO email_outbox(id,to_email,subject,text_body,html_body,template,status,attempts,last_error,metadata_json,created_at,updated_at)
    VALUES(?,?,?,?,?,?, 'queued',0,'',?,?,?)`)
    .run(id, email, sanitizeText(subject, 180), String(text || ''), String(html || ''), sanitizeText(template, 80), JSON.stringify(metadata || {}), nowIso(), nowIso());
  return db.prepare('SELECT * FROM email_outbox WHERE id=?').get(id);
}

export async function deliverEmailById(db, id) {
  const row = db.prepare("SELECT * FROM email_outbox WHERE id=? AND status IN ('queued','failed')").get(id);
  if (!row) return { delivered: false, skipped: true };
  db.prepare("UPDATE email_outbox SET status='sending',attempts=attempts+1,updated_at=? WHERE id=?").run(nowIso(), id);
  try {
    await sendSmtpMessage(db, { to: row.to_email, subject: row.subject, text: row.text_body, html: row.html_body });
    db.prepare("UPDATE email_outbox SET status='sent',sent_at=?,last_error='',updated_at=? WHERE id=?").run(nowIso(), nowIso(), id);
    return { delivered: true };
  } catch (error) {
    db.prepare("UPDATE email_outbox SET status='failed',last_error=?,updated_at=? WHERE id=?").run(String(error.message || error).slice(0, 1000), nowIso(), id);
    return { delivered: false, error: error.message };
  }
}

export async function deliverPendingEmails(db, limit = 10) {
  if (!Boolean(getSetting(db, 'smtp_enabled', false))) return { delivered: 0, skipped: true };
  const rows = db.prepare("SELECT id FROM email_outbox WHERE status IN ('queued','failed') AND attempts<5 ORDER BY created_at LIMIT ?").all(Math.max(1, Math.min(100, Number(limit) || 10)));
  let delivered = 0;
  for (const row of rows) {
    const result = await deliverEmailById(db, row.id);
    if (result.delivered) delivered += 1;
  }
  return { delivered, attempted: rows.length };
}

export function emailOutbox(db, limit = 200) {
  return db.prepare('SELECT * FROM email_outbox ORDER BY created_at DESC LIMIT ?').all(Math.max(1, Math.min(1000, Number(limit) || 200)));
}
