#include "imapsynchronizer.h"
#include <QEventLoop>
#include <QTimer>
#include <QDebug>
#include <QRegularExpression>
#include "../../core/mailitem.h"
#include "../../core/models/account.h"
#include "../../core/models/folder.h"
#include "../../db/dao/folderdao.h"
#include "../../db/dao/mailitemdao.h"
#include "../../services/mimestorage.h"
#include <algorithm>

ImapSynchronizer::ImapSynchronizer(QObject* parent)
    : Synchronizer(parent)
{
}

bool ImapSynchronizer::initialize(const Account& account)
{
    m_account = account;
    const Account::ConnectionSettings& settings = account.connectionSettings();
    m_host = settings.incomingHost;
    m_port = settings.incomingPort;
    m_useSsl = settings.incomingSsl;
    m_username = settings.username;
    m_password = settings.password;
    m_authMethod = settings.authMethod;
    return true;
}

// Helper: connects and logs in to the IMAP server
bool ImapSynchronizer::connectAndLogin(ImapConnection& conn) const
{
    bool connected = false;
    bool loggedIn = false;
    QString response;

    // Connect to host
    QEventLoop connectLoop;
    QTimer connectTimer;
    bool connectTimeout = false;
    connect(&conn, &ImapConnection::connected, &connectLoop, [&]() {
        connected = true;
        connectLoop.quit();
    });
    connect(&conn, &ImapConnection::errorOccurred, this, [&](const QString& err){
        qWarning() << "IMAP connection error:" << err;
        connected = false;
        connectLoop.quit();
    });
    connectTimer.setSingleShot(true);
    connect(&connectTimer, &QTimer::timeout, &connectLoop, [&](){
        connectTimeout = true;
        connectLoop.quit();
    });
    connectTimer.start(30000); // 30 seconds
    conn.connectToHost(m_host, m_port, m_useSsl);
    connectLoop.exec();
    if (connectTimeout) {
        qWarning() << "Connection timeout to" << m_host;
        return false;
    }
    if (!connected) return false;

    // Wait for server greeting (first untagged response)
    QEventLoop greetLoop;
    bool gotGreeting = false;
    QMetaObject::Connection greetConn = connect(&conn, &ImapConnection::untaggedResponse,
        [&](const QString& line) {
            Q_UNUSED(line);
            gotGreeting = true;
            greetLoop.quit();
        });
    QTimer greetTimer;
    greetTimer.setSingleShot(true);
    connect(&greetTimer, &QTimer::timeout, &greetLoop, [&]() {
        greetLoop.quit();
    });
    greetTimer.start(30000); // 30 seconds
    greetLoop.exec();
    QObject::disconnect(greetConn);
    if (!gotGreeting) {
        qWarning() << "Timeout waiting for server greeting";
        return false;
    }

    // Login
    QEventLoop loginLoop;
    QTimer loginTimer;
    bool loginTimeout = false;
    bool loginSuccess = false;
    // Already connected, now login
    conn.login(m_username, m_password, true, [&](bool ok, const QString& resp){
        if (ok) {
            loginSuccess = true;
        } else {
            qWarning() << "Login failed:" << resp;
        }
        loginLoop.quit();
    });
    connect(&conn, &ImapConnection::errorOccurred, this, [&](const QString& err){
        qWarning() << "IMAP error during login:" << err;
        loginLoop.quit();
    });
    loginTimer.setSingleShot(true);
    connect(&loginTimer, &QTimer::timeout, &loginLoop, [&](){
        loginTimeout = true;
        loginLoop.quit();
    });
    loginTimer.start(30000);
    loginLoop.exec();
    if (loginTimeout) {
        qWarning() << "Login timeout";
        return false;
    }
    if (!loginSuccess) {
        return false;
    }
    loggedIn = true;
    return true;
}

