#include "confluence-agent.h"

#include <QMessageBox>
#include <QSslSocket>

#include <iostream> // FIXME-5 for debugging...

#include "branchitem.h"
#include "confluence-user.h"
#include "file.h"
#include "mainwindow.h"
#include "misc.h"
#include "vymmodel.h"
#include "warningdialog.h"

extern Main *mainWindow;
extern QDir vymBaseDir;
extern QString confluencePassword;
extern Settings settings;
extern bool debug;

bool ConfluenceAgent::available() 
{ 
    if (!QSslSocket::supportsSsl())
        return false;
    if ( settings.value("/atlassian/confluence/username", "").toString().isEmpty())
        return false;

    if ( settings.value("/atlassian/confluence/url", "").toString().isEmpty())
        return false;

    return true;
}

ConfluenceAgent::ConfluenceAgent() { 
    //qDebug() << "Constr. ConfluenceAgent jobType=";
    init(); 
}

ConfluenceAgent::ConfluenceAgent(BranchItem *bi)
{
    //qDebug() << "Constr. ConfluenceAgent selbi = " << bi;

    if (!bi) {
        qWarning("Const ConfluenceAgent: bi == nullptr");
        // This will leave the agent hanging around undeleted...
        return;
    }

    init();

    setBranch(bi);
}

ConfluenceAgent::~ConfluenceAgent()
{
    // qDebug() << "Destr ConfluenceAgent." << jobType;
    if (killTimer)
        delete killTimer;
}

void ConfluenceAgent::init()
{
    jobType = Undefined;
    jobStep = -1;
    abortJob = false;

    killTimer = nullptr;

    networkManager = new QNetworkAccessManager(this);

    modelID = 0;    // invalid ID

    killTimer = new QTimer(this);
    killTimer->setInterval(15000);
    killTimer->setSingleShot(true);

    QObject::connect(killTimer, SIGNAL(timeout()), this, SLOT(timeout()));

    apiURL = "/rest/api";
    baseURL = settings.value("/atlassian/confluence/url", "baseURL").toString();
    
    // If pages are created recursively, ordering might be wrong
    // Save original inded before starting retrieval
    originalPageIndexInt = -1;

    // Attachments
    attachmentsAgent = nullptr;
    currentUploadAttachmentIndex = -1;

    // Read credentials 
    authUsingPAT = 
        settings.value("/atlassian/confluence/authUsingPAT", true).toBool();
    if (authUsingPAT)
        personalAccessToken =
            settings.value("/atlassian/confluence/PAT", "undefined").toString();
    else {
        username =
            settings.value("/atlassian/confluence/username", "user_johnDoe").toString();
        if (!confluencePassword.isEmpty())
            password = confluencePassword;
        else
            password = 
                settings.value("/atlassian/confluence/password", "").toString();
    }

    if (!authUsingPAT && password.isEmpty()) {
        // Set global password
        if (!mainWindow->settingsConfluence()) 
            abortJob = true;
    }
}

void ConfluenceAgent::setJobType(JobType jt)
{
    jobType = jt;
}

void ConfluenceAgent::setBranch(BranchItem *bi)
{
    if (!bi) {
        qWarning() << "ConfluenceAgent::setBranch  bi == nullptr";
        abortJob = true;
    } else {
        branchID = bi->getID();
        VymModel *model = bi->getModel();
        modelID = model->modelId();
    }
}

void ConfluenceAgent::setModelID(uint id)
{
    modelID = id;
}

void ConfluenceAgent::setPageURL(const QString &u)
{
    pageURL = u;
}

void ConfluenceAgent::setPageID(const QString &id)
{
    pageID =id;
}

void ConfluenceAgent::setOriginalPageIndex(const int &i)
{
    originalPageIndexInt = i;
}

void ConfluenceAgent::setLabelName(const QString &labelName)
{
    labelNameInt = labelName;
}

void ConfluenceAgent::setNewPageName(const QString &t)
{
    newPageName = t;
}

void ConfluenceAgent::setUploadPagePath(const QString &fp)
{
    uploadPagePath = fp;
}

void ConfluenceAgent::addUploadAttachmentPath(const QString &fp)
{
    uploadAttachmentPaths << fp;
}

void ConfluenceAgent::startJob()
{
    if (jobStep > 0) {
        unknownStepWarningFinishJob();
    } else {
        jobStep = 0;
        continueJob();
    }
}

