#include "OSTreeSystem.h"
#include "utils/Device.h"
#include "utils/Utils.h"
#include "utils/OSTreeTask.h"
#include "utils/CheckTask.h"
#include "utils/errorCodes.h"
#include <QDebug>
#include <QUuid>
#include <QtDBus/QtDBus>
#include <DSysInfo>

DCORE_USE_NAMESPACE

// sudo dbus-monitor --system "interface=org.deepin.AtomicUpgrade1"

static const QString DBUS_ATOMIC_UPGRADE_SERVICE = "org.deepin.AtomicUpgrade1";
static const QString DBUS_ATOMIC_UPGRADE_PATH = "/org/deepin/AtomicUpgrade1";
static const QString DBUS_ATOMIC_UPGRADE_INTERFACE = DBUS_ATOMIC_UPGRADE_SERVICE;
static const QString DBUS_ATOMIC_UPGRADE_COMMIT = "Commit";
static const QString DBUS_ATOMIC_UPGRADE_LISTVERSION = "ListVersion";
static const QString DBUS_ATOMIC_UPGRADE_QUERYSUBJECT = "QuerySubject";
static const QString DBUS_ATOMIC_UPGRADE_DELETE = "Delete";
static const QString DBUS_ATOMIC_UPGRADE_GETCOMMITID = "GetCommitId";
static const QString DBUS_ATOMIC_UPGRADE_ROLLBACK = "Rollback";
static const char *DBUS_ATOMIC_UPGRADE_REPOUUID = "RepoUUID";
static const char *DBUS_ATOMIC_UPGRADE_RUNNING = "Running";
static const int OSTREE_COMMIT = 1;
static const int OSTREE_ROLLBACK = 2;
static const int OSTREE_DELETE = 3;
static const int OSTREE_END_COMMIT = 199;
static const int OSTREE_END_ROLLBACK = 299;
static const int OSTREE_END_DELETE = 399;

// 后台不用翻译，翻译统一在gui层处理
QString OSTreeSystem::getErrorMsgByErrorCode(int errCode)
{
    QMap<int, QString> allErrInfo = {
        {OStree::OSTREE_ERR_STORAGE_PATH_NOT_EXIST,
         "System backup error: the storage path does not exist."},

         {OStree::OSTREE_ERR_INSUFFICIENT_DISK_SPACE,
         "System backup error: the storage path does not exist."},

         {OStree::OSTREE_ERR_UPDATE_GRUB_FAILED,
         "System backup error: failed to update the grub."},

         {OStree::OSTREE_ERR_MOUNT_OR_UMOUNT_FAILED,
         "System backup error: failed to mount and unmount the disk."},

         {OStree::OSTREE_ERR_INIT_REPO_FAILED,
         "System backup error: initialization failed."},

         {OStree::OSTREE_ERR_SUBMIT_FAILED,
         "System backup error: failed to submit the backup."},

         {OStree::OSTREE_ERR_CHECKOUT_FAILED,
         "System backup error: failed to check out the version."},

         {OStree::OSTREE_ERR_CANNOT_DELETE,
         "File deletion error: the backup file cannot be deleted."}
    };

    if (allErrInfo.contains(errCode)) {
        return allErrInfo.value(errCode);
    }

    return "";
}

// 参考控制中心的，修改部分实现，原子更新 commint 信息里不能带中文
QString OSTreeSystem::getOSVersion()
{
    QString uosSysName = DSysInfo::uosSystemName(QLocale::English);
    DSysInfo::UosEdition edition = DSysInfo::uosEditionType();
    QString backupFlag = "UOS";
    QString version;
    if (DSysInfo::uosType() == DSysInfo::UosServer || DSysInfo::uosEditionType() == DSysInfo::UosEuler) {
        version = QString("%1-%2").arg(DSysInfo::majorVersion())
                .arg(DSysInfo::minorVersion());
    } else if (DSysInfo::isDeepin()) {
        if (DSysInfo::UosEdition::UosCommunity == edition) {
            backupFlag = uosSysName;
        }
        version = QString("%1-V%2-%3").arg(backupFlag).arg(DSysInfo::majorVersion())
                .arg(DSysInfo::minorVersion());
        QString buildVer = DSysInfo::buildVersion();
        if (!buildVer.isEmpty()) {
            version = QString("%1-%2").arg(version).arg(buildVer);
        }
    } else {
        version = QString("%1-%2").arg(DSysInfo::productVersion())
                .arg(DSysInfo::productTypeString());
    }

    return version;
}

