/*
 * Copyright (C) 2020, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "kadbusoperator.h"
#include "chatdatabase.h"
#include "configmanager.h"
#include "notificationnotify.h"
#include <QDBusConnection>
#include <QJsonDocument>
#include <QJsonObject>
#include <ukui-log4qt.h>

namespace {

constexpr QLatin1String kVoiceAssistantSessionUuid("voice-assistant");

QString normalizeVoiceAssistantMessageForChat(const QString &message)
{
    QJsonParseError jsonErr;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toUtf8(), &jsonErr);
    if (jsonErr.error != QJsonParseError::NoError || !jsonDoc.isObject()) {
        return message;
    }

    QJsonObject obj = jsonDoc.object();
    if (!obj.contains("sentByUser") && !obj.contains("uniqueIndex")) {
        return message;
    }

    const bool sentByUser = obj.value("sentByUser").toBool(false);
    obj["author"] = sentByUser ? "User" : "Bot";
    obj["isEnd"] = obj.value("isEnd").toBool(true);
    obj["isRefreshButtonVisiable"] = sentByUser ? false : obj.value("isRefreshButtonVisiable").toBool(true);
    obj["colorMsg"] = obj.value("colorMsg").toString() == "red" ? "red" : "";
    obj["drawMsg"] = obj.value("drawMsg").toBool(false);
    obj["imageSrc"] = obj.value("imageSrc").toString();
    obj["imagePath"] = obj.value("imagePath").toString();
    obj["showImageOptions"] = obj.value("showImageOptions").toBool(false);
    obj["showRead"] = obj.value("showRead").toBool(true);
    obj["msgHead"] = obj.value("msgHead").toString();
    obj["promptId"] = obj.value("promptId").toInt(-1);
    obj["promptIconPath"] = obj.value("promptIconPath").toString();
    const QString modelMsg = obj.value("modelMsg").toString();
    obj["modelMsg"] = (!sentByUser && modelMsg.isEmpty())
            ? QStringLiteral("Content generated by AI") : modelMsg;
    obj["reasonMessage"] = obj.value("reasonMessage").toString();
    obj["isCleared"] = obj.value("isCleared").toBool(false);
    obj["isAddSchedule"] = obj.value("isAddSchedule").toBool(false);
    obj["isGenerateToDo"] = obj.value("isGenerateToDo").toBool(false);
    obj["isSkipAddSchedule"] = obj.value("isSkipAddSchedule").toBool(false);
    obj["isAddScheduleShow"] = obj.value("isAddScheduleShow").toBool(false);
    obj["pdfPath"] = obj.value("pdfPath").toString();
    obj["noModelError"] = obj.value("noModelError").toString();
    obj["isFirst"] = obj.value("isFirst").toBool(true);
    obj["speechFlag"] = true;
    obj["isShowInfoSources"] = obj.value("isShowInfoSources").toBool(false);
    obj["infoSources"] = obj.value("infoSources").toString();
    obj["isShowWebSearchSources"] = obj.value("isShowWebSearchSources").toBool(false);
    obj["webSearchSources"] = obj.value("webSearchSources").toString();
    obj["pictureOperates"] = obj.value("pictureOperates").toString();
    obj["isShowPictureOperateRefresh"] = obj.value("isShowPictureOperateRefresh").toBool(true);
    obj["operatorId"] = obj.value("operatorId").toInt(0);
    obj["isLike"] = obj.value("isLike").toBool(false);
    obj["isDislike"] = obj.value("isDislike").toBool(false);
    obj["feedbackURL"] = obj.value("feedbackURL").toString();
    obj["closePopup"] = obj.value("closePopup").toBool(false);
    obj["allFollowUpQuestion"] = obj.value("allFollowUpQuestion").toString();
    obj["fileType"] = obj.value("fileType").toString();
    obj["documents"] = obj.value("documents").toString();
    obj["questionModeEnabledWhenAnswer"] = obj.value("questionModeEnabledWhenAnswer").toString();
    obj["deepThinkingEnabledWhenCreated"] = obj.value("deepThinkingEnabledWhenCreated").toBool(false);
    obj["targetDocument"] = obj.value("targetDocument").toString();
    obj["todoList"] = obj.value("todoList").toString();
    obj["commandUuid"] = obj.value("commandUuid").toString();
    obj["taskToolCalls"] = obj.value("taskToolCalls").toString();

    return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
}

KADBusOperator::KADBusOperator(QObject *parent)
    : QObject{parent}
{
    QDBusConnection::sessionBus().unregisterService("org.ukui.kylin_aiassistant");
    QDBusConnection::sessionBus().registerService("org.ukui.kylin_aiassistant");
    QDBusConnection::sessionBus().registerObject("/org/ukui/kylin_aiassistant", "org.ukui.kylin_aiassistant", this,
        QDBusConnection::ExportNonScriptableSlots | QDBusConnection::ExportAllSignals);
}

KADBusOperator::~KADBusOperator()
{

}

void KADBusOperator::showOrHideView()
{
    Q_EMIT sigShowOrHideView();
}

void KADBusOperator::showAIBall(bool visible)
{
    Q_EMIT sigShowAIBall(visible);
}

QString KADBusOperator::AllocateVoiceSessionId()
{
    ChatDatabase *db = ChatDatabase::getChatDatabase();
    int historyIdDb = db->selectMaxHistoryId();
    int historyIdSettings = ConfigManager::getInstance()->getConfigValue("history-id").toInt();
    // 分配独立 ID，并把 max 推进到 newId+1，避免智能体随后 getCurrentMaxHistoryId() 复用语音会话 ID。
    const int newId = qMax(historyIdDb, historyIdSettings) + 1;
    const int reservedMax = newId + 1;
    db->updateMaxHistoryId(reservedMax);
    ConfigManager::getInstance()->updateConfig("history-id", reservedMax);
    const QString sessionId = QString::number(newId);
    KyInfo() << "KADBusOperator::AllocateVoiceSessionId:" << sessionId;
    return sessionId;
}

bool KADBusOperator::SaveVoiceChatMessage(QString sessionID, int msgIndex, QString message, int chatType, qlonglong operateTime, QString sessionUuid)
{
    ChatDatabase *db = ChatDatabase::getChatDatabase();
    const QString existingUuid = db->selectSessionUuidBySessionID(sessionID);
    if (!existingUuid.isEmpty() && existingUuid != QString(kVoiceAssistantSessionUuid) && existingUuid != sessionUuid) {
        KyInfo() << "KADBusOperator::SaveVoiceChatMessage: reject polluting text/task session"
                 << sessionID << "existingUuid=" << existingUuid << "msgIndex=" << msgIndex;
        return false;
    }

    const QString normalizedMessage = normalizeVoiceAssistantMessageForChat(message);
    const QString uuidToUse = sessionUuid.isEmpty() ? QString(kVoiceAssistantSessionUuid) : sessionUuid;
    if (!db->insertOneWithModeTypeAndUuid(sessionID, msgIndex, normalizedMessage, chatType, 1,
                                          uuidToUse)) {
        KyInfo() << "KADBusOperator::SaveVoiceChatMessage: insert failed, sessionID=" << sessionID
                 << "msgIndex=" << msgIndex << "chatType=" << chatType;
        return false;
    }

    db->updateOperateTime(sessionID, 0, operateTime);

    bool ok = false;
    int sessionHistoryId = sessionID.toInt(&ok);
    if (ok) {
        int historyIdDb = db->selectMaxHistoryId();
        int historyIdSettings = ConfigManager::getInstance()->getConfigValue("history-id").toInt();
        if (sessionHistoryId >= historyIdDb) {
            db->updateMaxHistoryId(sessionHistoryId + 1);
        }
        if (sessionHistoryId + 1 > historyIdSettings) {
            ConfigManager::getInstance()->updateConfig("history-id", sessionHistoryId + 1);
        }
    }

    KyInfo() << "KADBusOperator::SaveVoiceChatMessage: sessionID=" << sessionID
             << "msgIndex=" << msgIndex << "chatType=" << chatType << "sessionUuid=" << uuidToUse;
    return true;
}

void KADBusOperator::NotifyHistoryChanged()
{
    KyInfo() << "KADBusOperator::NotifyHistoryChanged";
    Q_EMIT sigNotifyHistoryChanged();
}

bool KADBusOperator::NotifyVoiceTaskCompleted(QString sessionID, QString message)
{
    const QString body = message.trimmed().isEmpty()
            ? tr("The task is complete. Please enter the conversation to view details.")
            : message.trimmed();

    ChatDatabase *db = ChatDatabase::getChatDatabase();
    if (!sessionID.trimmed().isEmpty()) {
        db->setUnreadFlag(sessionID, 1);
    }

    NotificationNotify::getInstance()->sendNotification(
            tr("Agent"),
            body,
            sessionID,
            false);

    Q_EMIT sigNotifyHistoryChanged();
    return true;
}

bool KADBusOperator::ShowChatAndSendText(QString text)
{
    const QString trimmed = text.trimmed();
    if (trimmed.isEmpty()) {
        return false;
    }

    Q_EMIT sigShowChatAndSendText(trimmed);
    return true;
}