void ConfluenceAgent::continueJob(int nextStep)
{
    if (abortJob) {
        finishJob();
        return;
    }

    if (nextStep < 0)
        jobStep++;
    else
        jobStep = nextStep;

    VymModel *model;

    // qDebug() << "CA::contJob " << jobType << " Step: " << jobStep;

    switch(jobType) {
        case GetPageDetails:
        case GetPageDetailsRecursively:
            if (jobStep == 1) {
                // Get pageID and spaceKey
                startGetPageSourceRequest(pageURL);
                return;
            }
            if (jobStep == 2) {
                startGetPageDetailsRequest();
                return;
            }
            if (jobStep == 3) {
                model = mainWindow->getModel(modelID);
                if (model) {
                    BranchItem *bi = (BranchItem *)(model->findID(branchID));

                    if (bi) {
                        QString title = pageObj["title"].toString();
                        QString h = spaceKey + ": " + title;
                        model->setHeadingPlainText(h, bi);
                        model->setAttribute( bi, "Confluence.title", title);

                        // Set labels of page as attributes
                        model->deleteAttributesKeyStartingWith(bi, "Confluence.");
                        model->setAttribute( bi, "Confluence.pageID", pageID);
                        model->setAttribute( bi, "Confluence.spaceKey", spaceKey);
                        if (originalPageIndexInt > -1)
                            model->setAttribute( bi, "Confluence.childIndex", originalPageIndexInt);

                        QJsonObject metaDataObj = pageObj["metadata"].toObject();
                        QJsonObject labelsObj = metaDataObj["labels"].toObject();
                        QJsonArray resultsArr = labelsObj["results"].toArray();

                        model->setAttribute( bi, "Confluence.labels.count", resultsArr.size());
                        for (int i = 0; i < resultsArr.size(); ++i) {
                            QJsonObject ro = resultsArr[i].toObject();
                            model->setAttribute( bi, QString("Confluence.label-%1").arg(i), ro["name"].toString());
                        }

                        QJsonObject versionObj = pageObj["version"].toObject();
                        QString timestamp = versionObj["when"].toString();
                        QJsonObject byObj = versionObj["by"].toObject();
                        QString author = byObj["displayName"].toString();
                        //qDebug() << bi->headingText() << " changed at " << timestamp << " by " << author;
                        model->setAttribute( bi, QString("Confluence.lastChanged"), timestamp);
                        model->setAttribute( bi, QString("Confluence.lastAuthor"), author);

                    } else
                        qWarning() << "CA::continueJob couldn't find branch "
                                   << branchID;
                    if (jobType == GetPageDetails) {
                        finishJob();
                        return;
                    }

                    jobStep++;
                } else {
                    qWarning() << "CA::continueJob couldn't find model " << modelID;
                    finishJob();
                    return;
                }
            }

            if (jobStep == 4) {
                startGetPageChildrenRequest();
                return;
            }

            if (jobStep == 5) {
                model = mainWindow->getModel(modelID);
                if (model) {
                    BranchItem *bi = (BranchItem *)(model->findID(branchID));

                    if (bi) {
                        QJsonObject pObj = pageObj["page"].toObject();
                        QJsonArray resultsArr = pObj["results"].toArray();

                        model->setAttribute( bi, "Confluence.children.count", resultsArr.size());
                        for (int i = 0; i < resultsArr.size(); ++i) {
                            QJsonObject ro = resultsArr[i].toObject();
                            //qDebug() << "  n=" << ro["title"].toString() << ro["id"].toString();
                            QString childTitle = ro["title"].toString();
                            QString childId = ro["id"].toString();
                            model->setAttribute( bi, QString("Confluence.child-%1.title").arg(i), childTitle);
                            model->setAttribute( bi, QString("Confluence.child-%1.id").arg(i), childId);

                            if (true) {
                                // Recursively create branches for child pages
                                // Warning: Branches might be created in a different order
                                //          than pages.
                                BranchItem *newbi = model->addNewBranch(bi);
                                if (newbi) {
                                    newbi->setHeadingPlainText(childTitle);
                                    qDebug() << "  * newbi " << childTitle;
                                    qDebug() << "          " << childId;
                                    QString newUrl = "https://" + baseURL + "/pages/viewpage.action?pageId=" + childId;
                                    // Set Url, but do not update from cloud in VymModel
                                    model->setUrl(newUrl, false, newbi);

                                    ConfluenceAgent *ca_setHeading = new ConfluenceAgent(newbi);
                                    ca_setHeading->setPageURL(newUrl);
                                    ca_setHeading->setJobType(ConfluenceAgent::GetPageDetailsRecursively);
                                    ca_setHeading->setOriginalPageIndex(i);
                                    ca_setHeading->startJob();
                                }
                            }
                        }

                    } else
                        qWarning() << "CA::continueJob couldn't find branch "
                                   << branchID;
                } // model found
            }

            finishJob();
            return;

            unknownStepWarningFinishJob();
            return;

        case DeletePageLabel:
            if (jobStep == 1) {
                // FIXME-4 check if pageID is set
                startDeleteLabelRequest();
                return;
            }

            if (jobStep == 2) {
                finishJob();
                return;
            }

            unknownStepWarningFinishJob();
            return;

        case CreatePage:
            if (jobStep == 1) {
                if (pageURL.isEmpty()) {
                    qWarning() << "CA::contJob NewPage: pageURL is empty";
                    finishJob();
                    return;
                }
                if (newPageName.isEmpty()) {
                    qWarning() << "CA::contJob NewPage: newPageName is empty";
                    finishJob();
                    return;
                }

                mainWindow->statusMessage(
                    QString("Starting to create Confluence page %1").arg(pageURL));

                // Check if parent page with url already exists and get pageID, spaceKey
                startGetPageSourceRequest(pageURL);
                return;
            }
            if (jobStep == 2) {
                // Create new page with parent url
                startCreatePageRequest();
                return;
            }
            if (jobStep == 3) {

                pageID = pageObj["id"].toString();

                // Upload attachments?
                if (uploadAttachmentPaths.count() > 0) {
                    attachmentsAgent = new ConfluenceAgent;
                    attachmentsAgent->setJobType(ConfluenceAgent::UploadAttachments);
                    attachmentsAgent->pageID = pageID;
                    attachmentsAgent->uploadAttachmentPaths = uploadAttachmentPaths;

                    connect(attachmentsAgent, &ConfluenceAgent::attachmentsSuccess,
                        this, &ConfluenceAgent::attachmentsUploadSuccess);
                    connect(attachmentsAgent, &ConfluenceAgent::attachmentsFailure,
                        this, &ConfluenceAgent::attachmentsUploadFailure);
                    attachmentsAgent->startJob();
                    return;
                } else
                    // Proceed to next step
                    jobStep = 4;
            }
            if (jobStep == 4) {
                // qDebug() << "CA::finished  Created page with ID: " << pageObj["id"].toString();
                // cout << QJsonDocument(pageObj).toJson(QJsonDocument::Indented).toStdString();
                model = mainWindow->getModel(modelID);
                if (model) {
                    pageURL = QString("https://%1/pages/viewpage.action?pageId=%2")
                        .arg(baseURL).arg(pageObj["id"].toString());
                    QString command = QString("vym.currentMap().exportMap([\"ConfluenceUpdatePage\",\"%1\"])")
                        .arg(pageURL);
                    QString dest = QString("Page title: \"%1\"\nUrl: \"%2\"")
                        .arg(pageObj["title"].toString()).arg(pageURL);
                    QString desc = tr("Update existing confluence page");
                    model->setExportLastCommand(command);
                    model->setExportLastDestination(dest);
                    model->setExportLastDescription(desc);
                    mainWindow->updateActions();
                }

                mainWindow->statusMessage(
                    QString("Created Confluence page %1").arg(pageURL));


                finishJob();
                return;
            }
            unknownStepWarningFinishJob();
            return;

        case UpdatePage:
            if (jobStep == 1) {
                if (pageURL.isEmpty()) {
                    qWarning() << "CA::contJob UpdatePage: pageURL is empty";
                    finishJob();
                    return;
                }

                mainWindow->statusMessage(
                    QString("Starting to update Confluence page %1").arg(pageURL));

                // Check if page with url already exists and get pageID, spaceKey
                startGetPageSourceRequest(pageURL);
                return;
            }
            if (jobStep == 2) {
                // Get title, which is required by Confluence to update a page
                startGetPageDetailsRequest();
                return;
            }
            if (jobStep == 3) {
                // Upload attachments?
                if (uploadAttachmentPaths.count() > 0) {
                    attachmentsAgent = new ConfluenceAgent;
                    attachmentsAgent->setJobType(ConfluenceAgent::UploadAttachments);
                    attachmentsAgent->pageID = pageID;
                    attachmentsAgent->uploadAttachmentPaths = uploadAttachmentPaths;

                    connect(attachmentsAgent, &ConfluenceAgent::attachmentsSuccess,
                        this, &ConfluenceAgent::attachmentsUploadSuccess);
                    connect(attachmentsAgent, &ConfluenceAgent::attachmentsFailure,
                        this, &ConfluenceAgent::attachmentsUploadFailure);
                    attachmentsAgent->startJob();
                    return;
                }
                jobStep++;
            }
            if (jobStep == 4) {
                // Update page with parent url
                if (newPageName.isEmpty())
                        newPageName = pageObj["title"].toString();
                startUpdatePageRequest();
                return;
            }
            if (jobStep == 5) {
                //qDebug() << "CA::finished  Updated page with ID: " << pageObj["id"].toString();
                mainWindow->statusMessage(
                    QString("Updated Confluence page %1").arg(pageURL));

                model = mainWindow->getModel(modelID);
                if (model) {
                    pageURL = QString("https://%1/pages/viewpage.action?pageId=%2")
                        .arg(baseURL).arg(pageObj["id"].toString());
                    QString command = QString("vym.currentMap().exportMap([\"ConfluenceUpdatePage\",\"%1\"])")
                        .arg(pageURL);
                    QString dest = QString("Page title: \"%1\"\nUrl: \"%2\"").arg(pageObj["title"].toString())
                        .arg(pageURL);
                    QString desc = tr("Update existing confluence page");
                    model->setExportLastCommand(command);
                    model->setExportLastDestination(dest);
                    model->setExportLastDescription(desc);
                    mainWindow->updateActions();
                }
                finishJob();
                return;
            }
            unknownStepWarningFinishJob();
            return;

        case GetUserInfo:
            if (jobStep == 1) {
                // qDebug() << "CA:: begin getting UserInfo";
                startGetUserInfoRequest();
                return;
            }
            if (jobStep == 2) {
                QJsonArray array = pageObj["results"].toArray();
                QJsonObject userObj;
                QJsonObject u;
                ConfluenceUser user;
                userList.clear();
                for (int i = 0; i < array.size(); ++i) {
                    userObj = array[i].toObject();

                    u = userObj["user"].toObject();
                    user.setTitle( userObj["title"].toString());
                    user.setUrl( "https://" + baseURL + "/"
                            + "display/~" + u["username"].toString());
                    user.setUserKey( u["userKey"].toString());
                    user.setUserName( u["username"].toString());
                    user.setDisplayName( u["displayName"].toString());
                    userList << user;
                }
                emit foundUsers(userList);
                finishJob();
                return;
            }
            unknownStepWarningFinishJob();
            return;

        case UploadAttachments:
            if (jobStep == 1) {

                if (uploadAttachmentPaths.count() <= 0) {
                    qWarning() << "ConfluenceAgent: No attachments to upload!";
                    emit attachmentsFailure();
                    finishJob();
                    return;
                }

                // Prepare to upload first attachment in list
                currentUploadAttachmentIndex = 0;

                // Try to get info for attachments
                startGetAttachmentsInfoRequest();
                return;
            }
            if (jobStep == 2) {
                // Entry point for looping over list of attachments to upload

                if (currentUploadAttachmentIndex >= uploadAttachmentPaths.count()) {
                    // All uploaded, let's finish uploading
                    emit attachmentsSuccess();
                    finishJob();
                } else {
                    currentAttachmentPath = uploadAttachmentPaths.at(currentUploadAttachmentIndex);
                    currentAttachmentTitle = basename(currentAttachmentPath);

                    // Create attachment with image of map, if required
                    if (attachmentsTitles.count() == 0 || 
                        !attachmentsTitles.contains(currentAttachmentTitle)) {
                        // Create new attachment
                        startCreateAttachmentRequest();
                    } else {
                        // Update existing attachment
                        startUpdateAttachmentRequest();
                    }
                }
                return;
            }
            unknownStepWarningFinishJob();
            return;

        default:
            qWarning() << "ConfluenceAgent::continueJob   unknown jobType " << jobType;
    }
}

