import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import crypto from 'crypto';
import dotenv from 'dotenv';
import imaps from 'imap-simple';
import { simpleParser } from 'mailparser';
import nodemailer from 'nodemailer';

dotenv.config();

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();

app.use(express.json({ limit: '5mb' }));

function imapConfig({ user, password, host, port, tls }) {
  return {
    imap: {
      user,
      password,
      host,
      port: Number(port || 993),
      tls: tls !== false,
      authTimeout: 10000,
      connTimeout: 15000,
      tlsOptions: { rejectUnauthorized: false }
    }
  };
}

function extractSenderEmail(value) {
  const raw = String(value || '').trim();
  const match = raw.match(/<([^>]+)>/);
  return (match?.[1] || raw).trim().toLowerCase();
}

function gravatarUrl(senderValue) {
  const email = extractSenderEmail(senderValue);
  if (!email || !email.includes('@')) return '';
  const hash = crypto.createHash('md5').update(email).digest('hex');
  return `https://www.gravatar.com/avatar/${hash}?s=64&d=404`;
}

function flattenBoxes(tree, prefix = '') {
  const names = [];
  for (const key of Object.keys(tree || {})) {
    const box = tree[key];
    const delimiter = box?.delimiter || '/';
    const full = prefix ? `${prefix}${delimiter}${key}` : key;
    if (!box?.attribs || !box.attribs.includes('\\Noselect')) names.push(full);
    if (box?.children) names.push(...flattenBoxes(box.children, full));
  }
  return names;
}

function parseHeaderRow(res) {
  const header = res.parts.find((p) => p.which === 'HEADER.FIELDS (FROM TO SUBJECT DATE)')?.body || {};
  const textPart = res.parts.find((p) => p.which === 'TEXT');
  let snippet = '';
  if (textPart?.body) {
    snippet = String(textPart.body)
      .replace(/(<([^>]+)>)/gi, '')
      .replace(/\s+/g, ' ')
      .trim()
      .substring(0, 120);
    if (snippet) snippet += '...';
  }
  const sender = header.from ? header.from[0] : 'Unknown';
  return {
    id: res.attributes.uid,
    subject: header.subject ? header.subject[0] : '(No Subject)',
    sender,
    avatar: gravatarUrl(sender),
    time: header.date ? new Date(header.date[0]).toLocaleString() : '',
    unread: !res.attributes.flags.includes('\\Seen'),
    snippet
  };
}