QVector<qint64> ImapSynchronizer::parseUidSearchResponse(const QString& response) const
{
    QVector<qint64> uids;
    QStringList lines = response.split(QStringLiteral("\n"));
    for (const QString& line : lines) {
        if (line.startsWith(QStringLiteral("* SEARCH"))) {
            QStringList parts = line.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
            for (int i = 2; i < parts.size(); ++i) { // skip "* SEARCH"
                bool ok;
                qint64 uid = parts[i].toLongLong(&ok);
                if (ok && uid > 0)
                    uids.append(uid);
            }
            break;
        }
    }
    return uids;
}
bool ImapSynchronizer::syncFolder(const Folder& folder)
{
    // First, get all UIDs from server to detect deletions
    QVector<qint64> allUids = fetchAllUids(QString::number(folder.id()));
    if (allUids.isEmpty()) {
        qWarning() << "Failed to fetch UIDs for folder" << folder.id();
        return false;
    }

    // Get local UIDs
    QVector<qint64> localUids = MailItemDao::getUidsForFolder(folder.id());
    
    // Find deleted UIDs (exist locally but not on server)
    QSet<qint64> serverUidSet(allUids.begin(), allUids.end());
    QVector<qint64> deletedUids;
    for (qint64 uid : localUids) {
        if (!serverUidSet.contains(uid)) {
            deletedUids.append(uid);
        }
    }
    
    // Delete locally if removed from server
    bool success = true;
    for (qint64 uid : deletedUids) {
        if (!MailItemDao::removeByUid(folder.id(), uid)) {
            qWarning() << "Failed to delete local mail item UID" << uid;
            success = false;
        } else {
            qDebug() << "Removed local mail item UID" << uid << "(deleted on server)";
        }
    }

    // Now fetch new/changed messages
    const qint64 sinceUid = MailItemDao::maxUidForFolder(folder.id()).value_or(0);
    QVector<MailItem> items = fetchMailItems(QString::number(folder.id()), sinceUid);
    for (MailItem &item : items) {
        if (!persistFetchedItem(folder, item)) success = false;
    }
    
    // Also check for flag changes on existing messages
    if (!allUids.isEmpty()) {
        // Fetch flags for all known UIDs (in batches)
        const int batchSize = 100;
        for (int i = 0; i < allUids.size(); i += batchSize) {
            QVector<qint64> batch = allUids.mid(i, qMin(batchSize, allUids.size() - i));
            QStringList batchList;
            for (qint64 uid : batch) batchList << QString::number(uid);
            QString uidList = batchList.join(",");
            
            ImapConnection conn;
            if (!connectAndLogin(conn)) continue;
            
            // SELECT folder
            QString selectCommand = QString("SELECT \"%1\"").arg(folder.name());
            QString selectResponse;
            if (!conn.sendCommandWait(selectCommand, selectResponse, 30000) || !selectResponse.contains(" OK ")) {
                continue;
            }
            
            // Fetch only FLAGS for this batch
            QString fetchCommand = QString("UID FETCH %1 (FLAGS)").arg(uidList);
            QByteArray fetchResponse;
            if (!conn.sendCommandWaitBytes(fetchCommand, fetchResponse, 30000)) {
                continue;
            }
            
            // Parse flags and update local
            parseAndUpdateFlags(fetchResponse, folder.id());
        }
    }
    
    return success;
}

QVector<qint64> ImapSynchronizer::fetchAllUids(const QString& folderId) const
{
    QVector<qint64> uids;
    ImapConnection conn;
    
    if (!connectAndLogin(conn)) {
        return uids;
    }
    
    int fid = folderId.toInt();
    auto folderOpt = FolderDao::findById(fid);
    if (!folderOpt) {
        qWarning() << "Folder not found for id" << folderId;
        return uids;
    }
    QString folderName = folderOpt->name();
    
    // SELECT
    QString selectCommand = QString("SELECT \"%1\"").arg(folderName);
    QString selectResponse;
    if (!conn.sendCommandWait(selectCommand, selectResponse, 30000) || !selectResponse.contains(" OK ")) {
        qWarning() << "SELECT failed:" << selectResponse;
        return uids;
    }
    
    // SEARCH ALL
    QString searchCommand = "UID SEARCH ALL";
    QString searchResponse;
    if (!conn.sendCommandWait(searchCommand, searchResponse, 30000)) {
        qWarning() << "SEARCH command failed:" << searchResponse;
        return uids;
    }
    
    uids = parseUidSearchResponse(searchResponse);
    return uids;
}

