//
// Created by 张雪明 <zhangxueming@uniontech.com> on 2024/9/23.
//
#include "ImmutableSystem.h"
#include <QSettings>
#include <QDateTime>
#include <QDebug>
#include <QFile>

#include "utils/Utils.h"
#include "utils/Process.h"
#include "utils/ImmutableTask.h"
#include "utils/CheckTask.h"

static const QString immutableCtlCmd = "deepin-immutable-ctl";

ImmutableSystem::ImmutableSystem()
{
    QSettings versionFile("/etc/os-version", QSettings::IniFormat);
    versionFile.beginGroup("Version");
    m_osMajorVer = versionFile.value("MajorVersion").toInt();
    m_osMinorVer = versionFile.value("MinorVersion").toInt();
    versionFile.endGroup();
}

ImmutableSystem::~ImmutableSystem()
{

}

bool ImmutableSystem::supported(FSTabInfoList &fsTabInfoList)
{
    m_fsTabInfoList = fsTabInfoList;
    if (Utils::isImmutableSystem()) {
        return true;
    }

    if (m_osMajorVer < OS_VERSION_MAJOR_V25) {
        return false;
    }

    return false;
}

QString ImmutableSystem::getDefaultBackupDeviceUUID(const QString &rootPath)
{
    if (m_defaultBackupDeviceUUID.isEmpty()) {
        FSTabInfoPtr fstabInfo = FSTab::findFSTabByMountPoint(m_fsTabInfoList,"/persistent");
        if (!fstabInfo.isNull()) {
            m_defaultBackupDeviceUUID = FSTab::getDeviceUUID(fstabInfo);
        } else {
            qWarning()<<"persistent partition not found, rootPath: "<<rootPath;
            m_defaultBackupDeviceUUID = rootPath;
        }
    }
    return m_defaultBackupDeviceUUID;
}

ErrorCode ImmutableSystem::systemBackup(const SystemBackupRequest &request)
{
    QString uiOperateUserName = request.userName;
    QString snapshotName = request.snapshotName;
    QString snapshotDescription = request.remark;

    m_pImmutableTask = new ImmutableTask(OperateType::ImmutableSystemBackup);
    m_pImmutableTask->setSnapshotName(snapshotName);
    m_pImmutableTask->setSnapshotDesc(snapshotDescription);
    m_pImmutableTask->setUiOperateUserName(uiOperateUserName);
    connect(m_pImmutableTask, &ImmutableTask::progressChanged, [=](const QJsonObject &jsonObject) {
        QJsonObject progress = jsonObject;
        progress.insert("operateType", static_cast<int>(OperateType::ImmutableSystemBackup));
        progress.insert("operateID", request.operateID);
        Q_EMIT progressChanged(Utils::JsonToQString(progress));
    });

    connect(m_pImmutableTask, &ImmutableTask::success, [=](const QJsonObject &jsonObject) {
        ResultInfo retInfo(0, "success", "");
        this->reportEventLog(retInfo, OperateType::ImmutableSystemBackup, RecoveryType::ImmutableMode);
        QJsonObject jsonObjectOut = jsonObject;
        jsonObjectOut.insert("operateType", static_cast<int>(OperateType::ImmutableSystemBackup));
        jsonObjectOut.insert("errCode", 0);
        jsonObjectOut.insert("operateID", request.operateID);
        
        //保存备份结果的id和用户名关系到配置文件中
        QJsonDocument backupUserInfoDdoc;
        if (QFile::exists(BACKUP_USER_INFO)) {
            if (!Utils::readJsonFile(BACKUP_USER_INFO, backupUserInfoDdoc)){
                backupUserInfoDdoc = QJsonDocument::fromJson("{}");
            }
        } else {
            backupUserInfoDdoc = QJsonDocument::fromJson("{}");
        }

        QJsonObject backupUserInfoObj = backupUserInfoDdoc.object();
        QString backupID = jsonObject.value("data").toObject().value("id").toString();
        QString userName = jsonObject.value("uiOperateUserName").toString();
        QJsonObject userInfoObj;
        userInfoObj.insert("username", userName);
        backupUserInfoObj.insert(backupID, userInfoObj);
        backupUserInfoDdoc.setObject(backupUserInfoObj);
        Utils::writeJsonFile(BACKUP_USER_INFO, backupUserInfoDdoc);

        Q_EMIT success(Utils::JsonToQString(jsonObjectOut));
    });

    connect(m_pImmutableTask, &ImmutableTask::error, [=](const QJsonObject &jsonObject) {
        QJsonObject errJson = jsonObject;
        errJson.insert("operateType", static_cast<int>(OperateType::ImmutableSystemBackup));
        errJson.insert("operateID", request.operateID);

        int errCode = jsonObject.value("errCode").toInt(-1);
        QString errMsg = jsonObject.value("errMsg").toString();
        ResultInfo retInfo(errCode, "failed", errMsg);
        this->reportEventLog(retInfo, OperateType::ImmutableSystemBackup, RecoveryType::ImmutableMode);
        Q_EMIT error(Utils::JsonToQString(errJson));
    });

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

    m_pImmutableTask->buildArgumentsForBackup();
    m_pImmutableTask->init();
    m_pImmutableTask->startTimer();
    m_pImmutableTask->start();

    return ErrorCode::OK;
}