void ConfluenceAgent::finishJob()
{
    deleteLater();
}

void ConfluenceAgent::unknownStepWarningFinishJob()
{
    qWarning() << "CA::contJob  unknown step in jobType = " 
        << jobType 
        << "jobStep = " << jobStep;
    finishJob();
}

void ConfluenceAgent::getUsers(const QString &usrQuery)
{
    userQuery = usrQuery;
    if (usrQuery.contains(QRegularExpression("\\W+"))) {
        qWarning() << "ConfluenceAgent::getUsers  Forbidden characters in " << usrQuery;
        return;
    }

    setJobType(GetUserInfo);
    startJob();
}

QNetworkRequest ConfluenceAgent::createRequest(const QUrl &url)
{
    QNetworkRequest request = QNetworkRequest(url);

    QString headerData;
    if (authUsingPAT)
        headerData = QString("Bearer %1").arg(personalAccessToken);
    else {
        QString concatenated = username + ":" + password;
        QByteArray data = concatenated.toLocal8Bit().toBase64();
        headerData = "Basic " + data;
    }
    request.setRawHeader("Authorization", headerData.toLocal8Bit());

    return request;
}

void ConfluenceAgent::startGetPageSourceRequest(QUrl requestedURL)
{
    //qDebug() << "CA::startGetPageSourceRequest " << requestedURL;
    if (!requestedURL.toString().startsWith("http"))
        requestedURL.setPath("https://" + requestedURL.path());

    QUrl url = requestedURL;

    QNetworkRequest request = createRequest(url);

    if (debug)
        qDebug() << "CA::startGetPageSourceRequest: url = " + request.url().toString();

    killTimer->start();

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::pageSourceReceived);

    networkManager->get(request);
}