void ImapSynchronizer::parseAndUpdateFlags(const QByteArray& response, int folderId) const
{
    // Parse FETCH response for FLAGS
    QStringList lines = QString::fromUtf8(response).split('\n');
    for (const QString& line : lines) {
        if (!line.startsWith("* ") || !line.contains(" FETCH ")) continue;
        
        // Extract UID
        QRegularExpression uidRx(QStringLiteral("\\bUID\\s+(\\d+)"));
        QRegularExpressionMatch uidMatch = uidRx.match(line);
        if (!uidMatch.hasMatch()) continue;
        qint64 uid = uidMatch.captured(1).toLongLong();
        
        // Extract FLAGS
        int flagsPos = line.indexOf("FLAGS (");
        if (flagsPos < 0) continue;
        int flagsEnd = line.indexOf(')', flagsPos);
        if (flagsEnd < 0) continue;
        QString flags = line.mid(flagsPos + 7, flagsEnd - flagsPos - 7);
        
        bool read = flags.contains("\\Seen");
        bool flagged = flags.contains("\\Flagged");
        
        // Update local DB if different
        auto itemOpt = MailItemDao::findByUid(folderId, uid);
        if (itemOpt.has_value()) {
            MailItem item = itemOpt.value();
            if (item.isRead() != read || item.isFlagged() != flagged) {
                item.setRead(read);
                item.setFlagged(flagged);
                if (!MailItemDao::update(item)) {
                    qWarning() << "Failed to update flags for UID" << uid;
                } else {
                    qDebug() << "Updated flags for UID" << uid << "read=" << read << "flagged=" << flagged;
                }
            }
        }
    }
}

