#include "uossimplelog.h"
#include "network/httpclient.h"
#include "global_define.h"

#include <QDebug>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStandardPaths>
#include <QFileInfo>
#include <QLoggingCategory>

Q_DECLARE_LOGGING_CATEGORY(logTAS)

using namespace uos_ai;

UosSimpleLog::UosSimpleLog(QObject *parent)
    : QThread(parent)
    , m_stopLogging(false)
{
    initServerAddress();
    start();
}

UosSimpleLog::~UosSimpleLog()
{
    m_stopLogging.store(true);
    while (isRunning()) {
        m_condition.wakeAll();
        QThread::msleep(200);
    }
}

UosSimpleLog &UosSimpleLog::instance()
{
    static UosSimpleLog instance(nullptr);
    return instance;
}

void UosSimpleLog::addLog(const UosLogObject &logObj)
{
    if (!m_stopLogging.load()) {
        m_preLogObjectQueue.enqueue(logObj);
        m_mutex.lock();
        m_condition.wakeAll();
        m_mutex.unlock();
        qCDebug(logTAS) << "Log object enqueued, type:" << logObj.type;
    }
}

void UosSimpleLog::addRateLog(const UosRateLog &logObj)
{
    if (!m_stopLogging.load()) {
        m_preRateLogQueue.enqueue(logObj);
        m_mutex.lock();
        m_condition.wakeAll();
        m_mutex.unlock();
        qCDebug(logTAS) << "Rate log enqueued";
    }
}

QJsonObject UosSimpleLog::toJson(const UosRateLog &logObj)
{
    QJsonObject logTextJson;
    logTextJson["app"] = logObj.app;
    logTextJson["llm"] = logObj.llm;
    logTextJson["question"] = logObj.question;
    logTextJson["answer"] = logObj.answer;
    logTextJson["questionTime"] = logObj.questionTime;
    logTextJson["answerTime"] = logObj.answerTime;
    logTextJson["modelType"] = logObj.modelType;
    logTextJson["assistantName"] = logObj.assistantName;

    if (logObj.likeOrNot != UosRateLog::None)
        logTextJson["likeOrNot"] = logObj.likeOrNot;

    return logTextJson;
}

