#include "DimFileHandle.h"
#include "utils/Utils.h"
#include "utils/global.h"
#include "utils/Device.h"
#include "utils/CheckTask.h"
#include <QJsonObject>
#include <QDir>
#include <QDebug>
#include <QSettings>

const QString DimImgFilePath = "/dimFileRestore";

DimFileHandle::DimFileHandle() : DimFileRecovery()
{
    m_subTimer = new QTimer(this);
    connect(m_subTimer, &QTimer::timeout, this, &DimFileHandle::subTimeSlot);
}

DimFileHandle::~DimFileHandle()
{
    disconnect(m_subTimer, &QTimer::timeout, this, &DimFileHandle::subTimeSlot);
}

bool DimFileHandle::dimFileTrans(const QString &dimFilePath, const QString &imgFilePath)
{
    m_dimFiles.clear();
    m_imgFiles.clear();
    m_dimFilePath = dimFilePath;
    m_imgFilePath = imgFilePath;

    // 获取dim文件列表
    QDir srcDir(dimFilePath);
    QStringList fileNames = {"*.dim"};
    QFileInfoList dimFileInfos = srcDir.entryInfoList(fileNames, QDir::Files | QDir::Readable, QDir::Name);
    for (QFileInfo item : dimFileInfos) {
        m_dimFiles.append(item.absoluteFilePath());
    }

    if (m_dimFiles.isEmpty()) {
        return false;
    }

    QList<QJsonObject> dimFileObjInfos;
    for (QString dimFilePath : m_dimFiles) {
        QString err = "";
        QJsonObject dimFileObjInfo;
        if (!Utils::getDimFileJsonInfo(dimFilePath, dimFileObjInfo, err)) {
            return false;
        }
        dimFileObjInfos.append(dimFileObjInfo);
    }

    // 校验Dim文件与本机的uuid匹配情况，不匹配则返回false
    if (!checkDimFiles(dimFileObjInfos)) {
        m_dimFiles.clear();
        return false;
    }

    if (!getLoopDevice(m_loopDevice)) {
        return false;
    }

    // 计算总共需要的时间，按照2M/s的速度做时间预估
    qint64 dimFilesTotalSize = 0;
    for (QJsonObject item : dimFileObjInfos) {
        dimFilesTotalSize += item.value("totalReadableDataSize").toVariant().toLongLong();
    }
    m_totalTimeNeed = (dimFilesTotalSize * 1.0 / MiB) / 2;

    m_timeUsed = 0;
    m_fileIndex = 0;
    m_preProgress = 0;

    if (m_dimFileTransTask == nullptr) {
        m_dimFileTransTask = new DimFileTransTask;
        connect(m_dimFileTransTask, &DimFileTransTask::progressChanged, this,[=](const QJsonObject &jsonObject) {
            QJsonObject progress = jsonObject;
            progress.insert("operateType", static_cast<int> (OperateType::DimFileRestore));

            int tmpProgress = m_preProgress + progress["progress"].toInt() / m_dimFiles.size();
            progress["remainSecond"] = updateTimeRemain(tmpProgress);
            progress["progress"] = tmpProgress;
            Q_EMIT progressChanged(Utils::JsonToQString(progress));
        });

        connect(m_dimFileTransTask, &DimFileTransTask::success, this, [=](const QJsonObject &jsonObject) {
            m_fileIndex++;
            m_imgFiles.append(jsonObject.value("imgFilPath").toString());
            if (m_fileIndex < m_dimFiles.size()) {
                m_preProgress = m_preProgress + (100 / m_dimFiles.size());
                m_dimFileTransTask->doTrans(m_dimFiles.at(m_fileIndex), m_imgFilePath, m_loopDevice);
            } else {
                m_subTimer->stop();
                writeRestoreConf();
                QJsonObject progress = jsonObject;
                progress.insert("operateType", static_cast<int> (OperateType::DimFileRestore));
                progress["remainSecond"] = 0;
                progress["progress"] = 100;
                Q_EMIT progressChanged(Utils::JsonToQString(progress));
            }
        }, Qt::QueuedConnection);

        connect(m_dimFileTransTask, &DimFileTransTask::error, this, [=](const QJsonObject &jsonObject) {
            QJsonObject errJson = jsonObject;
            errJson.insert("operateType", static_cast<int> (OperateType::DimFileRestore));
            clearDimFileRestoreCfg(m_imgFilePath);
            Q_EMIT error(Utils::JsonToQString(errJson));
        });
    }

    m_dimFileTransTask->doTrans(m_dimFiles.at(m_fileIndex), m_imgFilePath, m_loopDevice);
    m_subTimer->start(1000);
    return true;
}