void ConfluenceAgent::pageSourceReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::pageSourceReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "receive page source", fullReply))
        return;

    // Find pageID
    QRegularExpression re("\\sname=\"ajs-page-id\"\\scontent=\"(\\d*)\"");
    re.setPatternOptions(QRegularExpression::InvertedGreedinessOption);

    QRegularExpressionMatch match = re.match(fullReply);
    if (match.hasMatch())
        pageID = match.captured(1);
    else {
        qWarning()
            << "ConfluenceAgent::pageSourceReveived Couldn't find page ID";
        //qWarning() << fullReply;
        return;
    }

    // Find spaceKey 
    re.setPattern("meta\\s*id=\"confluence-space-key\"\\s* "
                  "name=\"confluence-space-key\"\\s*content=\"(.*)\"");
    match = re.match(fullReply);
    if (match.hasMatch())
        spaceKey = match.captured(1);
    else {
        qWarning() << "ConfluenceAgent::pageSourceReveived Couldn't find "
                      "space key in response";
        qWarning() << fullReply;
        finishJob();
        return;
    }

    const QVariant redirectionTarget =
        reply->attribute(QNetworkRequest::RedirectionTargetAttribute);

    continueJob();
}

void ConfluenceAgent::startGetPageDetailsRequest()
{
    if (debug) qDebug() << "CA::startGetPageDetailsRequest" << pageID;

    // Authentication in URL  (only SSL!)
    QString url = "https://" 
        + baseURL + apiURL 
        + "/content/" + pageID + "?expand=metadata.labels,version";

    QNetworkRequest request = createRequest(url);

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::pageDetailsReceived);

    killTimer->start();

    networkManager->get(request);
}

