#include "NetworkService.h"

#include <QUrlQuery>
#include <QHttpPart>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonArray>
#include <QThread>
#include <QSettings>
#include <QTimer>
#include <QTimeZone>

#include "dao/AccountDAO.h"
#include "dao/EventDAO.h"
#include "dao/TransactionDAO.h"
#include "db/EventInfo.h"

NetworkService::NetworkService(QObject *parent) : QObject(parent) {
    const QSettings settings("config.ini", QSettings::IniFormat);
    m_urlAccountUpdate = settings.value("net/accountUpdate").toString();
    m_urlPaymentsGet = settings.value("net/paymentsGet").toString();
    m_urlTransactionUpdate = settings.value("net/transactionUpdate").toString();
    m_urlTransactionInsert = settings.value("net/transactionInsert").toString();
    m_manager = new QNetworkAccessManager(this);
}

NetworkService::~NetworkService() = default;

// FIXME доделать работу с экстра
void NetworkService::addExtra(const QString &key, const QVariant &value) {
    m_extraData[key] = value;
}

void addFormField(QHttpMultiPart *multiPart, const QString &fieldName, const QString &fieldValue) {
    QHttpPart formField;
    formField.setHeader(
        QNetworkRequest::ContentDispositionHeader,QStringLiteral("form-data; name=\"%1\"").arg(fieldName)
    );
    formField.setBody(fieldValue.toUtf8());
    multiPart->append(formField);
}

void addFormMetadata(QHttpMultiPart *multiPart, const QByteArray &fieldValue) {
    QHttpPart jsonPart;
    jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8");
    jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="metadata")");
    jsonPart.setBody(fieldValue);
    multiPart->append(jsonPart);
}

QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path) {
    const QUrl url(path);
    const QNetworkRequest request(url);

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

    QEventLoop loop;
    connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
        loop.quit();
    });
    loop.exec(); // <- здесь и запускается event-loop потока

    QJsonValue result;
    if (reply->error() == QNetworkReply::NoError) {
        const QByteArray raw = reply->readAll();
        QJsonParseError err;
        const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
        if (err.error == QJsonParseError::NoError && doc.isObject()) {
            const QJsonObject root = doc.object();
            if (root.value("success").toBool()) {
                result = root.value("data");
            } else {
                qCritical() << "Ошибка:" << root;
            }
        }
    } else {
        qCritical() << "Ошибка:" << reply->errorString() << path;
    }
    reply->deleteLater();
    return result;
}

QJsonArray NetworkService::get(const QString &path, const QMap<QString, QString> &params) {
    QUrl url(path);
    QUrlQuery query;
    for (auto it = params.constBegin(); it != params.constEnd(); ++it) {
        query.addQueryItem(it.key(), it.value());
    }
    url.setQuery(query);

    const QNetworkRequest request(url);
    QNetworkReply *reply = m_manager->get(request);
    QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> guard(reply);

    QEventLoop loop;
    connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
        loop.quit();
    });
    loop.exec(); // <- здесь и запускается event-loop потока

    QJsonArray result;
    if (reply->error() == QNetworkReply::NoError) {
        const QByteArray raw = reply->readAll();
        QJsonParseError err;
        const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
        if (err.error == QJsonParseError::NoError && doc.isObject()) {
            const QJsonObject root = doc.object();
            if (root.value("success").toBool()) {
                result = root.value("data").toArray();
            } else {
                qWarning() << "Ошибка:" << root;
            }
        }
    } else {
        qWarning() << "Ошибка:" << reply->errorString();
    }
    reply->deleteLater();
    return result;
}

void NetworkService::postAccountsData() {
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    addFormMetadata(multiPart, m_json);
    addFormField(multiPart, "desktopUniqueName", m_desktopId);
    addFormField(multiPart, "androidUniqueName", m_deviceId);
    const bool isSuccess = post(multiPart, m_urlAccountUpdate).toBool();
    if (!isSuccess) {
        qCritical() << "Cant post accounts data";
    }

    emit finished();
}

void NetworkService::getPayments() {
    QMap<QString, QString> params;
    params["desktopUniqueName"] = m_desktopId;
    const QJsonArray data = get(m_urlPaymentsGet, params);

    for (QJsonValue item: data) {
        QJsonObject obj = item.toObject();
        EventInfo eventInfo;
        eventInfo.status = EventStatus::Wait;
        eventInfo.externalId = obj["id"].toInt();
        eventInfo.timestamp = QDateTime::fromMSecsSinceEpoch(obj["timestamp"].toDouble(), QTimeZone::utc());
        eventInfo.amount = obj["amount"].toDouble();
        eventInfo.bankName = obj["bankName"].toString();
        eventInfo.phone = obj["phone"].toString();

        QString deviceId = obj["deviceId"].toString();
        QString appName = obj["appName"].toString();
        QString lastNumbers = obj["lastNumbers"].toString();
        AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumbers);

        eventInfo.accountId = account.id;
        eventInfo.deviceId = account.deviceId;

        if (int id = EventDAO::insertEvent(eventInfo); id == -1) {
            qCritical() << "Cant insert event: " << eventInfo.externalId;
        }
    }

    if (!m_running) {
        emit finished();
    }
}


void NetworkService::updateTransactionData() {
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    addFormMetadata(multiPart, m_json);
    addFormField(multiPart, "desktopUniqueName", m_desktopId);
    addFormField(multiPart, "androidUniqueName", m_deviceId);
    addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString());

    const bool isSuccess = post(multiPart, m_urlTransactionUpdate).toBool();
    if (!isSuccess) {
        qCritical() << "Cant update transaction: updateTransactionData";
    }

    emit finished();
}

void NetworkService::insertTransactionData() {
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    addFormMetadata(multiPart, m_json);
    addFormField(multiPart, "desktopUniqueName", m_desktopId);
    addFormField(multiPart, "androidUniqueName", m_deviceId);
    addFormField(multiPart, "lastNumbers", m_extraData["lastNumbers"].toString());

    const int id = post(multiPart, m_urlTransactionInsert).toInt();
    if (id > 0) {
        const int transactionId = m_extraData["transactionId"].toInt();
        if (!TransactionDAO::updateExternalTransactionId(transactionId, id)) {
            qCritical() << "Cant update transaction id: " << id;
        }
    }
    emit finished();
}

void NetworkService::start() {
    QTimer *poller = new QTimer(this);
    poller->setInterval(1000);
    connect(poller, &QTimer::timeout, this, &NetworkService::getPayments);
    poller->start();
}

void NetworkService::stop() {
    m_running = false;
}
