#include "outlooksynchronizer.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
#include <QEventLoop>
#include "../../core/events.h"
#include "../../core/eventbus.h"

OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
    : Synchronizer(parent),
      m_networkManager(new QNetworkAccessManager(this)),
      m_deltaTimer(new QTimer(this))
{
    // Conectar señales
    connect(m_networkManager, &QNetworkAccessManager::finished,
            this, &OutlookSynchronizer::onGraphReplyFinished);
    connect(m_deltaTimer, &QTimer::timeout,
            this, &OutlookSynchronizer::onDeltaLinkTimer);
    
    // Configurar timer para delta sync cada 5 minutos
    m_deltaTimer->setInterval(5 * 60 * 1000); // 5 minutos
}

bool OutlookSynchronizer::initialize(const Account& account)
{
    // Guardar información de la cuenta
    m_account = account;
    
    // Para Outlook, usamos tokens de acceso
    m_accessToken = account.accessToken();
    m_refreshToken = account.refreshToken();
    m_tokenExpires = account.tokenExpires();
    
    if (m_accessToken.isEmpty()) {
        qWarning() << "Outlook account has no access token";
        return false;
    }
    
    // Publish AccountConnectedEvent
    WinoMail::Events::AccountConnectedEvent event;
    event.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    event.timestamp = QDateTime::currentDateTimeUtc();
    event.accountId = account.id();
    event.provider = "outlook";
    PUBLISH(event);
    
    qDebug() << "Outlook Synchronizer initialized for account:" << account.email();
    return true;
}

bool OutlookSynchronizer::syncFolder(const Folder& folder)
{
    if (!m_account.isTokenValid()) {
        if (!refreshAccessToken()) {
            qWarning() << "Failed to refresh token for Outlook sync";
            emit errorOccurred("Failed to refresh authentication token");
            return false;
        }
    }
    
    qDebug() << "Starting Outlook sync for folder:" << folder.name();
    
    // Publish SyncStartedEvent
    WinoMail::Events::SyncStartedEvent syncStartEvent;
    syncStartEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    syncStartEvent.timestamp = QDateTime::currentDateTimeUtc();
    syncStartEvent.accountId = m_account.id();
    syncStartEvent.folderId = QString::number(folder.id());
    PUBLISH(syncStartEvent);
    
    emit folderSyncStarted(QString::number(folder.id()));
    
    // Obtener elementos de correo desde Microsoft Graph
    QVector<MailItem> items = fetchMailItems(QString::number(folder.id()));
    
    // En una implementación real, aquí compararíamos con la base de datos local
    // y emitiríamos las señales apropiadas para elementos nuevos/actualizados/eliminados
    
    // Por ahora, simulamos que obtenemos algunos elementos
    if (!items.isEmpty()) {
        for (const MailItem& item : items) {
            emit mailItemAdded(item);
            // Publish MailItemAddedEvent
            WinoMail::Events::MailItemAddedEvent mailEvent;
            mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
            mailEvent.timestamp = QDateTime::currentDateTimeUtc();
            mailEvent.item = item;
            PUBLISH(mailEvent);
        }
    }
    
    emit folderSyncFinished(QString::number(folder.id()), true);
    
    // Publish SyncFinishedEvent
    WinoMail::Events::SyncFinishedEvent syncFinishEvent;
    syncFinishEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    syncFinishEvent.timestamp = QDateTime::currentDateTimeUtc();
    syncFinishEvent.accountId = m_account.id();
    syncFinishEvent.folderId = QString::number(folder.id());
    syncFinishEvent.success = true;
    syncFinishEvent.errorMessage = "";
    PUBLISH(syncFinishEvent);
    
    return true;
}

QVector<Folder> OutlookSynchronizer::getFolders() const
{
    // En una implementación real, llamaríamos a Microsoft Graph API
    // para obtener las carpetas del buzón
    // Por ahora, devolvemos algunas carpetas básicas
    
    QVector<Folder> folders;
    folders.append(Folder(1, m_account.id(), "Inbox", "Inbox", true, false, false, false, 0, QDateTime::currentDateTime()));
    folders.append(Folder(2, m_account.id(), "Drafts", "Drafts", false, true, false, false, 0, QDateTime::currentDateTime()));
    folders.append(Folder(3, m_account.id(), "Sent Items", "SentMail", false, false, true, false, 0, QDateTime::currentDateTime()));
    folders.append(Folder(4, m_account.id(), "Trash", "DeletedItems", false, false, false, true, 0, QDateTime::currentDateTime()));
    
    return folders;
}