void ConfluenceAgent::pageDetailsReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::pageDetailsReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "receive page details", fullReply))
        return;

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);

    pageObj = jsdoc.object();
    cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();

    continueJob();
}

void ConfluenceAgent::startGetPageChildrenRequest()
{
    if (debug) qDebug() << "CA::startGetPageChildrenRequest" << pageID;

    // Authentication in URL  (only SSL!)
    QString url = "https://"
        + baseURL + apiURL
        + "/content/" + pageID + "/child?expand=page&limit=999";    // API v1
        // + "/v2/pages/" + pageID + "/children"; //?expand=page";      // API v2

    QNetworkRequest request = createRequest(url);

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::pageChildrenReceived);

    killTimer->start();

    networkManager->get(request);
}

void ConfluenceAgent::pageChildrenReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::pageChildrenReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);

    cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();
    pageObj = jsdoc.object();

    if (!wasRequestSuccessful(reply, "receive page children", fullReply))
        return;

    continueJob();
}

void ConfluenceAgent::startDeleteLabelRequest()
{
    if (debug) qDebug() << "CA::startDeleteLabelRequest" << pageID;

    // Authentication in URL  (only SSL!)
    QString url = "https://"
        + baseURL + apiURL
        + "/content/" + pageID + "/label/" + labelNameInt;    // API v1

    QNetworkRequest request = createRequest(url);

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::deleteLabelResponseReceived);

    killTimer->start();

    networkManager->deleteResource(request);
}

