#include "imapconnection.h"
#include <QDebug>
#include <QEventLoop>
#include <QTimer>

ImapConnection::ImapConnection(QObject *parent) : QObject(parent),
    m_socket(new QSslSocket(this)),
    m_tagCounter(1)
{
    connect(m_socket, &QAbstractSocket::connected, this, &ImapConnection::onConnected);
    connect(m_socket, &QSslSocket::encrypted, this, &ImapConnection::onEncrypted);
    m_readyReadConnection = connect(m_socket, &QSslSocket::readyRead, this, &ImapConnection::onReadyRead);
    connect(m_socket, &QAbstractSocket::errorOccurred, this, &ImapConnection::onSocketError);
}

ImapConnection::~ImapConnection()
{
    disconnect();
}

void ImapConnection::connectToHost(const QString &host, int port, bool useSsl)
{
    m_host = host;
    m_port = port;
    m_useSsl = useSsl;
    if (useSsl) {
        m_socket->connectToHostEncrypted(host, port);
    } else {
        m_socket->connectToHost(host, port);
    }
}

void ImapConnection::login(const QString &username, const QString &password,
                           bool useAuthPlain,
                           std::function<void(bool, const QString&)> callback)
{
    if (!m_socket->isEncrypted() && m_useSsl) {
        callback(false, "Not encrypted");
        return;
    }
    QString cmd;
    if (useAuthPlain) {
        QByteArray auth;
        auth.append('\0');
        auth.append(username.toUtf8());
        auth.append('\0');
        auth.append(password.toUtf8());
        cmd = QString("AUTHENTICATE PLAIN %1").arg(QString::fromLatin1(auth.toBase64()));
    } else {
        cmd = QString("LOGIN %1 %2").arg(username, password);
    }
    sendCommand(cmd, callback);
}

void ImapConnection::sendCommand(const QString &command,
                                std::function<void(bool, const QString&)> callback)
{
    QString tag = generateTag();
    QString fullCmd = tag + " " + command + "\r\n";
    m_socket->write(fullCmd.toUtf8());
    m_socket->flush();
    m_pendingCallbacks[tag] = callback;
}

void ImapConnection::disconnect()
{
    if (m_socket->state() != QAbstractSocket::UnconnectedState) {
        m_socket->disconnectFromHost();
        m_socket->waitForDisconnected(1000);
    }
    m_pendingCallbacks.clear();
}

void ImapConnection::onConnected()
{
    // For non-SSL connections, signal connected immediately.
    // For SSL connections, onEncrypted() fires instead after the SSL handshake.
    if (!m_useSsl) {
        emit connected();
    }
}

void ImapConnection::onEncrypted()
{
    emit connected();
}

void ImapConnection::onReadyRead()
{
    while (m_socket->canReadLine()) {
        QString line = QString::fromUtf8(m_socket->readLine()).trimmed();
        processLine(line);
    }
}

void ImapConnection::onSocketError(QAbstractSocket::SocketError error)
{
    emit errorOccurred(m_socket->errorString());
}

void ImapConnection::processLine(const QString &line)
{
    // If it's an untagged response (starts with *), emit signal
    if (line.startsWith('*')) {
        emit untaggedResponse(line);
        return;
    }

    // Extract the tag prefix (first word) and look it up in pending callbacks
    int spacePos = line.indexOf(' ');
    if (spacePos == -1) return; // malformed line

    QString tagFromLine = line.left(spacePos);

    auto it = m_pendingCallbacks.find(tagFromLine);
    if (it != m_pendingCallbacks.end()) {
        bool ok = line.contains(" OK ");
        auto cb = it.value();
        m_pendingCallbacks.erase(it);
        cb(ok, line);
        return;
    }

    // If we get here, it's an unhandled response
    qWarning() << "Unhandled IMAP response:" << line;
}

void ImapConnection::sendRaw(const QString &data)
{
    m_socket->write(data.toUtf8() + "\r\n");
    m_socket->flush();
}

QString ImapConnection::generateTag()
{
    // Simple tag: A001, A002, ... (like traditional IMAP clients)
    return QString("A%1").arg(m_tagCounter++, 4, 10, QChar('0'));
}