OSTreeSystem::OSTreeSystem() : SystemRecovery()
{
    if (m_atomicUpgradeInterface == nullptr) {
        m_atomicUpgradeInterface = new QDBusInterface(DBUS_ATOMIC_UPGRADE_SERVICE,
                                                      DBUS_ATOMIC_UPGRADE_PATH,
                                                      DBUS_ATOMIC_UPGRADE_INTERFACE,
                                                      QDBusConnection::systemBus(),
                                                      this);
        connect(m_atomicUpgradeInterface, SIGNAL(StateChanged(int,int,const QString &, const QString &)),
                this,  SLOT(onStateChanged(int,int,const QString &, const QString &)));
    }

    if (m_pOSTreeTask == nullptr) {
        m_pOSTreeTask = new OSTreeTask();
        m_pOSTreeTask->init();
    }

    this->initConnection();
}

OSTreeSystem::~OSTreeSystem()
{
    if (m_atomicUpgradeInterface != nullptr) {
        delete m_atomicUpgradeInterface;
        m_atomicUpgradeInterface = nullptr;
    }

    if (m_pOSTreeTask != nullptr) {
        delete m_pOSTreeTask;
        m_pOSTreeTask = nullptr;
    }
}

void OSTreeSystem::initConnection()
{
    connect(m_pOSTreeTask, &OSTreeTask::progressChanged, [=](const QJsonObject &jsonObject) {
        QJsonObject progress = jsonObject;
        progress.insert("operateType", static_cast<int>(OperateType::SystemBackup));
        progress.insert("operateID", m_curOperateID);
        Q_EMIT progressChanged(Utils::JsonToQString(progress));
    });

    connect(m_pOSTreeTask, &OSTreeTask::error, [=](const QJsonObject &jsonObject) {
        QJsonObject errJson = jsonObject;
        errJson.insert("operateType", static_cast<int>(OperateType::SystemBackup));
        errJson.insert("operateID", m_curOperateID);
        Q_EMIT error(Utils::JsonToQString(errJson));
    });
}

void OSTreeSystem::onStateChanged(int operate, int state, const QString &version, const QString &msg)
{
    qInfo()<<Q_FUNC_INFO<<", operate = "<<operate<<", state = "<<state<<", msg = "<<msg<<", version = "<<version;
    int opType = operate / 100;

    QJsonObject jsonObject;
    jsonObject.insert("OStreeOperate", opType);
    jsonObject.insert("OStreeState", state);
    jsonObject.insert("operateId", m_backupInfo.operateID);
    jsonObject.insert("submissionVersion", version);
    jsonObject.insert("operateID", m_curOperateID);
    OperateType operateType;
    if (OSTREE_COMMIT == opType) {
        jsonObject.insert("operateType", static_cast<int>(OperateType::SystemBackup));
        operateType = OperateType::SystemBackup;
    } else if (OSTREE_ROLLBACK == opType){
        jsonObject.insert("operateType", static_cast<int>(OperateType::SystemRestore));
        operateType = OperateType::SystemRestore;
    } else if (OSTREE_DELETE == opType){
        jsonObject.insert("operateType", static_cast<int>(OperateType::removeBackup));
        operateType = OperateType::removeBackup;
    }

    if (0 == state) {
        if (!(OSTREE_END_COMMIT == operate || OSTREE_END_ROLLBACK == operate || OSTREE_END_DELETE == operate)) {
            return;
        }
        if (m_pOSTreeTask != nullptr) {
            m_pOSTreeTask->stopTimer();
        }

        ResultInfo retInfo(0, "success", "");
        this->reportEventLog(retInfo, operateType, RecoveryType::OSTree);

        m_backupInfo.status = OperateStatus::Success;
        jsonObject.insert("progress", 100);
        jsonObject.insert("remainSecond", 0);
        QString jsonString = Utils::JsonToQString(jsonObject);
        Q_EMIT progressChanged(jsonString);
        Q_EMIT success(jsonString);
    } else {
        if (state > 0) {
            return;
        }

        if (m_pOSTreeTask != nullptr) {
            m_pOSTreeTask->stopTimer();
        }

        QString errMsg = getErrorMsgByErrorCode(state);
        ResultInfo retInfo(state, "failed", errMsg);
        this->reportEventLog(retInfo, operateType, RecoveryType::OSTree);

        m_backupInfo.status = OperateStatus::Failed;
        jsonObject.insert("progress", 0);
        jsonObject.insert("remainSecond", 0);
        jsonObject.insert("errMsg", errMsg);
        Q_EMIT error(Utils::JsonToQString(jsonObject));
    }
}