QVector<MailItem> OutlookSynchronizer::fetchMailItems(const QString& folderId, 
                                                     qint64 sinceUid)
{
    Q_UNUSED(sinceUid); // En Outlook usamos delta links en lugar de UIDs
    
    if (!m_account.isTokenValid()) {
        if (!refreshAccessToken()) {
            qWarning() << "Failed to refresh token for fetching mail items";
            return QVector<MailItem>();
        }
    }
    
    qDebug() << "Fetching mail items for folder:" << folderId;
    
    // Construir la URL para Microsoft Graph API
    QString endpoint = QString("/me/mailFolders/%1/messages").arg(folderId);
    QString url = buildGraphUrl(endpoint);
    
    // Parámetros de consulta
    QUrlQuery query;
    query.addQueryItem("$top", "50"); // Limitar a 50 mensajes por petición
    query.addQueryItem("$orderby", "receivedDateTime DESC");
    url += "?" + query.toString();
    
    QNetworkRequest request = createAuthRequest(url);
    QNetworkReply* reply = m_networkManager->get(request);
    
    // Nota: En una implementación real, esperaríamos la respuesta asíncronamente
    // pero por simplicidad en este stub, simulamos una respuesta
    
    // Simular respuesta para desarrollo
    QVector<MailItem> items;
    items.append(MailItem(1, folderId.toInt(), "Reunión de proyecto", 
                         "juan.perez@empresa.com", m_account.email(),
                         QDateTime::currentDateTime().addSecs(-3600), 
                         false, false));
    items.append(MailItem(2, folderId.toInt(), "Entrega de documentación", 
                         "ana.gomez@cliente.com", m_account.email(),
                         QDateTime::currentDateTime().addSecs(-7200), 
                         true, false));
    
    return items;
}

bool OutlookSynchronizer::appendMailItem(const QString& folderId, const MailItem& item)
{
    if (!m_account.isTokenValid()) {
        if (!refreshAccessToken()) {
            qWarning() << "Failed to refresh token for appending mail item";
            return false;
        }
    }
    
    qDebug() << "Appending mail item to folder:" << folderId;
    
    // En una implementación real, llamaríamos a Microsoft Graph API
    // para crear un mensaje en la carpeta especificada
    
    // Por ahora, simulamos éxito
    emit mailItemAdded(item);
    
    // Publish MailItemAddedEvent
    WinoMail::Events::MailItemAddedEvent mailEvent;
    mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    mailEvent.timestamp = QDateTime::currentDateTimeUtc();
    mailEvent.item = item;
    PUBLISH(mailEvent);
    
    return true;
}

bool OutlookSynchronizer::updateMailItemFlags(const QString& folderId, 
                                             const QString& itemUid,
                                             bool read, bool flagged)
{
    if (!m_account.isTokenValid()) {
        if (!refreshAccessToken()) {
            qWarning() << "Failed to refresh token for updating mail item flags";
            return false;
        }
    }
    
    qDebug() << "Updating mail item flags:" << itemUid 
             << "read:" << read << "flagged:" << flagged;
    
    // En una implementación real, llamaríamos a Microsoft Graph API
    // para actualizar las flags del mensaje
    
    // Por ahora, simulamos éxito
    MailItem updatedItem;
    updatedItem.setId(itemUid.toLongLong());
    updatedItem.setFolderId(folderId.toInt());
    updatedItem.setRead(read);
    updatedItem.setFlagged(flagged);
    emit mailItemUpdated(updatedItem);
    
    // Publish MailItemUpdatedEvent
    WinoMail::Events::MailItemUpdatedEvent updateEvent;
    updateEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    updateEvent.timestamp = QDateTime::currentDateTimeUtc();
    updateEvent.item = updatedItem;
    updateEvent.changedFields = QStringList() << "read" << "flagged"; // Simplified
    PUBLISH(updateEvent);
    
    return true;
}

bool OutlookSynchronizer::deleteMailItem(const QString& folderId, 
                                        const QString& itemUid)
{
    if (!m_account.isTokenValid()) {
        if (!refreshAccessToken()) {
            qWarning() << "Failed to refresh token for deleting mail item";
            return false;
        }
    }
    
    qDebug() << "Deleting mail item:" << itemUid << "from folder:" << folderId;
    
    // En una implementación real, llamaríamos a Microsoft Graph API
    // para eliminar el mensaje
    
    // Por ahora, simulamos éxito
    emit mailItemRemoved(itemUid);
    
    // Publish MailItemRemovedEvent
    WinoMail::Events::MailItemRemovedEvent removeEvent;
    removeEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
    removeEvent.timestamp = QDateTime::currentDateTimeUtc();
    removeEvent.itemUid = itemUid;
    removeEvent.folderId = folderId.toInt();
    PUBLISH(removeEvent);
    
    return true;
}

