#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"

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

bool ImapSynchronizer::initialize(const 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, &QEventLoop::quit);
    connect(&conn, &ImapConnection::errorOccurred, this, [&](const QString& err){
        qWarning() << "IMAP connection error:" << err;
        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;
    }
    connected = true;

    // 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;
}

bool ImapSynchronizer::syncFolder(const Folder& folder)
{
    ImapConnection conn;
    if (!connectAndLogin(conn)) return false;

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

    // Get all UIDs
    QString searchCmd = "UID SEARCH ALL";
    QString searchResp;
    if (!conn.sendCommandWait(searchCmd, searchResp, 30000) || !searchResp.contains(" OK ")) {
        qWarning() << "SEARCH failed";
        return false;
    }

    QVector<qint64> uids;
    QStringList lines = searchResp.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 no messages, set unread count to 0 and exit
    if (uids.isEmpty()) {
        Folder f = folder;
        f.setUnreadCount(0);
        FolderDao::update(f);
        conn.disconnect();
        return true;
    }

    // Get flags for all UIDs in one command
    QStringList uidStrs;
    for (qint64 uid : uids) uidStrs.append(QString::number(uid));
    QString fetchCmd = "UID FETCH " + uidStrs.join(',') + " (FLAGS)";
    QString fetchResp;
    if (!conn.sendCommandWait(fetchCmd, fetchResp, 30000) || !fetchResp.contains(" OK ")) {
        qWarning() << "FETCH FLAGS failed";
        conn.disconnect();
        return false;
    }

    // Parse flags and update local DB
    QMap<qint64, QPair<bool,bool>> flagsMap; // uid -> (seen, flagged)
    QRegularExpression flagsRx(R"(UID (\d+).*FLAGS \(([^)]*)\))");
    auto it = flagsRx.globalMatch(fetchResp);
    while (it.hasNext()) {
        auto m = it.next();
        qint64 uid = m.captured(1).toLongLong();
        QString flags = m.captured(2);
        bool seen = flags.contains("\\Seen");
        bool flagged = flags.contains("\\Flagged");
        flagsMap[uid] = qMakePair(seen, flagged);
    }

    // Update local items
    auto localItems = MailItemDao::findByFolderId(folder.id());
    for (MailItem& item : localItems) {
        if (flagsMap.contains(item.uid())) {
            bool seen = flagsMap[item.uid()].first;
            bool flagged = flagsMap[item.uid()].second;
            if (item.isRead() != seen || item.isFlagged() != flagged) {
                item.setRead(seen);
                item.setFlagged(flagged);
                MailItemDao::update(item);
            }
        }
    }

    // Count unread
    int unread = 0;
    for (const MailItem& item : localItems) {
        if (!item.isRead()) unread++;
    }
    Folder f = folder;
    f.setUnreadCount(unread);
    FolderDao::update(f);

    conn.disconnect();
    return true;
}

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 %1:*").arg(sinceUid);
    } 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 batches of 50 UIDs
    const int batchSize = 50;
    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 headers and flags (efficient)
        QString fetchCommand = QString("UID FETCH %1 (BODY.PEEK[HEADER.FIELDS (SUBJECT FROM TO DATE)] FLAGS INTERNALDATE)")
                                   .arg(batchList);
        QString fetchResponse;
        if (!conn.sendCommandWait(fetchCommand, fetchResponse, 30000)) {
            qWarning() << "FETCH failed for batch" << i;
            continue;
        }

        // Parse and persist
        QVector<MailItem> batchItems = parseFetchResponse(fetchResponse.split('\n'));
        for (MailItem& item : batchItems) {
            item.setFolderId(fid);
            if (MailItemDao::insert(item)) {
                items.append(item);
            } else {
                qWarning() << "Failed to insert mail item uid" << item.uid();
            }
        }
    }

    // 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::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 + 16; // length of "INTERNALDATE \""
            int dateEnd = line.indexOf('\"', dateStart);
            if (dateEnd != -1) {
                QString dateStr = line.mid(dateStart, dateEnd - dateStart);
                QDateTime dt = QDateTime::fromString(dateStr, QStringLiteral("dd-MMM-yyyy hh:mm:ss zzz"));
                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;
}