void OSTreeSystem::fillBackupInfo(const SystemBackupRequest &request)
{
    static QString systemVersion = OSTreeSystem::getOSVersion();
    m_backupInfo.username = request.userName;
    m_backupInfo.operateID = QUuid::createUuid().toString();
    m_backupInfo.startTime = QDateTime::currentSecsSinceEpoch();
    m_backupInfo.remark = request.remark;
    m_backupInfo.operateType = static_cast<int> (OperateType::SystemBackup);
    m_backupInfo.recoveryType = static_cast<int>(RecoveryType::OSTree);
    m_backupInfo.rootUUID = request.rootUUID;
    m_backupInfo.backupDevUUID = request.destUUID;
    m_backupInfo.size = 0;
    m_backupInfo.submissionTime = QDateTime::currentSecsSinceEpoch();
    m_backupInfo.systemVersion = systemVersion;
    m_backupInfo.submissionType = CommitType::UserCommit;
}

// 构造原子更新commit subject 信息
QString OSTreeSystem::buildCommitSubject(const BackupInfo &backupInfo)
{
    QJsonObject jsonObj;
    jsonObj.insert("SubmissionTime", QString("%1").arg(backupInfo.submissionTime));
    jsonObj.insert("SystemVersion", backupInfo.systemVersion);
    jsonObj.insert("SubmissionType", backupInfo.submissionType);
    jsonObj.insert("UUID", backupInfo.operateID);
    jsonObj.insert("Note", backupInfo.remark);

    return Utils::JsonToQString(jsonObj);
}

QString OSTreeSystem::getCurRepoUuid()
{
    QString repoUuid;
    if (nullptr == m_atomicUpgradeInterface) {
        qInfo()<<"getCurRepoUuid nullptr";
        return repoUuid;
    }

    QVariant repoUuidProperty = m_atomicUpgradeInterface->property(DBUS_ATOMIC_UPGRADE_REPOUUID);
    if (repoUuidProperty.type() != QVariant::Type::String) {
        return repoUuid;
    }
    repoUuid = repoUuidProperty.toString();
    qInfo()<<"repoUuid = "<<repoUuid;

    return repoUuid;
}

bool OSTreeSystem::supported(FSTabInfoList &fsTabInfoList)
{
    static bool existOStree = Utils::isOStree();
    if (!existOStree) {
        return false;
    }

    // 原子更新存储位置可能发生改变，调用新接口去获取当前的存储位置
    m_defaultBackupDeviceUUID = this->getCurRepoUuid();
    if (m_defaultBackupDeviceUUID.isEmpty()) {
        return false;
    }

    bool support = false;
    for (auto &info : fsTabInfoList) {
        if (info->isComment || info->isEmptyLine) {
            continue;
        }

        if (info->mountPoint == "/" && Device::isFsTypeSupported(info->type)) {
            support = true;
            break;
        }
    }

    return support;
}

QString OSTreeSystem::getDefaultBackupDeviceUUID(const QString &rootPath)
{
    return m_defaultBackupDeviceUUID;
}

ErrorCode OSTreeSystem::systemBackup(const SystemBackupRequest &request)
{
    if (request.rootUUID.isEmpty()) {
        qCritical() << Q_FUNC_INFO << "error: rootUUID isEmpty";
        return ErrorCode::RootUuidIsEmpty;
    }

    if (request.destUUID.isEmpty()) {
        qCritical() << Q_FUNC_INFO << "error: destUUID isEmpty";
        return ErrorCode::DestUuidIsEmpty;
    }

    this->fillBackupInfo(request);
    QString subject = this->buildCommitSubject(m_backupInfo);

    if (m_atomicUpgradeInterface == nullptr) {
        qCritical() << Q_FUNC_INFO << "error: m_atomicUpgradeInterface is nullptr";
        return ErrorCode::NullPointer;
    }

    m_curOperateID = request.operateID;

    m_pOSTreeTask->startTimer();
    m_pOSTreeTask->start();

    QDBusMessage commitReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_COMMIT, subject);
    if (commitReply.type() != QDBusMessage::ReplyMessage) {
        m_pOSTreeTask->stopTimer();
        QString errMsg = commitReply.errorMessage();
        QString errName = commitReply.errorName();
        qCritical() << Q_FUNC_INFO << "error: call Commit failed, errMsg = "<<errMsg<<", errName = "<<errName;
        return ErrorCode::DbusError;
    }

    m_backupInfo.status = OperateStatus::Running;
    QJsonObject progress;
    progress.insert("progress", 1);
    progress.insert("remainSecond", 0);
    progress.insert("operateType", static_cast<int>(OperateType::SystemBackup));
    Q_EMIT progressChanged(Utils::JsonToQString(progress));

    return ErrorCode::OK;
}