void OutlookSynchronizer::onGraphReplyFinished(QNetworkReply* reply)
{
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Graph API error:" << reply->errorString();
        emit errorOccurred(reply->errorString());
        reply->deleteLater();
        return;
    }
    
    QByteArray responseData = reply->readAll();
    reply->deleteLater();
    
    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
    
    if (parseError.error != QJsonParseError::NoError) {
        qWarning() << "Failed to parse Graph API response:" << parseError.errorString();
        emit errorOccurred("Failed to parse server response");
        return;
    }
    
    if (!doc.isObject()) {
        qWarning() << "Graph API response is not an object";
        emit errorOccurred("Invalid server response");
        return;
    }
    
    QJsonObject obj = doc.object();
    
    // Procesar según el tipo de llamada
    // En una implementación completa, aquí manejaríamos las respuestas
    // para obtener carpetas, mensajes, etc.
    
    qDebug() << "Graph API response received successfully";
}

void OutlookSynchronizer::onDeltaLinkTimer()
{
    qDebug() << "Delta link timer fired - checking for changes";
    
    // En una implementación real, aquí usaríamos los delta links
    // obtenidos previamente para consultar solo los cambios
    // desde la última sincronización
    
    // Por ahora, simplemente iniciamos una sincronización completa
    // de todas las carpetas (esto sería ineficiente en producción)
    QVector<Folder> folders = getFolders();
    for (const Folder& folder : folders) {
        syncFolder(folder);
    }
}

QString OutlookSynchronizer::buildGraphUrl(const QString& endpoint) const
{
    return QStringLiteral("https://graph.microsoft.com/v1.0%1").arg(endpoint);
}

QNetworkRequest OutlookSynchronizer::createAuthRequest(const QString& url) const
{
    QNetworkRequest request{QUrl(url)};
    request.setRawHeader("Authorization", 
                        QString("Bearer %1").arg(m_accessToken).toUtf8());
    request.setHeader(QNetworkRequest::ContentTypeHeader, 
                     "application/json");
    return request;
}

bool OutlookSynchronizer::refreshAccessToken()
{
    // En una implementación real, aquí usaríamos el refresh token
    // para obtener un nuevo access token desde Azure AD
    
    if (m_refreshToken.isEmpty()) {
        qWarning() << "No refresh token available for Outlook";
        return false;
    }

    // TODO: These should come from secure configuration, not hardcoded
    const QString clientId = "YOUR_CLIENT_ID_HERE"; // Replace with actual client ID
    const QString clientSecret = "YOUR_CLIENT_SECRET_HERE"; // Replace with actual client secret
    const QString tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";

    QNetworkRequest networkRequest{QUrl(tokenUrl)};
    networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

    QUrlQuery postData;
    postData.addQueryItem("client_id", clientId);
    postData.addQueryItem("scope", "Mail.Read Mail.ReadWrite Mail.Send offline_access");
    postData.addQueryItem("refresh_token", m_refreshToken);
    postData.addQueryItem("grant_type", "refresh_token");
    postData.addQueryItem("client_secret", clientSecret);

    QNetworkReply* reply = m_networkManager->post(networkRequest, postData.toString(QUrl::FullyEncoded).toUtf8());

    // Wait for reply synchronously for simplicity in this context
    QEventLoop loop;
    connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    loop.exec();

    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Outlook token refresh failed:" << reply->errorString();
        reply->deleteLater();
        return false;
    }

    QByteArray responseData = reply->readAll();
    reply->deleteLater();

    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        qWarning() << "Failed to parse Outlook token response:" << parseError.errorString();
        return false;
    }

    QJsonObject obj = doc.object();
    if (!obj.contains("access_token")) {
        qWarning() << "No access_token in Outlook token response";
        return false;
    }

    m_accessToken = obj["access_token"].toString();
    int expiresIn = obj["expires_in"].toInt(3600); // default 1 hour
    m_tokenExpires = QDateTime::currentDateTimeUtc().addSecs(expiresIn);

    // Update refresh token if provided (sometimes rotation is used)
    if (obj.contains("refresh_token")) {
        m_refreshToken = obj["refresh_token"].toString();
    }

    qDebug() << "Outlook access token refreshed successfully, expires in" << expiresIn << "seconds";
    return true;
}

QString OutlookSynchronizer::getDeltaLink(const QString& folderId) const
{
    return m_deltaLinks.value(folderId, QString());
}

void OutlookSynchronizer::setDeltaLink(const QString& folderId, const QString& deltaLink)
{
    m_deltaLinks.insert(folderId, deltaLink);
}

MailItem OutlookSynchronizer::parseMailItemFromGraph(const QJsonObject& mailJson) const
{
    // Esta función convertiría un objeto JSON de Microsoft Graph
    // a nuestro objeto MailItem
    
    // Por ahora, devolvemos un objeto vacío como stub
    return MailItem();
}

Folder OutlookSynchronizer::parseFolderFromGraph(const QJsonObject& folderJson) const
{
    // Esta función convertiría un objeto JSON de Microsoft Graph
    // a nuestro objeto Folder
    
    // Por ahora, devolvemos un objeto vacío como stub
    return Folder();
}