QVector<MailItem> ImapSynchronizer::fetchMailItems(const QString& folderId, qint64 sinceUid)
{
    QVector<MailItem> items;
    ImapConnection conn;

    if (!connectAndLogin(conn)) {
        return items;
    }

    // Get folder name from DB
    int fid = folderId.toInt();
    auto folderOpt = FolderDao::findById(fid);
    if (!folderOpt) {
        qWarning() << "Folder not found for id" << folderId;
        return items;
    }
    QString folderName = folderOpt->name();

    // SELECT with real folder name
    QString selectCommand = QString("SELECT \"%1\"").arg(folderName);
    QString selectResponse;
    if (!conn.sendCommandWait(selectCommand, selectResponse, 30000)) {
        qWarning() << "SELECT command failed:" << selectResponse;
        return items;
    }
    if (!selectResponse.contains(" OK ")) {
        qWarning() << "SELECT failed:" << selectResponse;
        return items;
    }

    // SEARCH
    QString searchCommand;
    if (sinceUid > 0) {
        searchCommand = QString("UID SEARCH UID %1:*").arg(sinceUid + 1);
    } else {
        searchCommand = "UID SEARCH ALL";
    }
    QString searchResponse;
    if (!conn.sendCommandWait(searchCommand, searchResponse, 30000)) {
        qWarning() << "SEARCH command failed:" << searchResponse;
        return items;
    }

    // Parse UIDs (robust: look for line starting with "* SEARCH")
    QVector<qint64> uids;
    QStringList lines = searchResponse.split('\n');
    for (const QString& line : lines) {
        if (line.startsWith("* SEARCH")) {
            QStringList parts = line.split(QRegularExpression("\\s+"));
            for (int i = 2; i < parts.size(); ++i) { // skip "* SEARCH"
                bool ok;
                qint64 uid = parts[i].toLongLong(&ok);
                if (ok && uid > 0) uids.append(uid);
            }
            break;
        }
    }

    if (uids.isEmpty()) {
        conn.sendCommandWait("CLOSE", searchResponse, 30000);
        conn.disconnect();
        return items;
    }

    // Fetch in small batches - large emails (9MB+) need smaller batches
    // 50 UIDs * 9MB = 450MB per batch, too large for 120s timeout
    const int batchSize = 5;
    for (int i = 0; i < uids.size(); i += batchSize) {
        QVector<qint64> batch = uids.mid(i, qMin(batchSize, uids.size() - i));
        QString batchList;
        for (qint64 uid : batch) {
            batchList.append(QString::number(uid)).append(",");
        }
        batchList.chop(1); // remove trailing comma

        // Fetch the complete RFC822 message. Headers alone are not enough to
        // reconstruct HTML, inline resources or attachments.
        QString fetchCommand = QString("UID FETCH %1 (BODY.PEEK[] FLAGS INTERNALDATE)")
                                   .arg(batchList);
        QByteArray fetchResponse;
        // Increase timeout to 300s (5 min) for large emails with attachments
        if (!conn.sendCommandWaitBytes(fetchCommand, fetchResponse, 300000)) {
            qWarning() << "FETCH failed for batch" << i << "(" << batch.size() << "UIDs)"
                       << "- first UID:" << batch.first() << "last UID:" << batch.last()
                       << "- response:" << QString::fromUtf8(fetchResponse).left(200);
            // Fallback: try fetching UIDs one by one
            for (qint64 uid : batch) {
                QString singleFetchCmd = QString("UID FETCH %1 (BODY.PEEK[] FLAGS INTERNALDATE)").arg(uid);
                QByteArray singleResponse;
                if (conn.sendCommandWaitBytes(singleFetchCmd, singleResponse, 300000)) {
                    QVector<MailItem> singleItems = parseFetchResponseBytes(singleResponse);
                    for (MailItem& item : singleItems) {
                        item.setFolderId(fid);
                        items.append(item);
                    }
                } else {
                    qWarning() << "  Single UID fetch failed for UID" << uid
                               << "- response:" << QString::fromUtf8(singleResponse).left(200);
                }
            }
            continue;
        }

        // Parse and persist
        QVector<MailItem> batchItems = parseFetchResponseBytes(fetchResponse);
        for (MailItem& item : batchItems) {
            item.setFolderId(fid);
            items.append(item);
        }
    }

    // Close and disconnect
    QString dummy;
    conn.sendCommandWait("CLOSE", dummy, 30000);
    conn.disconnect();
    return items;
}

QVector<Folder> ImapSynchronizer::getFolders() const
{
    QVector<Folder> folders;
    ImapConnection conn;

    if (!connectAndLogin(conn)) {
        return folders;
    }

    // List folders: LIST "" "*"
    QString listCommand = QStringLiteral("LIST \"\" \"*\"");
    QString response;
    if (!conn.sendCommandWait(listCommand, response, 30000)) {
        qWarning() << "LIST command failed:" << response;
        return folders;
    }

    // Parse the response
    QStringList lines = response.split('\n');
    folders = parseListResponse(lines);

    // Logout
    conn.disconnect();
    return folders;
}

bool ImapSynchronizer::persistFetchedItem(const Folder& folder, MailItem& item) const
{
    MimeStorageService storage;
    QVector<QString> storedPaths;
    ParsedMimeMessage parsed;
    if (!item.rawMime().isEmpty()) {
        if (!storage.parseMessage(item.rawMime(), parsed)
            || !storage.storeMessage(QString::number(m_account.id()), QString::number(folder.id()),
                                     item, item.rawMime(), &storedPaths)) {
            qWarning() << "Failed to store MIME message for UID" << item.uid();
            return false;
        }
    }

    if (!MailItemDao::upsert(item)) {
        storage.deleteEmlFile(item.fileId());
        return false;
    }

    QVector<StoredAttachmentRecord> records;
    for (int i = 0; i < parsed.attachments.size(); ++i) {
        const ParsedMimeAttachment &attachment = parsed.attachments.at(i);
        StoredAttachmentRecord record;
        record.fileName = attachment.fileName;
        record.mimeType = attachment.mimeType;
        record.contentId = attachment.contentId;
        record.size = attachment.data.size();
        if (i < storedPaths.size()) record.storedPath = storedPaths.at(i);
        records.append(record);
    }
    if (!MailItemDao::replaceAttachments(item.id(), records)) {
        storage.deleteEmlFile(item.fileId());
        return false;
    }
    return true;
}