void ConfluenceAgent::deleteLabelResponseReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::deleteLabelResponseReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);

    cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();
    pageObj = jsdoc.object();

    if (!wasRequestSuccessful(reply, "received delete label response", fullReply))
        return;

    continueJob();
}

void ConfluenceAgent::startCreatePageRequest()
{
    // qDebug() << "CA::startCreatePageRequest";

    QString url = "https://" + baseURL + apiURL + "/content";

    QNetworkRequest request = createRequest(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    QJsonObject payload;
    payload["type"] = "page";
    payload["title"] = newPageName;

    // Build array with ID of parent page
    QJsonObject ancestorsID;
    ancestorsID["id"] = pageID;
    QJsonArray ancestorsArray;
    ancestorsArray.append(ancestorsID);
    payload["ancestors"] = ancestorsArray;

    // Build object with space key
    QJsonObject skey;
    skey["key"] = spaceKey;
    payload["space"] = skey;

    // Build body
    QString body;
    if (!loadStringFromDisk(uploadPagePath, body))
    {
        qWarning() << "ConfluenceAgent: Couldn't read file to upload:" << uploadPagePath;
        finishJob();
        return;
    }

    QJsonObject innerStorageObj
    {
        {"value", body},
        {"representation", "storage"}
    };
    QJsonObject outerStorageObj;
    outerStorageObj["storage"] = innerStorageObj;
    payload["body"] = outerStorageObj;

    QJsonDocument doc(payload);
    QByteArray data = doc.toJson();

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::pageUploaded);

    killTimer->start();

    networkManager->post(request, data);
}

void ConfluenceAgent::startUpdatePageRequest()
{
    if (debug) qDebug() << "CA::startUpdatePageRequest";

    QString url = "https://" + baseURL + apiURL + "/content" + "/" + pageID;

    QNetworkRequest request = createRequest(url);

    request.setHeader(
        QNetworkRequest::ContentTypeHeader,
        "application/json; charset=utf-8");

    QJsonObject payload;
    payload["id"] = pageID;
    payload["type"] = "page";
    payload["title"] = newPageName;

    // Build version object
    QJsonObject newVersionObj;
    QJsonObject oldVersionObj = pageObj["version"].toObject();

    newVersionObj["number"] = oldVersionObj["number"].toInt() + 1;
    payload["version"] = newVersionObj;

    // Build object with space key
    QJsonObject skey;
    skey["key"] = spaceKey;
    payload["space"] = skey;

    // Build body
    QString body;
    if (!loadStringFromDisk(uploadPagePath, body))
    {
        qWarning() << "ConfluenceAgent: Couldn't read file to upload:" << uploadPagePath;
        finishJob();
        return;
    }

    QJsonObject innerStorageObj
    {
        {"value", body},
        {"representation", "storage"}
    };
    QJsonObject outerStorageObj;
    outerStorageObj["storage"] = innerStorageObj;
    payload["body"] = outerStorageObj;

    QJsonDocument doc(payload);
    QByteArray data = doc.toJson();

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::pageUploaded);

    killTimer->start();

    networkManager->put(request, data);
}

void ConfluenceAgent::pageUploaded(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::pageUploaded";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "upload page", fullReply))
        return;

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);
    pageObj = jsdoc.object();
    // cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();
    continueJob();
}

void ConfluenceAgent::startGetUserInfoRequest()
{
    if (debug) qDebug() << "CA::startGetInfoRequest for " << userQuery;

    QString url = "https://" + baseURL + apiURL
        + "/search?cql=user.fullname~" + userQuery;

    networkManager->disconnect();

    QNetworkRequest request = createRequest(url);

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::userInfoReceived);

    killTimer->start();

    networkManager->get(request);
}

void ConfluenceAgent::userInfoReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::UserInfopageReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "receive user info", fullReply))
        return;

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);
    pageObj = jsdoc.object();
    continueJob();
}

void ConfluenceAgent::startGetAttachmentsInfoRequest()
{
    if (debug) qDebug() << "CA::startGetAttachmentIdRequest";

    QString url = "https://" + baseURL + apiURL + "/content" + "/" + pageID + "/child/attachment";

    QNetworkRequest request = createRequest(url);
    request.setRawHeader("X-Atlassian-Token", "no-check");

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::attachmentsInfoReceived);

    killTimer->start();

    QNetworkReply *reply = networkManager->get(request);
}