ErrorCode ImmutableSystem::systemRestore(const SystemRestoreRequest &request)
{
    QString snapshotID = request.backupInfo.operateID;
    qInfo()<<"systemRestore, snapshotID = "<<snapshotID;

    m_pImmutableTask = new ImmutableTask(OperateType::ImmutableSystemRestore);
    m_pImmutableTask->setSnapshotID(snapshotID);
    connect(m_pImmutableTask, &ImmutableTask::progressChanged, [=](const QJsonObject &jsonObject) {
        QJsonObject progress = jsonObject;
        progress.insert("operateType", static_cast<int>(OperateType::ImmutableSystemRestore));
        progress.insert("operateID", request.operateID);
        QString str = Utils::JsonToQString(progress);
        Q_EMIT progressChanged(str);
    });

    connect(m_pImmutableTask, &ImmutableTask::success, [=] {
        ResultInfo retInfo(0, "success", "");
        this->reportEventLog(retInfo, OperateType::ImmutableSystemRestore, RecoveryType::ImmutableMode);
        QJsonObject jsonObject;
        jsonObject.insert("operateType", static_cast<int>(OperateType::ImmutableSystemRestore));
        jsonObject.insert("errCode", 0);
        jsonObject.insert("operateID", request.operateID);
        jsonObject.insert("progress", 100);
        jsonObject.insert("remainSecond", 0);
        Q_EMIT success(Utils::JsonToQString(jsonObject));
    });

    connect(m_pImmutableTask, &ImmutableTask::error, [=](const QJsonObject &jsonObject) {
        QJsonObject errJson = jsonObject;
        errJson.insert("operateType", static_cast<int>(OperateType::ImmutableSystemRestore));
        errJson.insert("operateID", request.operateID);

        int errCode = jsonObject.value("errCode").toInt(-1);
        QString errMsg = jsonObject.value("errMsg").toString();
        ResultInfo retInfo(errCode, "failed", errMsg);
        this->reportEventLog(retInfo, OperateType::ImmutableSystemRestore, RecoveryType::ImmutableMode);
        Q_EMIT error(Utils::JsonToQString(errJson));
    });

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

    m_pImmutableTask->buildArgumentsForBackup();
    m_pImmutableTask->init();
    m_pImmutableTask->startTimer();
    m_pImmutableTask->start();

    return ErrorCode::OK;
}

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

bool ImmutableSystem::getSnapshotByID(const QString &id, SnapshotItem &item)
{
    QStringList args;
    args <<"snapshot"<<"show"<<id;
    QString out;
    QString err;
    if (!Process::spawnCmd(immutableCtlCmd, args, out, err)) {
        qCritical()<<"snapshot show failed, id: "<<id<<", err: "<<err;
        return false;
    }
    out = out.trimmed();
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
    if (outList.size() != 4) {
        return false;
    }

    for (auto &line : outList) {
        line = line.trimmed();
        int index = line.indexOf(":");
        QString val = line.right(line.length() - index - 1).trimmed();
        if (line.startsWith("ID")) {
            item.id = val;
        } else if (line.startsWith("Name")) {
            item.name = val;
        } else if (line.startsWith("Time")) {
            item.time = val;
        } else if (line.startsWith("Desc")) {
            item.desc = val;
        }
    }

    return true;
}