bool DimFileHandle::clearDimFileRestoreCfg(const QString &imgFilePath)
{
    // 删除writeRestoreConf的设置，并还原根路径下的配置文件
    QString recoveryConf = "/" + UOS_RECOVERY_INI;
    QString recoveryConfTmp = imgFilePath + "/" + UOS_RECOVERY_INI.right(UOS_RECOVERY_INI.length() - UOS_RECOVERY_INI.lastIndexOf("/") - 1);

    // 清除目录
    QString cmd = QString("cp -f %1 %2").arg(recoveryConfTmp).arg(recoveryConf);
    QString cmdLog = "";
    QString err = "";
    if (!Process::spawnCmd("cp", {"-f", recoveryConfTmp, recoveryConf}, cmdLog, err)) {
        return false;
    }

    cmd = QString("rm -rf %1").arg(imgFilePath);
    cmdLog = "";
    err = "";
    if (!Process::spawnCmd("rm", {"-rf", imgFilePath}, cmdLog, err)) {
        return false;
    }

    return true;
}

bool DimFileHandle::hasInitBackDimFile()
{
    // 获取dim文件列表
    QDir srcDir("/recovery/backup");
    QStringList fileNames = {"*.dim"};
    QFileInfoList dimFileInfos = srcDir.entryInfoList(fileNames, QDir::Files | QDir::Readable, QDir::Name);
    for (QFileInfo item : dimFileInfos) {
        m_dimFiles.append(item.absoluteFilePath());
    }

    if (m_dimFiles.isEmpty()) {
        return false;
    }
    return true;
}

ErrorCode DimFileHandle::checkDimFileSpace(const QString &dimFilesPath)
{
    if (nullptr == m_checkTask) {
        m_checkTask = new CheckTask(OperateType::CheckDimFileUseSpace, RecoveryType::Rsync);
        m_checkTask->setParent(this);
        connect(m_checkTask, &CheckTask::spaceCheckFinished, [=](const QJsonObject &jsonObject) {
            QJsonObject spaceJson = jsonObject;
            Q_EMIT spaceCheckFinished(Utils::JsonToQString(spaceJson));
        });
    }

    if (nullptr != m_checkTask) {
        m_checkTask->setDestPath(dimFilesPath);
        m_checkTask->start();
    }
    return OK;
}

bool DimFileHandle::getLoopDevice(QString &loopDevice)
{
    loopDevice = "";
    QString out;
    QString err;
    if (!Process::spawnCmd("losetup", {"-f"}, out, err)) {
        qCritical()<<"getLoopDevice spawnCmd failed to get losetup, err: "<<err;
        return false;
    }
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
    QStringList freeLoop = out.split("\n", Qt::SkipEmptyParts);
#else
    QStringList freeLoop = out.split("\n", QString::SkipEmptyParts);
#endif
    if (freeLoop.size() == 0) {
        QString cmd = "mknod /dev/loop1 b 7 1";
        QString cmdLog = "";
        err = "";
        QStringList args;
        args<<"/dev/loop1"<<"b"<<"7"<<"1";
        if (!Process::spawnCmd("mknod",args, cmdLog, err)) {
            qWarning() << "getLoopDevice error cmd : "<< cmd << " err : " << err;
            return false;
        }
        loopDevice = "/dev/loop1";
    } else {
        loopDevice = freeLoop.first();
    }

    return true;
}