void ConfluenceAgent::attachmentsInfoReceived(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::attachmentsInfoReceived";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "get attachment info", fullReply))
        return;

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);

    attachmentObj = jsdoc.object();
    int attachmentsCount = jsdoc["size"].toInt();
    //cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();
    for (int i = 0; i < attachmentsCount; i++) {
        attachmentsTitles << jsdoc["results"][i]["title"].toString();
        attachmentsIds    << jsdoc["results"][i]["id"].toString();
        //qDebug() << " Title: " << attachmentsTitles.last() << 
        //            " Id: " << attachmentsIds.last();
    }

    continueJob();
}

void ConfluenceAgent::startCreateAttachmentRequest()
{
    if (debug) qDebug() << "CA::startCreateAttachmentRequest";

    QString url = "https://" + baseURL + apiURL + "/content" + "/" + pageID + "/child/attachment";

    QNetworkRequest request = createRequest(url);
    request.setRawHeader("X-Atlassian-Token", "no-check");

    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    QHttpPart imagePart;
    imagePart.setHeader(
            QNetworkRequest::ContentDispositionHeader,

            // Name must be "file"
            QVariant(
                QString("form-data; name=\"file\"; filename=\"%1\"")
                    .arg(currentAttachmentTitle)));
    imagePart.setHeader(
            QNetworkRequest::ContentTypeHeader,
            QVariant("image/jpeg"));

    QFile *file = new QFile(currentAttachmentPath);
    if (!file->open(QIODevice::ReadOnly)) {
        qWarning() << "Problem opening attachment: " << currentAttachmentPath;
        QMessageBox::warning(
            nullptr, tr("Warning"),
            QString("Could not open attachment file \"%1\" in page with ID: %2").arg(currentAttachmentTitle, pageID));
        finishJob();
        return;
    }
    imagePart.setBodyDevice(file);
    /*
    qDebug() << "      title=" << currentAttachmentTitle;
    qDebug() << "       path=" << currentAttachmentPath;
    qDebug() << "        url=" << url;
    qDebug() << "  file size=" << file->size();
    */
    multiPart->append(imagePart);
    file->setParent(multiPart); // delete later with the multiPart

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::attachmentCreated);

    killTimer->start();

    QNetworkReply *reply = networkManager->post(request, multiPart);

    multiPart->setParent(reply);
}

void ConfluenceAgent::attachmentCreated(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::attachmentCreated";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
        if (fullReply.contains(
                    QString("Cannot add a new attachment with same file name as an existing attachment").toLatin1())) {
            // Replace existing attachment
            qWarning() << "Attachment with name " << currentAttachmentTitle << " already exists.";
            qWarning() << "AttachmentID unknown, stopping now"; 

            finishJob();
            return;
        }
        if (!wasRequestSuccessful(reply, "create attachment", fullReply))
            return;
    }

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);
    attachmentObj = jsdoc.object();

    //qDebug() << "CA::attachmentCreated Successful:";
    //cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();
    //cout << attachmentObj["results"].toArray().toStdString();

    currentUploadAttachmentIndex++;

    continueJob(2);
}

void ConfluenceAgent::startUpdateAttachmentRequest()
{
    if (debug) qDebug() << "CA::startUpdateAttachmentRequest";

    for (int i = 0; i < attachmentsTitles.count(); i++) {
        // qDebug() << "     - " << attachmentsTitles.at(i);
        if (attachmentsTitles.at(i) == currentAttachmentTitle) {
            currentAttachmentId = attachmentsIds.at(i);
            break;
        }
    }

    if (currentAttachmentId.isEmpty()) {
        QMessageBox::warning(
            nullptr, tr("Warning"),
            QString("Could not find existing attachment \"%1\" in page with ID: %2").arg(currentAttachmentTitle).arg(pageID));
        finishJob();
        return;
    }

    QString url = "https://" + baseURL + apiURL + "/content" + "/" + pageID + "/child/attachment/" + currentAttachmentId + "/data";

    QNetworkRequest request = createRequest(url);
    request.setRawHeader("X-Atlassian-Token", "no-check");

    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    QHttpPart imagePart;
    imagePart.setHeader(
            QNetworkRequest::ContentDispositionHeader,

            // Name must be "file"
            QVariant(
                QString("form-data; name=\"file\"; filename=\"%1\"")
                    .arg(currentAttachmentTitle)));
    imagePart.setHeader(
            QNetworkRequest::ContentTypeHeader,
            QVariant("image/jpeg"));

    QFile *file = new QFile(currentAttachmentPath);
    if (!file->open(QIODevice::ReadOnly)) {
        qWarning() << "Problem opening attachment: " << currentAttachmentPath;
        QMessageBox::warning(
            nullptr, tr("Warning"),
            QString("Could not open attachment file \"%1\" in page with ID: %2").arg(currentAttachmentTitle).arg(pageID));
        finishJob();
        return;
    }
    imagePart.setBodyDevice(file);
    /*
    qDebug() << "      title=" << currentAttachmentTitle;
    qDebug() << "       path=" << currentAttachmentPath;
    qDebug() << "        url=" << url;
    qDebug() << "  file size=" << file->size();
    */
    multiPart->append(imagePart);
    file->setParent(multiPart);

    connect(networkManager, &QNetworkAccessManager::finished,
        this, &ConfluenceAgent::attachmentUpdated);

    killTimer->start();

    QNetworkReply *reply = networkManager->post(request, multiPart);

    multiPart->setParent(reply);
}