QVector<MailItem> ImapSynchronizer::parseFetchResponseBytes(const QByteArray& response) const
{
    QVector<MailItem> items;
    MimeStorageService mimeStorage;
    int searchFrom = 0;
    
    // Debug: check if response contains error
    if (response.contains(" NO ") || response.contains(" BAD ")) {
        qWarning() << "IMAP FETCH response contains error:" << QString::fromUtf8(response).left(500);
    }
    
    while (true) {
        const int bodyPos = response.indexOf("BODY[]", searchFrom);
        if (bodyPos < 0) {
            // Check if we have untagged FETCH responses without BODY[]
            int fetchPos = response.indexOf(" FETCH ", searchFrom);
            if (fetchPos >= 0) {
                int lineEnd = response.indexOf("\r\n", fetchPos);
                if (lineEnd > 0) {
                    QByteArray line = response.mid(fetchPos, lineEnd - fetchPos);
                    qWarning() << "FETCH response without BODY[]:" << QString::fromLatin1(line);
                }
            }
            break;
        }
        const int bracePos = response.indexOf('{', bodyPos);
        const int braceEnd = response.indexOf('}', bracePos);
        if (bracePos < 0 || braceEnd < 0) break;
        QByteArray literalSize = response.mid(bracePos + 1, braceEnd - bracePos - 1);
        if (literalSize.endsWith('+')) literalSize.chop(1);
        bool sizeOk = false;
        const qint64 size = literalSize.toLongLong(&sizeOk);
        const int lineEnd = response.indexOf("\r\n", braceEnd);
        if (!sizeOk || lineEnd < 0 || response.size() < lineEnd + 2 + size) {
            qWarning() << "FETCH parse error: sizeOk=" << sizeOk << "lineEnd=" << lineEnd 
                       << "response.size=" << response.size() << "needed=" << (lineEnd + 2 + size);
            break;
        }

        const QByteArray rawMime = response.mid(lineEnd + 2, int(size));
        const int fetchLineStart = response.lastIndexOf("\r\n", bodyPos) + 2;
        const QByteArray fetchLine = response.mid(fetchLineStart, bodyPos - fetchLineStart);
        const QRegularExpression uidRx(QStringLiteral("\\bUID\\s+(\\d+)"));
        const QRegularExpressionMatch uidMatch = uidRx.match(QString::fromLatin1(fetchLine));
        if (!uidMatch.hasMatch()) {
            qWarning() << "No UID found in FETCH line:" << QString::fromLatin1(fetchLine).left(200);
            searchFrom = lineEnd + 2 + int(size);
            continue;
        }

        ParsedMimeMessage parsed;
        if (mimeStorage.parseMessage(rawMime, parsed)) {
            MailItem item;
            item.setUid(uidMatch.captured(1).toLongLong());
            item.setMessageId(parsed.messageId);
            item.setSubject(parsed.subject.isEmpty() ? QStringLiteral("(No Subject)") : parsed.subject);
            item.setSender(parsed.from);
            item.setRecipient(parsed.to);
            item.setTo(parsed.to);
            item.setCc(parsed.cc);
            item.setBcc(parsed.bcc);
            item.setDate(parsed.date);
            item.setBodyHtml(parsed.bodyHtml);
            item.setSize(rawMime.size());
            item.setRawMime(rawMime);
            QVector<QString> names;
            for (const ParsedMimeAttachment &attachment : parsed.attachments)
                names.append(attachment.fileName);
            item.setAttachments(names);

            // Flags are in the FETCH line, not in the MIME payload.
            const int flagsPos = fetchLine.indexOf("FLAGS (");
            if (flagsPos >= 0) {
                const int flagsEnd = fetchLine.indexOf(')', flagsPos);
                if (flagsEnd > flagsPos) {
                    const QString flags = QString::fromLatin1(fetchLine.mid(flagsPos + 7, flagsEnd - flagsPos - 7));
                    item.setRead(flags.contains(QStringLiteral("\\Seen")));
                    item.setFlagged(flags.contains(QStringLiteral("\\Flagged")));
                }
            }
            items.append(item);
        } else {
            qWarning() << "Failed to parse MIME for UID" << uidMatch.captured(1);
        }
        searchFrom = lineEnd + 2 + int(size);
    }
    return items;
}