bool ImapConnection::sendCommandWait(const QString &command, QString &response, int msecs)
{
    QByteArray rawResponse;
    const bool completed = sendCommandWaitBytes(command, rawResponse, msecs);
    response = QString::fromUtf8(rawResponse);
    return completed;
}

bool ImapConnection::sendCommandWaitBytes(const QString &command, QByteArray &response, int msecs)
{
    QString tag = generateTag();
    QString fullCmd = tag + " " + command + "\r\n";
    bool complete = false;
    bool timedOut = false;
    QEventLoop loop;
    QTimer timer;
    QByteArray buffer;
    const QByteArray tagBytes = tag.toUtf8();

    // Fetch responses contain arbitrary MIME bytes and IMAP literals. The
    // line-based parser cannot safely consume those bytes, so temporarily
    // take ownership of readyRead and collect the complete tagged response.
    QObject::disconnect(m_readyReadConnection);
    auto collectResponse = [&]() {
        buffer.append(m_socket->readAll());

        int pos = 0;
        while (pos < buffer.size()) {
            const int lineEnd = buffer.indexOf("\r\n", pos);
            if (lineEnd < 0) break;
            const QByteArray line = buffer.mid(pos, lineEnd - pos);
            
            // Check for tagged response FIRST (before literal parsing)
            // Tagged responses: "A0001 OK ...", "A0001 NO ...", "A0001 BAD ..."
            if (line.startsWith(tagBytes + QByteArrayLiteral(" OK ")) ||
                line.startsWith(tagBytes + QByteArrayLiteral(" NO ")) ||
                line.startsWith(tagBytes + QByteArrayLiteral(" BAD ")) ||
                line.startsWith(tagBytes + QByteArrayLiteral(" "))) {
                complete = true;
                loop.quit();
                break;
            }

            const int open = line.lastIndexOf('{');
            QByteArray literalSize = open >= 0 && line.endsWith('}')
                ? line.mid(open + 1, line.size() - open - 2) : QByteArray();
            if (literalSize.endsWith('+')) literalSize.chop(1);
            bool literalOk = false;
            const qint64 parsedLiteralSize = literalSize.toLongLong(&literalOk);
            const bool hasLiteral = open >= 0 && line.endsWith('}') && literalOk && parsedLiteralSize >= 0;
            if (hasLiteral) {
                const qint64 length = parsedLiteralSize;
                const int dataStart = lineEnd + 2;
                if (buffer.size() < dataStart + length) break;
                pos = dataStart + int(length);
                if (buffer.mid(pos, 2) == QByteArrayLiteral("\r\n")) pos += 2;
                continue;
            }
            pos = lineEnd + 2;
        }
    };
    QMetaObject::Connection readyConn = connect(m_socket, &QSslSocket::readyRead, &loop, collectResponse);
    QMetaObject::Connection errorConn = connect(m_socket, &QAbstractSocket::errorOccurred, &loop, [&](QAbstractSocket::SocketError) {
        loop.quit();
    });
    timer.setSingleShot(true);
    QMetaObject::Connection timeoutConn = connect(&timer, &QTimer::timeout, &loop, [&]() {
        timedOut = true;
        loop.quit();
    });

    m_socket->write(fullCmd.toUtf8());
    m_socket->flush();
    if (m_socket->bytesAvailable() > 0) collectResponse();
    timer.start(msecs);
    loop.exec();
    QObject::disconnect(readyConn);
    QObject::disconnect(errorConn);
    QObject::disconnect(timeoutConn);
    m_readyReadConnection = connect(m_socket, &QSslSocket::readyRead, this, &ImapConnection::onReadyRead);

    response = buffer;
    
    // Debug: log if failed
    if (timedOut || !complete) {
        qWarning() << "IMAP command failed:" << command.left(100)
                   << "- timedOut:" << timedOut
                   << "- complete:" << complete
                   << "- response size:" << buffer.size()
                   << "- last 200 chars:" << QString::fromUtf8(buffer.right(200));
    }
    if (timedOut || !complete) return false;
    return true;
}