void ConfluenceAgent::attachmentUpdated(QNetworkReply *reply)
{
    if (debug) qDebug() << "CA::attachmentUpdated";

    killTimer->stop();
    networkManager->disconnect();
    reply->deleteLater();

    QByteArray fullReply = reply->readAll();
    if (!wasRequestSuccessful(reply, "update attachment", fullReply))
        return;

    QJsonDocument jsdoc;
    jsdoc = QJsonDocument::fromJson(fullReply);
    attachmentObj = jsdoc.object();

    //cout << jsdoc.toJson(QJsonDocument::Indented).toStdString();

    currentUploadAttachmentIndex++;

    continueJob(2);
}

void ConfluenceAgent::attachmentsUploadSuccess() // slot called from attachmentsAgent
{
    continueJob();
}

void ConfluenceAgent::attachmentsUploadFailure() // slot called from attachmentsAgent
{
    qWarning() << "CA::attachmentsUpload failed";
    finishJob();
}

bool ConfluenceAgent::wasRequestSuccessful(QNetworkReply *reply, const QString &requestDesc, const QByteArray &fullReply)
{
    if (reply->error()) {

        // Additionally print full error on console
        qWarning() << "         Step: " << requestDesc;
        qWarning() << "        Error: " << reply->error();
        qWarning() << "  Errorstring: " <<  reply->errorString();

        qDebug() << "    Request Url: " << reply->url() ;
        qDebug() << "      Operation: " << reply->operation() ;

        qDebug() << "      readAll: ";
        QJsonDocument jsdoc;
        jsdoc = QJsonDocument::fromJson(fullReply);
        QString fullReplyFormatted = QString(jsdoc.toJson(QJsonDocument::Indented));
        cout << fullReplyFormatted.toStdString();

        /*
        qDebug() << "Request headers: ";
	QList<QByteArray> reqHeaders = reply->rawHeaderList();
	foreach( QByteArray reqName, reqHeaders )
        {
            QByteArray reqValue = reply->rawHeader( reqName );
            qDebug() << "  " << reqName << ": " << reqValue;
	}
        */

        if (reply->error() == QNetworkReply::AuthenticationRequiredError)
            QMessageBox::warning(
                nullptr, tr("Warning"),
                tr("Authentication problem when contacting Confluence") + "\n\n" + 
                requestDesc);
        else {
            QString msg = QString("QNetworkReply error when trying to \"%1\"\n\n").arg(requestDesc);
            WarningDialog warn;
            warn.setText(msg + "\n\n" + fullReplyFormatted);
            warn.showCancelButton(false);
            warn.exec();
        }

        finishJob();
        return false;
    } else
        return true;
}

void ConfluenceAgent::timeout()
{
    qWarning() << "ConfluenceAgent timeout!!   jobType = " << jobType;
}

#ifndef QT_NO_SSL
void ConfluenceAgent::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{
    QString errorString;
    foreach (const QSslError &error, errors) {
        if (!errorString.isEmpty())
            errorString += '\n';
        errorString += error.errorString();
    }

    reply->ignoreSslErrors();
    qWarning() << "ConfluenceAgent: One or more SSL errors has occurred: " << errorString;
    qWarning() << "Errors ignored.";
}
#endif