bool ImapSynchronizer::appendMailItem(const QString& folderId, const MailItem& item)
{
    // Get folder name
    int fid = folderId.toInt();
    auto folderOpt = FolderDao::findById(fid);
    if (!folderOpt) {
        qWarning() << "Folder not found for id" << folderId;
        return false;
    }
    QString folderName = folderOpt->name();

    // Build RFC822 message (headers + body)
    QStringList headers;
    if (!item.sender().isEmpty())   headers << "From: " + item.sender();
    if (!item.recipient().isEmpty()) headers << "To: " + item.recipient();
    if (!item.cc().isEmpty())       headers << "Cc: " + item.cc();
    if (!item.bcc().isEmpty())      headers << "Bcc: " + item.bcc();
    if (!item.subject().isEmpty())  headers << "Subject: " + item.subject();
    headers << "Date: " + item.date().toString(Qt::RFC2822Date);
    headers << "MIME-Version: 1.0";
    headers << "Content-Type: text/html; charset=UTF-8";
    headers << "Content-Transfer-Encoding: 7bit";
    headers << ""; // blank line separates headers from body
    headers << item.bodyHtml(); // using bodyHtml as the body

    QString message = headers.join("\r\n");
    QByteArray msgData = message.toUtf8();

    ImapConnection conn;
    if (!connectAndLogin(conn)) return false;

    // SELECT folder (some servers require it)
    QString selectCmd = QString("SELECT \"%1\"").arg(folderName);
    QString resp;
    conn.sendCommandWait(selectCmd, resp, 30000); // ignore response

    // APPEND with literal: APPEND "folder" (\Seen) {size}
    QString appendCmd = QString("APPEND \"%1\" (\\Seen) {%2}").arg(folderName).arg(msgData.size());
    QString response;
    if (!conn.sendCommandWait(appendCmd, response, 30000)) {
        qWarning() << "APPEND command initial failed:" << response;
        conn.disconnect();
        return false;
    }

    // Send the literal (message data)
    conn.sendRaw(QString::fromUtf8(msgData));

    // Wait for the tagged response (tag + OK/NO)
    if (!conn.sendCommandWait("", response, 30000)) { // dummy command to get response
        qWarning() << "APPEND literal failed:" << response;
        conn.disconnect();
        return false;
    }

    conn.disconnect();
    return true;
}