bool DimFileHandle::checkDimFiles(const QList<QJsonObject> &dimFileObjInfos)
{
    // 校验Dim文件与本机的uuid匹配情况，不匹配则返回false
    DeviceInfoList devInfoList = Device::getDeviceByLsblk();

    for (QJsonObject fileItem : dimFileObjInfos) {
        QJsonArray dimFileInfoArray = fileItem.value("childrenPartList").toArray();
        if (dimFileInfoArray.size() > 0) {
            QJsonObject dimFileInfo = dimFileInfoArray.first().toObject();
            QString dimFileUuid = dimFileInfo.value("uuid").toString();
            // 如在在本地系统中不存在该uuid则判定dim文件在本机不可用
            if (std::find_if(devInfoList.begin(), devInfoList.end(), [=](DeviceInfoPtr devItem){ return devItem->uuid == dimFileUuid; }) == devInfoList.end()) {
                return false;
            }
        }

    }
    return true;
}

bool DimFileHandle::writeRestoreConf()
{
    QString err = "";
    // 获取所有分区信息
    QJsonArray allPartJsonArrayInfo;
    QStringList args;
    args<<"-lpOJ";
    if (!Utils::getLsblkJsonReturn(args, allPartJsonArrayInfo, err)) {
        return false;
    }
    QList<QJsonObject> allPartJsonObjInfo;
    for (int i = 0; i < allPartJsonArrayInfo.size(); i++) {
        allPartJsonObjInfo.append(allPartJsonArrayInfo.at(i).toObject());
    }

    QJsonObject imgPartInfo;
    QString restorePathOut = "";
    err = "";
    // 根据dimImg文件路径获取分区路径信息
    if (!Utils::getDestPartInfoByDir(m_imgFilePath, imgPartInfo, err)) {
        return false;
    }

    QString imgFilePathOut = m_imgFilePath;
    if (imgFilePathOut.contains(" ")) {
        imgFilePathOut.replace("\"", "");
    }

    QString pathStr = imgPartInfo.value("uuid").toString();
    QString infoType = imgPartInfo.value("type").toString();
    QString infoPkname = imgPartInfo.value("pkname").toString();
    QString infoMountPoint = imgPartInfo.value("mountpoint").toString();

    // 如果是dimImg文件是存放在usb设备中，则去掉路径前面携带的usb设备挂载点的路径
    if (!infoType.compare("disk")) {
        if (!imgPartInfo.value("tran").toString().compare("usb")) {
            imgFilePathOut.replace(infoMountPoint, "");
        }
    } else if (!infoType.compare("part")) {
        // 如果是分区则通过判断分区所在设备来确定设备的传输类型
        auto partInfoIter = std::find_if(allPartJsonObjInfo.begin(), allPartJsonObjInfo.end(), [=](QJsonObject partInfo) {
            return (!partInfo.value("name").toString().compare(infoPkname));
        });

        if (partInfoIter != allPartJsonObjInfo.end()) {
            if (!(*partInfoIter).value("tran").toString().compare("usb")) {
                imgFilePathOut.replace(infoMountPoint, "");
            } else if (imgFilePathOut.compare(m_dimFilePath + DimImgFilePath)) {
                // 通过比较dimImg文件的路径和dim文件的路径判断目录改变，修改写到配置文件中的img文件的路径信息
                imgFilePathOut = imgFilePathOut.right(imgFilePathOut.length() - imgFilePathOut.indexOf(DimImgFilePath));
            }
        }
    }

    restorePathOut = pathStr + ";" + imgFilePathOut;

    QStringList imgFileListOut;
    if (!getImgFileListOut(allPartJsonObjInfo, imgFilePathOut, imgFileListOut)) {
        return false;
    }

    QStringList tarFileListOut;
    getTarFileListOut(allPartJsonObjInfo, tarFileListOut);

    QString recoveryConf = "/" + UOS_RECOVERY_INI;
    QString recoveryConfTmp = m_imgFilePath + "/" + UOS_RECOVERY_INI.right(UOS_RECOVERY_INI.length() - UOS_RECOVERY_INI.lastIndexOf("/") - 1);

    QFile::remove(recoveryConfTmp);
    QFile::copy(recoveryConf, recoveryConfTmp);

    QSettings settings(recoveryConf, QSettings::IniFormat);
    settings.beginGroup(RESTORE_GROUP);
    settings.setValue(RESTORE_DO_RESTORE_KEY, true);
    settings.setValue(RESTORE_RECOVERY_TYPE_KEY, static_cast<int>(RecoveryType::RsyncV20));
    settings.setValue(RESTORE_BACKUP_DEV_UUID_KEY, "ok");
    settings.setValue(RESTORE_BACKUP_POINT_KEY, "ok");
    settings.setValue(V20RESTORE_PATH, restorePathOut);
    settings.setValue(V20RESTORE_LIST, imgFileListOut.join(";"));
    settings.setValue(V20RESTORE_TAR, tarFileListOut.join(";"));
    settings.endGroup();
    settings.sync();

    return true;
}

