#include "pop3synchronizer.h"
#include <QDebug>
#include <QDateTime>
#include <QRegularExpression>
#include <QSqlQuery>
#include <QAbstractSocket>
#include <QMap>
#include <QCryptographicHash>
#include "../../core/mailitem.h"
#include "../../db/dao/mailitemdao.h"
#include "../../db/dao/folderdao.h"
#include "../../core/models/account.h"
#include "../../services/mimestorage.h"

Pop3Synchronizer::Pop3Synchronizer(QObject *parent)
    : Synchronizer(parent)
    , m_socket(nullptr)
{
}

bool Pop3Synchronizer::initialize(const Account &account)
{
    m_account = account;
    const auto &settings = account.connectionSettings();
    m_host = settings.incomingHost;
    m_port = settings.incomingPort;
    m_useSsl = settings.incomingSsl;
    m_username = settings.username;
    m_password = settings.password;

    if (m_socket) {
        m_socket->deleteLater();
        m_socket = nullptr;
    }
    m_socket = new QSslSocket(this);
    qDebug() << "POP3 synchronizer initialized for" << m_username << "at" << m_host;
    return true;
}

bool Pop3Synchronizer::syncFolder(const Folder &folder)
{
    Q_UNUSED(folder);
    return true;
}

QVector<Folder> Pop3Synchronizer::getFolders() const
{
    QVector<Folder> folders;
    Folder inbox;
    inbox.setId(0);
    inbox.setAccountId(m_account.id());
    inbox.setName(QStringLiteral("Inbox"));
    inbox.setParentFolderId(QString());
    folders.append(inbox);
    return folders;
}

QVector<MailItem> Pop3Synchronizer::fetchMailItems(const QString &folderId, qint64 sinceUid)
{
    QVector<MailItem> items;
    QSslSocket socket;
    if (m_useSsl) socket.connectToHostEncrypted(m_host, m_port);
    else socket.connectToHost(m_host, m_port);
    if (!socket.waitForConnected(15000)) {
        qWarning() << "Failed to connect to POP3 server:" << socket.errorString();
        return items;
    }
    if (m_useSsl && !socket.waitForEncrypted(15000)) {
        qWarning() << "TLS handshake failed:" << socket.errorString();
        return items;
    }

    auto readLine = [&](QByteArray &line) {
        if (!socket.canReadLine() && !socket.waitForReadyRead(15000)) return false;
        line = socket.readLine();
        return !line.isEmpty();
    };
    auto command = [&](const QByteArray &commandText, QByteArray &response, bool multiline) {
        socket.write(commandText + QByteArrayLiteral("\r\n"));
        socket.flush();
        if (!readLine(response) || !response.trimmed().startsWith('+')) return false;
        if (multiline) {
            while (true) {
                QByteArray line;
                if (!readLine(line)) return false;
                if (line == QByteArrayLiteral(".\r\n") || line == QByteArrayLiteral(".\n")) break;
                if (line.startsWith("..")) line.remove(0, 1);
                response.append(line);
            }
        }
        return true;
    };

    QByteArray response;
    if (!readLine(response) || !response.trimmed().startsWith('+')) return items;
    if (!command(QByteArrayLiteral("USER ") + m_username.toUtf8(), response, false)
        || !command(QByteArrayLiteral("PASS ") + m_password.toUtf8(), response, false)) {
        qWarning() << "POP3 authentication failed:" << response;
        return items;
    }
    if (!command(QByteArrayLiteral("UIDL"), response, true)) {
        qWarning() << "UIDL failed:" << response;
        return items;
    }

    m_uidToMsgNum.clear();
    m_maxUid = 0;
    QList<qint64> uidsToFetch;
    const QStringList lines = QString::fromUtf8(response).split(QRegularExpression("\\r?\\n"));
    for (const QString &line : lines) {
        const QStringList parts = line.trimmed().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
        if (parts.size() < 2) continue;
        bool okMsgId = false, okUid = false;
        const int msgNum = parts.at(0).toInt(&okMsgId);
        qint64 uid = parts.at(1).toLongLong(&okUid);
        if (!okUid) {
            const QByteArray digest = QCryptographicHash::hash(parts.at(1).toUtf8(), QCryptographicHash::Sha256).toHex().left(15);
            uid = digest.toLongLong(&okUid, 16);
        }
        if (!okMsgId || !okUid || uid <= 0) continue;
        m_uidToMsgNum[QString::number(uid)] = msgNum;
        m_maxUid = qMax(m_maxUid, uid);
        if (uid > sinceUid) uidsToFetch.append(uid);
    }

    MimeStorageService mimeStorage;
    int processed = 0;
    for (const qint64 uid : uidsToFetch) {
        const int msgNum = m_uidToMsgNum.value(QString::number(uid));
        QByteArray rawResponse;
        if (!command(QByteArrayLiteral("RETR ") + QByteArray::number(msgNum), rawResponse, true)) {
            qWarning() << "RETR failed for message" << msgNum;
            continue;
        }
        const int firstLineEnd = rawResponse.indexOf("\r\n");
        const QByteArray rawMime = firstLineEnd >= 0 ? rawResponse.mid(firstLineEnd + 2) : QByteArray();
        ParsedMimeMessage parsed;
        if (rawMime.isEmpty() || !mimeStorage.parseMessage(rawMime, parsed)) {
            qWarning() << "Failed to parse POP3 MIME message with UID" << uid;
            continue;
        }

        MailItem item;
        item.setFolderId(folderId.toInt());
        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);
        item.setUid(uid);
        QVector<QString> attachmentNames;
        for (const ParsedMimeAttachment &attachment : parsed.attachments) attachmentNames.append(attachment.fileName);
        item.setAttachments(attachmentNames);
        items.append(item);
        ++processed;
        emit progressChanged(uidsToFetch.isEmpty() ? 100 : static_cast<int>((100.0 * processed) / uidsToFetch.size()));
        emit statusMessage(tr("Fetched %1 of %2 messages").arg(processed).arg(uidsToFetch.size()));
    }
    command(QByteArrayLiteral("QUIT"), response, false);
    socket.disconnectFromHost();
    return items;
}