BackupInfoList ImmutableSystem::listSystemBackup(QStringList &destUUIDs)
{
    BackupInfoList backList;
    QStringList args;
    args <<"snapshot"<<"list"<<"--json";
    QString out;
    QString err;
    if (!Process::spawnCmd(immutableCtlCmd, args, out, err)) {
        qCritical()<<"snapshot list failed, err: "<<err;
        return backList;
    }

    out = out.trimmed();
    qWarning()<<"out: "<<out.toLocal8Bit().data();
    QJsonArray snapshotListArray;
    QJsonObject snapshotListObj = Utils::QStringToJson(out);
    if (snapshotListObj.contains("snapshot_list")) {
        snapshotListArray = snapshotListObj.value("snapshot_list").toArray();
    }

    int version = -1;
    if (snapshotListObj.contains("data")) {
        QJsonObject dataObj = snapshotListObj.value("data").toObject();
        if (dataObj.contains("snapshot_list")) {
            snapshotListArray = dataObj.value("snapshot_list").toArray();
        }

        if (dataObj.contains("version")) {
            version = dataObj.value("version").toInt(-1);
        }
    }

    int arraySize = snapshotListArray.size();
    for (int i = 0; i < arraySize; ++i) {
        QJsonObject snapshotObj = snapshotListArray.at(i).toObject();
        SnapshotItem item;
        item.id = snapshotObj.value("id").toString();
        item.name = snapshotObj.value("name").toString();
        item.desc = snapshotObj.value("desc").toString();

        BackupInfo backupInfo;
        backupInfo.operateID = item.id;
        if (version < 2) {
            item.time = snapshotObj.value("time").toString();
            QDateTime createTime = QDateTime::fromString(item.time, "yyyy-MM-dd hh:mm:ss");
            backupInfo.startTime = createTime.toSecsSinceEpoch();
        } else {
            backupInfo.startTime = snapshotObj.value("time").toInteger();
        }
        backupInfo.remark = item.desc;
        backupInfo.operateType = static_cast<int> (OperateType::ImmutableSystemBackup);
        backupInfo.recoveryType = static_cast<int> (RecoveryType::ImmutableMode);
        backupInfo.status = static_cast<int> (OperateStatus::Success);
        backupInfo.snapshotName = item.name;
        backupInfo.systemVersion = item.name;
        if (0 == i) {
            backupInfo.submissionType = CommitType::InstallerCommit;
        }
        backList.append(backupInfo);
    }

    return backList;
}

ErrorCode ImmutableSystem::removeSystemBackup(const RemoveUserDataBackupRequest &request)
{
    QString snapshotID = request.backupInfo.operateID;
    qInfo()<<"removeSystemBackup, snapshotID = "<<snapshotID;
    QStringList args;
    args <<"snapshot"<<"delete"<<snapshotID<<"--json";
    QJsonObject jsonObject;
    jsonObject.insert("operateId", request.backupInfo.operateID);
    jsonObject.insert("operateType", static_cast<int>(OperateType::removeBackup));
    jsonObject.insert("remainSecond", 0);
    jsonObject.insert("operateID", request.operateID);
    jsonObject.insert("backupInfoType", request.backupInfo.operateType);

    QString out;
    QString err;
    if (!Process::spawnCmd(immutableCtlCmd, args, out, err)) {
        qCritical()<<"snapshot delete failed, snapshotID: "<<snapshotID<<", err: "<<err<<",out: "<<out;
        if (err.isEmpty()) {
            err = out;
        }

        QString errCodeStr;
        QJsonObject outObj = Utils::QStringToJson(out);
        QJsonObject outErrObj = outObj.value("error").toObject();
        errCodeStr = outErrObj.value("code").toString();

        jsonObject.insert("progress", 0);
        jsonObject.insert("errMsg", err);
        jsonObject.insert("errCodeStr", errCodeStr);
        Q_EMIT error(Utils::JsonToQString(jsonObject));
        return ErrorCode::UnKnow;
    }

    jsonObject.insert("progress", 100);
    QString jsonString = Utils::JsonToQString(jsonObject);

    //保存备份结果的id和用户名关系到配置文件中
    QJsonDocument backupUserInfoDdoc;
    if (QFile::exists(BACKUP_USER_INFO)) {
        if (Utils::readJsonFile(BACKUP_USER_INFO, backupUserInfoDdoc)) {
            QJsonObject backupUserInfoObj = backupUserInfoDdoc.object();
            qInfo() << "removeSystemBackup snapshotID: " << snapshotID;
            backupUserInfoObj.remove(snapshotID);
            backupUserInfoDdoc.setObject(backupUserInfoObj);
            Utils::writeJsonFile(BACKUP_USER_INFO, backupUserInfoDdoc);
        }
    }

    Q_EMIT progressChanged(jsonString);
    Q_EMIT success(jsonString);
    return ErrorCode::OK;
}

ErrorCode ImmutableSystem::checkFullSystemBackupSpace(SystemBackupRequest &request)
{
    return ErrorCode::OK;
}

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

        connect(m_checkTask, &CheckTask::spaceCheckFinished, [=](const QJsonObject &jsonObject) {
            QJsonObject spaceJson = jsonObject;
            spaceJson.insert("operateID", request.operateID);
            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("operateID", request.operateID);
            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 ImmutableSystem::isRunning()
{
    return false;
}

void ImmutableSystem::setFstabInfoList(const FSTabInfoList &fsTabInfoList)
{
    m_fsTabInfoList = fsTabInfoList;
}