ErrorCode OSTreeSystem::systemRestore(const SystemRestoreRequest &request)
{
    if (m_atomicUpgradeInterface == nullptr) {
        qCritical() << Q_FUNC_INFO << "error: systemRestore atomic upgrade interface is nullptr";
        return ErrorCode::NullPointer;
    }

    QString subVersion = request.backupInfo.submissionVersion;
    if (subVersion.isEmpty()) {
        qCritical() << Q_FUNC_INFO << "error: systemRestore submissionVersion is empty";
        return ErrorCode::ParamError;
    }

    m_curOperateID = request.operateID;

    QDBusMessage rollbackReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_ROLLBACK, subVersion);
    if (rollbackReply.type() != QDBusMessage::ReplyMessage) {
        QString errMsg = rollbackReply.errorMessage();
        qCritical() << Q_FUNC_INFO << "error: Rollback failed, subVersion = " << subVersion << ", errMsg = " << errMsg;
        return ErrorCode::DbusError;
    }

    return ErrorCode::OK;
}

ErrorCode OSTreeSystem::createUImg(const QString &backupDir)
{
    return UnKnow;
}

BackupInfoList OSTreeSystem::listSystemBackup(QStringList &destUUIDs)
{
    Q_UNUSED(destUUIDs);
    BackupInfoList backupInfoList;
    if (m_atomicUpgradeInterface == nullptr) {
        qCritical() << Q_FUNC_INFO << "error: m_atomicUpgradeInterface is nullptr";
        return backupInfoList;
    }

    QString errMsg;
    QDBusReply<QStringList> listVersionReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_LISTVERSION);
    if (!listVersionReply.isValid()) {
        errMsg = listVersionReply.error().message();
        qCritical() << Q_FUNC_INFO << "error: listVersionReply invalid, errMsg = "<<errMsg;
        return backupInfoList;
    }

    QStringList verList = listVersionReply.value();
    QDBusReply<QStringList> subjectsReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_QUERYSUBJECT, verList);
    if (!subjectsReply.isValid()) {
        errMsg = subjectsReply.error().message();
        qCritical() << Q_FUNC_INFO << "error: subjectsReply invalid, errMsg = "<<errMsg;
        return backupInfoList;
    }

    QStringList subjectList = subjectsReply.value();
    if (subjectList.contains("SubmissionVersion")) {
        this->subject2BackupInfoList(subjectList, backupInfoList);
    } else {
        for (auto version : verList) {
            QStringList curVerList(version);
            QDBusReply<QStringList> subjectReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_QUERYSUBJECT, curVerList);
            if (!subjectReply.isValid()) {
                errMsg = subjectReply.error().message();
                qCritical() << Q_FUNC_INFO << "subjectReply invalid, version = "<<version<<", errMsg = "<<errMsg;
                continue;
            }

            QJsonObject subjectObj = Utils::QStringToJson(subjectReply.value().first());
            BackupInfo backupInfo;
            backupInfo.submissionVersion = version;
            // OSTree 通过version来唯一标记，备份文件管理，非备份还原commit的是没有operateID，但是version是都存在，而且要求唯一
            backupInfo.operateID = version;
            backupInfo.operateType = static_cast<int> (OperateType::SystemBackup);
            this->subject2BackupInfo(subjectObj, backupInfo);
            backupInfoList.push_back(backupInfo);
        }
    }

    return backupInfoList;
}

void OSTreeSystem::subject2BackupInfoList(const QStringList &subjectList, BackupInfoList &backupInfoList)
{
    for (auto subject : subjectList) {
        QJsonObject jsonObj = Utils::QStringToJson(subject);
        QString submissionVersion;
        if (jsonObj.contains("SubmissionVersion")) {
            submissionVersion = jsonObj.value("SubmissionVersion").toString();
        }

        if (submissionVersion.isEmpty()) {
            qWarning()<< Q_FUNC_INFO << ", no submissionVersion in subject = "<<subject;
            continue;
        }

        BackupInfo backupInfo;
        backupInfo.submissionVersion = submissionVersion;
        backupInfo.operateID = submissionVersion;
        backupInfo.operateType = static_cast<int> (OperateType::SystemBackup);
        this->subject2BackupInfo(jsonObj, backupInfo);
        backupInfoList.push_back(backupInfo);
    }
}