bool DimFileHandle::getImgFileListOut(const QList<QJsonObject> &allPartJsonObjInfo, const QString &realImgFilePath,
    QStringList &imgFileListOut)
{
    for (QString dimFileName : m_dimFiles) {
        QJsonObject dimFileJsonInfo;
        QString err = "";
        if (!Utils::getDimFileJsonInfo(dimFileName, dimFileJsonInfo, err)) {
            return false;
        }

        // 获取img文件的实际路径
        auto imgFileName = std::find_if(m_imgFiles.begin(), m_imgFiles.end(), [=](QString fileName) {
            return fileName.contains(dimFileName.right(dimFileName.length() - dimFileName.lastIndexOf("/") - 1));
        });

        if (imgFileName == m_imgFiles.end()) {
            return false;
        }

        QString imgFileNameStr = *imgFileName;
        if (imgFileNameStr.contains(" ")) {
            imgFileNameStr.replace("\"", "");
        }
        QString dimImgName = imgFileNameStr.right(imgFileNameStr.length() - imgFileNameStr.lastIndexOf("/") - 1);
        QString realImgFilePathOut = realImgFilePath + "/" + dimImgName;
        qInfo()<<"imgFileNameStr = "<<imgFileNameStr<<", dimImgName = "<<dimImgName<<
                ", realImgFilePath = "<<realImgFilePath;

        // 获取childrenPartList中分区路径和挂载点的对应关系
        QJsonArray childrenPartList = dimFileJsonInfo.value("childrenPartList").toArray();
        QList<QStringList> partMountInfoList;
        for (int i = 0; i < childrenPartList.size(); i++) {
            QJsonObject childrenPartObj = childrenPartList.at(i).toObject();
            QString partPath = childrenPartObj.value("name").toString();
            QString partUuid = childrenPartObj.value("uuid").toString();

            auto partInfoIter = std::find_if(allPartJsonObjInfo.begin(), allPartJsonObjInfo.end(), [=](QJsonObject partInfo) {
                return (!partInfo.value("name").toString().compare(partPath));
            });

            if (partInfoIter == allPartJsonObjInfo.end()) {
                return false;
            }

            // 过滤掉交换分区
            if ((*partInfoIter).value("fstype").toString().contains("swap")) {
                continue;
            }

            QString partMountPoint = (*partInfoIter).value("mountpoint").toString();
            if (partMountPoint.isEmpty()) {
                partMountPoint = "/tmpPoint" + partPath.right(partPath.length() - partPath.lastIndexOf("/") - 1);
            }

            // 保存分区路径和挂载点的对应关系
            partMountInfoList.append({QString("%1").arg((!partMountPoint.compare("/")) ? 0 : partMountPoint.count("/")),
                                      partUuid + ":" + partMountPoint});
        }

        // 按照路径浅度排序
        std::sort(partMountInfoList.begin(), partMountInfoList.end(), [=](QStringList strItemPre, QStringList strItemAfter){
            return (strItemPre.first().toInt() < strItemAfter.first().toInt());
        });

        QStringList partMountKeyValueList;
        for (QStringList mountInfoItem: partMountInfoList) {
            partMountKeyValueList.append(mountInfoItem.last());
        }

        // 保存镜像文件个分区挂载对应的关系
        imgFileListOut.append(realImgFilePathOut + "[" + partMountKeyValueList.join("["));
    }

    return true;
}