bool Pop3Synchronizer::appendMailItem(const QString &folderId, const MailItem &item)
{
    Q_UNUSED(folderId);
    Q_UNUSED(item);
    qWarning() << "POP3 appendMailItem is not a send transport; MailService owns SMTP sending";
    return false;
}

bool Pop3Synchronizer::updateMailItemFlags(const QString &folderId, const QString &itemUid, bool read, bool flagged)
{
    QVector<MailItem> items = MailItemDao::findByFolderId(folderId.toInt());
    for (MailItem &it : items) {
        if (it.uid() == itemUid.toLongLong()) {
            it.setRead(read);
            it.setFlagged(flagged);
            if (MailItemDao::update(it))
                return true;
        }
    }
    return false;
}

bool Pop3Synchronizer::deleteMailItem(const QString &folderId, const QString &itemUid)
{
    Q_UNUSED(folderId);
    if (!m_socket) {
        qWarning() << "POP3 socket not initialized";
        return false;
    }

    if (m_socket->state() != QAbstractSocket::UnconnectedState) {
        m_socket->disconnectFromHost();
        if (!m_socket->waitForDisconnected(3000)) {
            qWarning() << "Failed to disconnect from previous connection";
            return false;
        }
    }

    if (m_useSsl)
        m_socket->connectToHostEncrypted(m_host, m_port);
    else
        m_socket->connectToHost(m_host, m_port);
    if (!m_socket->waitForConnected(5000)) {
        qWarning() << "Failed to connect to POP3 server:" << m_socket->errorString();
        return false;
    }
    if (m_useSsl && !m_socket->waitForEncrypted(5000)) {
        qWarning() << "TLS handshake failed:" << m_socket->errorString();
        m_socket->disconnectFromHost();
        return false;
    }

    QString greeting;
    if (!m_socket->waitForReadyRead(5000)) {
        qWarning() << "No greeting from POP3 server";
        m_socket->disconnectFromHost();
        return false;
    }
    greeting = QString::fromUtf8(m_socket->readLine()).trimmed();
    if (!greeting.startsWith(QLatin1Char('+'))) {
        qWarning() << "Invalid POP3 greeting:" << greeting;
        m_socket->disconnectFromHost();
        return false;
    }

    QString userCmd = QStringLiteral("USER %1\r\n").arg(m_username);
    QString passCmd = QStringLiteral("PASS %1\r\n").arg(m_password);
    QString resp;
    if (!sendCommand(userCmd, resp) || !resp.startsWith(QLatin1Char('+'))) {
        qWarning() << "USER command failed:" << resp;
        m_socket->disconnectFromHost();
        return false;
    }
    if (!sendCommand(passCmd, resp) || !resp.startsWith(QLatin1Char('+'))) {
        qWarning() << "PASS command failed:" << resp;
        m_socket->disconnectFromHost();
        return false;
    }

    // Get UIDL to map uid to message number
    QString uidlResp;
    if (!sendCommand(QStringLiteral("UIDL\r\n"), uidlResp) || !uidlResp.startsWith(QLatin1Char('+'))) {
        qWarning() << "UIDL failed:" << uidlResp;
        m_socket->disconnectFromHost();
        return false;
    }

    int msgNum = -1;
    QStringList lines = uidlResp.split(QRegularExpression("\\r?\\n"));
    for (const QString &line : lines) {
        if (line.startsWith(QLatin1Char('.')) || line.isEmpty())
            continue;
        QString l = line;
        if (l.startsWith(QLatin1Char('+')))
            l.remove(0, 1);
        QStringList parts = l.split(QRegularExpression("\\s+"));
        if (parts.size() < 2)
            continue;
        bool okUid;
        qint64 uid = parts[1].toLongLong(&okUid);
        if (!okUid)
            continue;
        if (uid == itemUid.toLongLong()) {
            bool okMsgId;
            msgNum = parts[0].toInt(&okMsgId);
            if (!okMsgId)
                msgNum = -1;
            break;
        }
    }

    if (msgNum == -1) {
        qWarning() << "Could not find message number for UID" << itemUid;
        m_socket->disconnectFromHost();
        return false;
    }

    QString deleCmd = QStringLiteral("DELE %1\r\n").arg(msgNum);
    if (!sendCommand(deleCmd, resp) || !resp.startsWith(QLatin1Char('+'))) {
        qWarning() << "DELE failed for msg" << msgNum << ":" << resp;
        m_socket->disconnectFromHost();
        return false;
    }

    // Deletion takes effect on QUIT
    sendCommand(QStringLiteral("QUIT\r\n"), resp);
    m_socket->disconnectFromHost();
    return true;
}

QString Pop3Synchronizer::readResponse()
{
    if (!m_socket)
        return QString();
    if (!m_socket->waitForReadyRead(5000))
        return QString();
    return QString::fromUtf8(m_socket->readLine()).trimmed();
}

bool Pop3Synchronizer::sendCommand(const QString &cmd, QString &response)
{
    if (!m_socket)
        return false;
    m_socket->write(cmd.toUtf8());
    if (!m_socket->waitForBytesWritten(5000))
        return false;
    response = readResponse();
    return !response.isEmpty();
}