void OSTreeSystem::subject2BackupInfo(const QJsonObject &jsonObj, BackupInfo &backupInfo)
{
    backupInfo.recoveryType = static_cast<int>(RecoveryType::OSTree);
    backupInfo.status = OperateStatus::Success;
    backupInfo.username = tr("Administrator");
    backupInfo.size = 0;
    backupInfo.backupDevUUID = this->getDefaultBackupDeviceUUID("");
    backupInfo.backupDeviceRemovable = false;
    if (jsonObj.contains("Note")) {
        backupInfo.remark = jsonObj.value("Note").toString();
    }

    // 非备份还原commit的是不存在UUID的，OSTree环境统一用version来填充operateID字段
    // if (jsonObj.contains("UUID")) {
    //     backupInfo.operateID = jsonObj.value("UUID").toString();
    // }

    if (jsonObj.contains("SubmissionTime")) {
        backupInfo.submissionTime = jsonObj.value("SubmissionTime").toString().toULongLong();
        backupInfo.startTime = backupInfo.submissionTime;
    }

    if (jsonObj.contains("SystemVersion")) {
        backupInfo.systemVersion = jsonObj.value("SystemVersion").toString();
    }

    if (jsonObj.contains("SubmissionType")) {
        backupInfo.submissionType = static_cast<CommitType>(jsonObj.value("SubmissionType").toInt(-1));
        if (backupInfo.submissionType == CommitType::SystemCommit) {
            backupInfo.operateType = static_cast<int> (OperateType::AutoBackup);
        }
    }
}

ErrorCode OSTreeSystem::removeSystemBackup(const RemoveUserDataBackupRequest &request)
{
    if (m_atomicUpgradeInterface == nullptr) {
        qCritical() << Q_FUNC_INFO << "error: m_atomicUpgradeInterface is nullptr";
        return NullPointer;
    }

    m_curOperateID = request.operateID;
    QString subVersion = request.backupInfo.submissionVersion;
    QDBusMessage deleteReply = m_atomicUpgradeInterface->call(DBUS_ATOMIC_UPGRADE_DELETE, subVersion);
    if (deleteReply.type() != QDBusMessage::ReplyMessage) {
        QString errMsg = deleteReply.errorMessage();
        qCritical() << Q_FUNC_INFO << "error: Delete Dbus failed, subVersion = "<<subVersion<<", errMsg = "<<errMsg;
        return ErrorCode::DbusError;
    }

    m_backupInfo = request.backupInfo;
    QJsonObject progress;
    progress.insert("progress", 0);
    progress.insert("operateType", static_cast<int>(OperateType::removeBackup));
    Q_EMIT progressChanged(Utils::JsonToQString(progress));

    return OK;
}

ErrorCode OSTreeSystem::checkFullSystemBackupSpace(SystemBackupRequest &request)
{
    // TODO
    return OK;
}

ErrorCode OSTreeSystem::checkIncSystemBackupSpace(const SystemBackupRequest &request)
{
    if (nullptr == m_checkTask) {
        m_checkTask = new CheckTask(OperateType::CheckIncSystemBackupSpace, RecoveryType::OSTree);

        connect(m_checkTask, &CheckTask::spaceCheckFinished, [=](const QJsonObject &jsonObject) {
            QJsonObject spaceJson = jsonObject;
            spaceJson.insert("operateType", static_cast<int>(OperateType::CheckIncSystemBackupSpace));
            QString spaceInfo = Utils::JsonToQString(spaceJson);
            Q_EMIT spaceCheckFinished(spaceInfo);
        });

        connect(m_checkTask, &CheckTask::error, [=](const QJsonObject &jsonObject) {
            QJsonObject errJson = jsonObject;
            errJson.insert("operateType", static_cast<int>(OperateType::CheckIncSystemBackupSpace));
            Q_EMIT error(Utils::JsonToQString(errJson));
        });

        connect(m_checkTask, &QThread::finished, [=] {
            m_checkTask->deleteLater();
            m_checkTask = nullptr;
        });
    }

    if (nullptr != m_checkTask) {
        m_checkTask->setDestUUID(request.destUUID);
        m_checkTask->start();
    }
    return OK;
}

bool OSTreeSystem::isRunning()
{
    bool isRun = false;
    if (m_atomicUpgradeInterface == nullptr) {
        qWarning() << "isRunning: m_atomicUpgradeInterface is nullptr";
        return isRun;
    }

    QVariant runningProperty = m_atomicUpgradeInterface->property(DBUS_ATOMIC_UPGRADE_RUNNING);
    if (runningProperty.type() != QVariant::Type::Bool) {
        return isRun;
    }
    isRun = runningProperty.toBool();
    return isRun;
}