app.post('/api/webmail/connect', async (req, res) => {
  const { user, password, host } = req.body;
  if (!user || !password || !host) return res.status(400).json({ error: 'Missing credentials' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    const boxes = await connection.getBoxes();
    const folderNames = flattenBoxes(boxes);

    const folders = [];
    for (const name of folderNames) {
      try {
        const box = await connection.openBox(name, true);
        folders.push({ name, total: box.messages.total || 0, unseen: box.messages.unseen || 0 });
      } catch {
        folders.push({ name, total: 0, unseen: 0 });
      }
    }

    res.json({ success: true, folders });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to connect to IMAP server' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/inbox', async (req, res) => {
  const {
    user, password, host, folder = 'INBOX',
    offset = 0, limit = 50, fetchAll = false,
    direction = 'newest', sync = false, sinceUid = 0, knownUids = []
  } = req.body;

  if (!user || !password || !host) return res.status(400).json({ error: 'Missing credentials' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    await connection.openBox(folder);

    const safeOffset = Math.max(0, Number(offset) || 0);
    const safeLimit = Math.max(1, Math.min(200, Number(limit) || 50));
    const loadAll = fetchAll === true || String(fetchAll).toLowerCase() === 'true';
    const oldest = String(direction).toLowerCase() === 'oldest';
    const syncMode = sync === true || String(sync).toLowerCase() === 'true';

    const fetchOptions = {
      bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'],
      struct: true,
      markSeen: false
    };

    const allUids = await new Promise((resolve, reject) => {
      connection.imap.search(['ALL'], (err, results) => {
        if (err) return reject(err);
        resolve(Array.isArray(results) ? results : []);
      });
    });

    const sortedUids = [...allUids].sort((a, b) => a - b);
    const total = sortedUids.length;

    if (syncMode) {
      const since = Math.max(0, Number(sinceUid) || 0);
      const known = new Set(Array.isArray(knownUids) ? knownUids.map((u) => Number(u)).filter((u) => Number.isFinite(u) && u > 0) : []);
      const current = new Set(sortedUids);
      const deletedUids = [...known].filter((uid) => !current.has(uid));
      const newUidList = sortedUids.filter((uid) => uid > since);

      let newResults = [];
      if (newUidList.length > 0) {
        newResults = await connection.search([['UID', newUidList.join(',')]], fetchOptions);
      }

      const newEmails = newResults.map(parseHeaderRow).sort((a, b) => Number(a.id || 0) - Number(b.id || 0));
      return res.json({ sync: true, total, newEmails, deletedUids });
    }

    let targetUids = sortedUids;
    if (!loadAll) {
      if (oldest) {
        const start = safeOffset;
        const end = Math.min(total, start + safeLimit);
        targetUids = sortedUids.slice(start, end);
      } else {
        const end = Math.max(0, total - safeOffset);
        const start = Math.max(0, end - safeLimit);
        targetUids = sortedUids.slice(start, end);
      }
    }

    if (targetUids.length === 0) {
      return res.json({ emails: [], total, hasMore: false, nextOffset: safeOffset });
    }

    const results = await connection.search([['UID', targetUids.join(',')]], fetchOptions);
    const emails = results.map(parseHeaderRow).sort((a, b) => Number(a.id || 0) - Number(b.id || 0));
    const nextOffset = loadAll ? total : Math.min(total, safeOffset + targetUids.length);
    const hasMore = loadAll ? false : nextOffset < total;
    res.json({ emails, total, hasMore, nextOffset });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to fetch inbox' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/message', async (req, res) => {
  const { user, password, host, folder = 'INBOX', uid } = req.body;
  if (!user || !password || !host || !uid) return res.status(400).json({ error: 'Missing credentials or UID' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    await connection.openBox(folder);
    const results = await connection.search([['UID', uid]], { bodies: [''], markSeen: true });
    if (!results.length) return res.status(404).json({ error: 'Message not found' });

    const rawEmail = results[0].parts.find((p) => p.which === '').body;
    const parsed = await simpleParser(rawEmail);
    const hasHtmlBody = typeof parsed.html === 'string' && parsed.html.trim().length > 0;
    let html = parsed.html || parsed.textAsHtml || parsed.text || '';
    if (typeof html !== 'string') html = String(html || '');

    const inlineAttachments = (parsed.attachments || []).filter((a) => a?.content && (a?.cid || a?.contentId));
    if (html && inlineAttachments.length > 0) {
      for (const attachment of inlineAttachments) {
        const rawCid = String(attachment.cid || attachment.contentId || '').trim();
        const cid = rawCid.replace(/^<|>$/g, '');
        if (!cid) continue;
        const mime = String(attachment.contentType || 'application/octet-stream');
        const base64 = Buffer.from(attachment.content).toString('base64');
        const safeCid = cid.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        html = html.replace(new RegExp(`cid:${safeCid}`, 'gi'), `data:${mime};base64,${base64}`);
      }
    }

    res.json({
      id: uid,
      subject: parsed.subject,
      sender: parsed.from ? parsed.from.text : '',
      to: parsed.to ? parsed.to.text : '',
      date: parsed.date,
      html,
      isPlainText: !hasHtmlBody,
      attachments: (parsed.attachments || []).map((a) => ({ filename: a.filename, size: a.size, contentType: a.contentType }))
    });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to fetch message' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/send', async (req, res) => {
  const { user, password, host, port, to, subject, body } = req.body;
  if (!user || !password || !host || !to) return res.status(400).json({ error: 'Missing arguments' });

  try {
    const transporter = nodemailer.createTransport({
      host,
      port: Number(port || 465),
      secure: Number(port || 465) === 465,
      auth: { user, pass: password },
      tls: { rejectUnauthorized: false }
    });

    await transporter.sendMail({ from: user, to, subject: subject || 'No Subject', html: body });
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to send message' });
  }
});

app.post('/api/webmail/delete', async (req, res) => {
  const { user, password, host, folder = 'INBOX', uid } = req.body;
  if (!user || !password || !host || !uid) return res.status(400).json({ error: 'Missing credentials or UID' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    await connection.openBox(folder);
    await connection.addFlags(uid, '\\Deleted');
    await new Promise((resolve, reject) => connection.imap.expunge((err) => (err ? reject(err) : resolve())));
    res.json({ success: true, uid: Number(uid) });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to delete message' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/delete-bulk', async (req, res) => {
  const { user, password, host, folder = 'INBOX', uids } = req.body;
  const uidList = Array.isArray(uids) ? uids.map((u) => Number(u)).filter((u) => Number.isFinite(u) && u > 0) : [];
  if (!user || !password || !host || uidList.length === 0) return res.status(400).json({ error: 'Missing credentials or UIDs' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    await connection.openBox(folder);
    await connection.addFlags(uidList, '\\Deleted');
    await new Promise((resolve, reject) => connection.imap.expunge((err) => (err ? reject(err) : resolve())));
    res.json({ success: true, deleted: uidList.length, uids: uidList });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to delete selected messages' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/empty-trash', async (req, res) => {
  const { user, password, host, folder = 'INBOX.Trash' } = req.body;
  if (!user || !password || !host) return res.status(400).json({ error: 'Missing credentials' });

  let connection;
  try {
    connection = await imaps.connect(imapConfig(req.body));
    await connection.openBox(folder);
    const uids = await new Promise((resolve, reject) => {
      connection.imap.search(['ALL'], (err, results) => {
        if (err) return reject(err);
        resolve(Array.isArray(results) ? results : []);
      });
    });

    if (!uids.length) return res.json({ success: true, deleted: 0 });

    await connection.addFlags(uids, '\\Deleted');
    await new Promise((resolve, reject) => connection.imap.expunge((err) => (err ? reject(err) : resolve())));
    res.json({ success: true, deleted: uids.length });
  } catch (err) {
    res.status(500).json({ error: err.message || 'Failed to empty trash' });
  } finally {
    if (connection) connection.end();
  }
});

app.post('/api/webmail/quota', async (_req, res) => {
  // Safe default for standalone starter. Can be enhanced for provider-specific quota later.
  res.json({ supported: false });
});

app.post('/api/webmail/folder-sizes', async (_req, res) => {
  // Safe default for standalone starter. Can be enhanced when needed.
  res.json({ success: true, sizes: {} });
});

if (process.env.NODE_ENV === 'production') {
  app.use(express.static(path.join(__dirname, 'dist')));
  app.get('*', (_req, res) => {
    res.sendFile(path.join(__dirname, 'dist', 'index.html'));
  });
}

const PORT = Number(process.env.PORT || 3001);
app.listen(PORT, () => {
  console.log(`Webmail server running on http://localhost:${PORT}`);
});