void UosSimpleLog::run()
{
    while (!m_stopLogging.load()) {
        m_mutex.lock();
        if (m_preLogObjectQueue.isEmpty() && m_preRateLogQueue.isEmpty()) {
            m_condition.wait(&m_mutex, 1000);
        }
        m_mutex.unlock();

        {
            UosLogObject logObj;
            while (m_preLogObjectQueue.dequeue(logObj) && !m_stopLogging.load()) {
                int nCount = 3;
                while (nCount-- > 0) {
                    if (QNetworkReply::NetworkError::NoError != pushLog(logObj)) {
                        qCWarning(logTAS) << "Try push log again, type:" << logObj.type << ", left retry:" << nCount;
                    } else {
                        break;
                    }
                }
            }
        }

        {
            UosRateLog logObj;
            while (m_preRateLogQueue.dequeue(logObj) && !m_stopLogging.load()) {
                int nCount = 3;
                while (nCount-- > 0) {
                    if (QNetworkReply::NetworkError::NoError != pushLog(logObj)) {
                        qCWarning(logTAS) << "Try push rate log again, left retry:" << nCount;
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

int UosSimpleLog::pushLog(const UosLogObject &logObj)
{
    QJsonDocument document;
    QJsonObject logTextJson;
    logTextJson["app"] = logObj.app;
    logTextJson["llm"] = logObj.llm;

    if (logObj.type == UosLogType::UserInput || logObj.type == UosLogType::TextToImageResult) {
        logTextJson["content"] = logObj.content;
        logTextJson["datetime"] = logObj.time.toString("yyyy-MM-dd hh:mm:ss");;
        logTextJson["modelType"] = logObj.ModelType;
        logTextJson["assistantName"] = logObj.assistant;
    }

    if (logObj.type == UosLogType::TextToImageResult) {
        logTextJson["result"] = logObj.t2iResult;
    }

    document.setObject(logTextJson);

    const QByteArray &sendData = document.toJson(QJsonDocument::Compact);
    return sendLog(sendData, logObj.type);
}

int UosSimpleLog::pushLog(const UosRateLog &logObj)
{
    QJsonDocument document;
    document.setObject(toJson(logObj));

    const QByteArray &sendData = document.toJson(QJsonDocument::Compact);
    return sendLog(sendData, logObj.type);
}

int UosSimpleLog::sendLog(const QByteArray &sendData, UosLogType logType)
{
    uos_ai::HttpClient httpClient;
    httpClient.setTimeOut(15000);
    
    QJsonObject data;
    QJsonDocument document = QJsonDocument::fromJson(sendData);
    if (document.isObject()) {
        data = document.object();
    }
    
    QVariantHash header;
    header["Content-Type"] = "application/json";
    
    uos_ai::HttpClient::HttpResponse response = httpClient.post(QUrl(hostUrl(logType)), data, header);
    
    QNetworkReply::NetworkError netReplyError = response.error;

    if (netReplyError == QNetworkReply::NetworkError::NoError) {
        QJsonDocument respJson = QJsonDocument::fromJson(response.data);
        if (respJson.isObject()) {
            auto resultObj = respJson.object();
            if (resultObj.contains("status")) {
                switch (resultObj["status"].toInt()) {
                case 200:
                    netReplyError = QNetworkReply::NetworkError::NoError;
                    break;
                case 201:
                    netReplyError = QNetworkReply::NetworkError::OperationNotImplementedError;
                    break;
                case 401:
                    netReplyError = QNetworkReply::NetworkError::AuthenticationRequiredError;
                    break;
                case 403:
                    netReplyError = QNetworkReply::NetworkError::ContentAccessDenied;
                    break;
                case 404:
                    netReplyError = QNetworkReply::NetworkError::HostNotFoundError;
                    break;
                default:
                    break;
                }
            }
        }
    }
    if (netReplyError != QNetworkReply::NetworkError::NoError) {
        qCWarning(logTAS) << "Push log failed, type:" << logType << ", req size:" << sendData.size() << ", resp:" << QString::fromUtf8(response.data);
    }

    return netReplyError;
}

QString UosSimpleLog::simplifiedText(const QString &content)
{
    int start = content.indexOf("Input:") + strlen("Input:");
    int end = content.indexOf("Output:");
    if (start < 0 || (end < start)) {
        return content;
    }
    return QString(content.begin() + start
                   , (end < 0 ? content.length() - start : end - start))
           .simplified();
}

QString UosSimpleLog::hostUrl(UosLogType type) const
{
    if (serverUrls.contains(type))
        return serverUrls[type];
    return "";
}

void UosSimpleLog::initServerAddress()
{
    QString testServerJsonPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/TestServer.json";
    QString apiAddress;
    if (QFileInfo::exists(testServerJsonPath)) {
        QFile dataInfoFile(testServerJsonPath);
        if (dataInfoFile.open(QIODevice::ReadOnly)) {
            QByteArray jsonData = dataInfoFile.readAll();
            QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
            QJsonObject jsonObj = jsonDoc.object();
            apiAddress = jsonObj["TestServer"].toString();
            qInfo() << "API Server Address is Test.";
        }
    } else {
        apiAddress = uos_ai::UosAPIAddress;
    }

    serverUrls = {
        {UosLogType::UserInput, apiAddress + "/saveAiModel"},
        {UosLogType::FailedRetry, apiAddress + "/saveRetryRecord"},
        {UosLogType::TextToImageResult, apiAddress + "/saveModelResult"},
        {UosLogType::AnwserRate, apiAddress + "/saveAiModelLikes"}
    };
}