void DimFileHandle::getTarFileListOut(const QList<QJsonObject> &allPartJsonObjInfo, QStringList &tarFileListOut)
{
    tarFileListOut = QStringList();

    QString cmd = QString("cp  %1/*.tar %2").arg(m_dimFilePath).arg(m_imgFilePath);
    QString cmdLog = "";
    QString err = "";

    if (!Process::spawnCmd("cp",{"m_dimFilePath/*.tar", m_imgFilePath}, cmdLog, err)) {
        qWarning() << "getTarFileListOut error cmd : "<< cmd << " err : " << err;
        return;
    }

    // 获取dim文件列表
    QDir srcDir(m_imgFilePath);
    QStringList fileNames = {"*.tar"};
    QFileInfoList tarFileInfos = srcDir.entryInfoList(fileNames, QDir::Files | QDir::Readable, QDir::Name);
    for (QFileInfo item : tarFileInfos) {
        // 提取tar问价中的datainfo文件，解析目标uuid和目标目录

        cmd = QString("tar --xattrs --xattrs-include=* -xvf %1 -C %2 datainfo").arg(item.absoluteFilePath()).arg(m_imgFilePath);
        cmdLog = "";
        err = "";
        QStringList args;
        args<<"--xattrs"<<"--xattrs-include=*"<<"-xvf"<<item.absoluteFilePath()<<"-C"<<m_imgFilePath<<"datainfo";
        if (!Process::spawnCmd("tar",args, cmdLog, err)) {
            qWarning() << "getTarFileListOut error cmd : "<< cmd << " err : " << err;
            return;
        }

        QString dataInfoFilePath = m_imgFilePath + "/datainfo";
        QFile file(dataInfoFilePath);
        if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
            qWarning() << "getTarFileListOut open file failed ! : "<< dataInfoFilePath;
            return;
        }
        QString dataInfoStr = file.readAll();
        file.close();

        QFile::remove(dataInfoFilePath);

        int uuidPos = dataInfoStr.indexOf("UUID=");
        int uuidPosE = dataInfoStr.indexOf("\n", uuidPos);
        QString tarFileUuid = dataInfoStr.mid(uuidPos, uuidPosE - uuidPos);
        tarFileUuid.replace("UUID=", "");

        int dirPos = dataInfoStr.indexOf("Directory=");
        int dirPosE = dataInfoStr.indexOf("\n", dirPos);
        QString tarFileDir = dataInfoStr.mid(dirPos, dirPosE - dirPos);
        tarFileDir.replace("Directory=", "");

        auto partInfoIter = std::find_if(allPartJsonObjInfo.begin(), allPartJsonObjInfo.end(), [=](QJsonObject partInfo) {
            return (!partInfo.value("uuid").toString().compare(tarFileUuid));
        });

        if (partInfoIter == allPartJsonObjInfo.end()) {
            return;
        }

        QString tarFileListItem = (*partInfoIter).value("uuid").toString() + ":" + tarFileDir + "[" + item.fileName();
        tarFileListOut.append(tarFileListItem);
    }
}

int DimFileHandle::updateTimeRemain(int progress)
{
    // 平滑处理时间进度，把总时间和当前进度 10 等分，根据实际进度值所在区间，更新已用时间值
    int tmpIndex = progress / 10;
    int perTimeItem = m_totalTimeNeed / 10;
    if (m_timeIndex != tmpIndex) {
        m_timeIndex = tmpIndex;
        m_timeUsed = perTimeItem * m_timeIndex;
    }

    if (m_timeUsed >= (perTimeItem * (m_timeIndex + 1))) {
        m_timeUsed = perTimeItem * (m_timeIndex + 1);
    }

    int remainSecond = m_totalTimeNeed - m_timeUsed;
    if (remainSecond < 0) {
        return 0;
    }

    return remainSecond;
}

void DimFileHandle::subTimeSlot()
{
    ++m_timeUsed;
}