bool ImapSynchronizer::updateMailItemFlags(const QString& folderId, const QString& itemUid, bool read, bool flagged)
{
    qint64 uid = itemUid.toLongLong();
    if (uid <= 0) return false;

    int fid = folderId.toInt();
    auto folderOpt = FolderDao::findById(fid);
    if (!folderOpt) return false;
    QString folderName = folderOpt->name();

    ImapConnection conn;
    if (!connectAndLogin(conn)) return false;

    // SELECT
    QString selectCmd = QString("SELECT \"%1\"").arg(folderName);
    QString resp;
    if (!conn.sendCommandWait(selectCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "SELECT failed";
        return false;
    }

    // STORE for Seen flag
    QString seenFlag = read ? "+" : "-";
    QString storeCmd = QString("UID STORE %1 %2FLAGS.SILENT \\Seen").arg(uid).arg(seenFlag);
    if (!conn.sendCommandWait(storeCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "STORE Seen failed:" << resp;
        conn.disconnect();
        return false;
    }

    // STORE for Flagged flag
    QString flaggedFlag = flagged ? "+" : "-";
    storeCmd = QString("UID STORE %1 %2FLAGS.SILENT \\Flagged").arg(uid).arg(flaggedFlag);
    if (!conn.sendCommandWait(storeCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "STORE Flagged failed:" << resp;
        conn.disconnect();
        return false;
    }

    conn.disconnect();

    // Update local DB
    auto items = MailItemDao::findByFolderId(fid);
    for (MailItem& item : items) {
        if (item.uid() == uid) {
            item.setRead(read);
            item.setFlagged(flagged);
            MailItemDao::update(item);
            break;
        }
    }
    return true;
}

bool ImapSynchronizer::deleteMailItem(const QString& folderId, const QString& itemUid)
{
    qint64 uid = itemUid.toLongLong();
    if (uid <= 0) return false;

    int fid = folderId.toInt();
    auto folderOpt = FolderDao::findById(fid);
    if (!folderOpt) return false;
    QString folderName = folderOpt->name();

    ImapConnection conn;
    if (!connectAndLogin(conn)) return false;

    // SELECT
    QString selectCmd = QString("SELECT \"%1\"").arg(folderName);
    QString resp;
    if (!conn.sendCommandWait(selectCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "SELECT failed";
        return false;
    }

    // Mark as \\Deleted
    QString storeCmd = QString("UID STORE %1 +FLAGS.SILENT \\Deleted").arg(uid);
    if (!conn.sendCommandWait(storeCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "STORE Deleted failed:" << resp;
        conn.disconnect();
        return false;
    }

    // EXPUNGE (permanently delete)
    QString expungeCmd = "EXPUNGE";
    if (!conn.sendCommandWait(expungeCmd, resp, 30000) || !resp.contains(" OK ")) {
        qWarning() << "EXPUNGE failed:" << resp;
        conn.disconnect();
        return false;
    }

    conn.disconnect();

    // Remove from local DB
    MailItemDao::remove(uid);
    return true;
}

QString ImapSynchronizer::generateEventId() const
{
    return QString::number(QDateTime::currentMSecsSinceEpoch());
}

QVector<Folder> ImapSynchronizer::parseListResponse(const QStringList& lines) const
{
    QVector<Folder> folders;
    QRegularExpression rx("\\* LIST \\\\([^)]*\\\\) \"([^\"]*)\" \"([^\"]*)\"");
    for (const QString& line : lines) {
        QRegularExpressionMatch match = rx.match(line);
        if (match.hasMatch()) {
            QString delimiter = match.captured(1); // unused for now
            QString mailbox = match.captured(2);
            Folder folder;
            folder.setName(mailbox);
            QString lower = mailbox.toLower();
            if (lower == "inbox") folder.setInbox(true);
            else if (lower == "sent") folder.setSent(true);
            else if (lower == "drafts") folder.setDrafts(true);
            else if (lower == "trash" || lower == "deleted items") folder.setTrash(true);
            folders.append(folder);
        }
    }
    return folders;
}

QVector<MailItem> ImapSynchronizer::parseFetchResponse(const QStringList& lines) const
{
    QVector<MailItem> items;
    int i = 0;
    while (i < lines.size()) {
        const QString& line = lines[i];
        if (!line.startsWith(QStringLiteral("* "))) {
            ++i;
            continue;
        }
        int fetchPos = line.indexOf(QStringLiteral(" FETCH ("));
        if (fetchPos == -1) {
            ++i;
            continue;
        }

        // Extract UID
        int uidPos = line.indexOf(QStringLiteral("UID "));
        if (uidPos == -1) {
            ++i;
            continue;
        }
        int uidEnd = line.indexOf(QRegularExpression(QStringLiteral("[\\s)]")), uidPos + 4);
        if (uidEnd == -1) uidEnd = line.length();
        QString uidStr = line.mid(uidPos + 4, uidEnd - uidPos - 4);
        bool ok;
        qint64 uid = uidStr.toLongLong(&ok);
        if (!ok) {
            ++i;
            continue;
        }

        MailItem item;
        item.setUid(uid);

        // Extract FLAGS
        int flagsPos = line.indexOf(QStringLiteral("FLAGS ("));
        if (flagsPos != -1) {
            int flagsEnd = line.indexOf(')', flagsPos + 7);
            if (flagsEnd != -1) {
                QString flags = line.mid(flagsPos + 7, flagsEnd - flagsPos - 7);
                item.setRead(flags.contains(QStringLiteral("\\Seen")));
                item.setFlagged(flags.contains(QStringLiteral("\\Flagged")));
            }
        }

        // Extract INTERNALDATE
        int datePos = line.indexOf(QStringLiteral("INTERNALDATE \""));
        if (datePos != -1) {
            int dateStart = datePos + 15; // length of "INTERNALDATE \""
            int dateEnd = line.indexOf('\"', dateStart);
            if (dateEnd != -1) {
                QString dateStr = line.mid(dateStart, dateEnd - dateStart);
                dateStr.replace('-', ' '); // ahora "9 Jul 2025 14:42:04 +0000"
                QDateTime dt = QDateTime::fromString(dateStr, Qt::RFC2822Date);
                if (dt.isValid())
                    item.setDate(dt);
            }
        }

        // Fetch BODY[HEADER.FIELDS ...] literal, may span multiple lines
        QString bodyData;
        int bodyPos = line.indexOf(QStringLiteral("BODY[]"));
        if (bodyPos != -1) {
            int bracePos = line.indexOf('{', bodyPos);
            if (bracePos != -1) {
                int sizeEnd = line.indexOf('}', bracePos);
                if (sizeEnd != -1) {
                    bool sizeOk;
                    int size = line.mid(bracePos + 1, sizeEnd - bracePos - 1).toInt(&sizeOk);
                    if (sizeOk) {
                        int dataStart = line.indexOf(QStringLiteral("\r\n"), sizeEnd);
                        if (dataStart != -1) {
                            dataStart += 2; // skip \r\n
                            int available = line.length() - dataStart;
                            if (available > size) available = size;
                            bodyData = line.mid(dataStart, available);
                            int received = available;
                            int remaining = size - received;
                            int j = i + 1;
                            while (j < lines.size() && remaining > 0) {
                                const QString& nextLine = lines[j];
                                int take = qMin(nextLine.length(), remaining);
                                bodyData += nextLine.left(take);
                                remaining -= take;
                                ++j;
                            }
                            // we have consumed lines up to j-1
                            i = j - 1; // will be incremented at end of loop
                        }
                    }
                }
            }
        }

        // Parse headers from bodyData
        if (!bodyData.isEmpty()) {
            QRegularExpression headerRx(QStringLiteral(R"((^([^:]+):\s*(.*?)\r\n))"), QRegularExpression::MultilineOption);
            auto it = headerRx.globalMatch(bodyData);
            while (it.hasNext()) {
                auto match = it.next();
                QString key = match.captured(2).toLower().trimmed();
                QString value = match.captured(3).trimmed();
                if (key == QStringLiteral("subject"))
                    item.setSubject(value);
                else if (key == QStringLiteral("from"))
                    item.setSender(value);
                else if (key == QStringLiteral("to"))
                    item.setRecipient(value);
                else if (key == QStringLiteral("date")) {
                    QDateTime dt = QDateTime::fromString(value, Qt::RFC2822Date);
                    if (dt.isValid())
                        item.setDate(dt);
                }
            }
        }

        // If subject not set, use placeholder
        if (item.subject().isEmpty())
            item.setSubject(QStringLiteral("(No Subject)"));

        items.append(item);
        ++i;
    }
    return items;
}
