//
// Created by uos on 2022/2/24.
//

#include "RsyncTask.h"
#include "utils/Utils.h"
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QException>
#include <QDir>
#include <QJsonObject>



RsyncTask::RsyncTask() : AsyncTask()
{
    m_cmd = "rsync";
    m_timer.setInterval(1000);
    m_progress = 0;
    m_opType = OperateType::Invalid;

    connect(&m_timer, &QTimer::timeout, [=] {
        QJsonObject jsonObject;
        if (m_autoIncProgress && m_progress < 80) {
            if (OperateType::GhostBackup == m_opType) {
                if (m_progress < 1) {
                    m_progress += 1;
                }else if (m_progress < 5) {
                    static int num = 0;
                    m_progress += (++num) / 6;
                    if (num >= 6) {
                        num = 0;
                    }
                } else if (m_progress < 15) {
                    static int count = 0;
                    m_progress += (++count) / 10;
                    if (count >= 10) {
                        count = 0;
                    }
                }
            } else {
                m_progress += 1;
            }
        } else if (m_progress >= 100) {
            if (m_timer.isActive()) {
                m_timer.stop();
            }
        }
        QTime currTime = QTime::currentTime();
        int elapsed = m_startTime.msecsTo(currTime);
        double speed = (m_progress * 1000.0) / elapsed;
        if (0 < speed) {
            m_remainSecond = (100 - m_progress) / speed;
        }

        if (m_progress >= m_rsyncProgress && (m_checkPath.startsWith("/media/") || OperateType::GhostBackup == m_opType)) {
//            qWarning()<<"timeout: m_progress = "<<m_progress<<", m_remainSecond"<<m_remainSecond<<", checkPath: "<<m_checkPath;
            QDir dir(m_checkPath);
            if (!dir.exists()) {
                m_errLogs = QString("path not exit, progress: %1").arg(m_progress);
                qCritical() << m_errLogs;
                this->kill();
                return ;
            }
        }

        reportProgress(m_progress, m_remainSecond);
    });
}

RsyncTask::RsyncTask(bool reportProgress) : AsyncTask(), m_reportProgress(reportProgress)
{
    // 不需要上报进度
    m_cmd = "rsync";
    m_progress = 0;
    m_opType = OperateType::Invalid;
}

RsyncTask::~RsyncTask()
{
    if (m_timer.isActive()) {
        m_timer.stop();
    }
}

void RsyncTask::readStandardOutput()
{
    if (!m_timer.isActive()) {
        m_timer.start();
    }
    if (m_startTime.isNull()) {
        m_startTime = QTime::currentTime();
    }
    m_errLogs.clear();

    while (m_process->canReadLine()) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        auto lines = QString(m_process->readLine()).split('\r', Qt::SkipEmptyParts);
#else
        auto lines = QString(m_process->readLine()).split('\r', QString::SkipEmptyParts);
#endif
        for (auto &line : lines) {
            if (line.contains("rsync:") && line.contains("failed") || line.contains("rsync error:")) {
                m_errLogs += line + QString("\n");
            }
#ifdef QT_DEBUG
            qInfo() << line;
#endif
            //list sample:{"175,850,891", "99%", "163.77GB/s", "0:00:00", "(xfr#1495,", "to-chk=4/2878)"}
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList list = line.trimmed().split(" ", Qt::SkipEmptyParts);
#else
            QStringList list = line.trimmed().split(" ", QString::SkipEmptyParts);
#endif
            if (list.size() == 6) {
                quint64 size = QString(list.at(0)).replace(",", "").trimmed().toULongLong();
                auto tmp = QString(list.at(0)).replace(",", "").trimmed();
                if (list.at(5).startsWith("to-chk") && size != 0) {
                    m_rsyncProgress = list[1].replace("%", " ").toInt(); // 获取rsync实际进度
                    if (m_rsyncProgress > 0) {
                        m_autoIncProgress = false; // rsync 有进度后停止定时器里的进度自动更新，取rsync实际进度
                        if (m_rsyncProgress > m_progress && m_rsyncProgress < 100) {
                            m_progress = m_rsyncProgress;
                            reportProgress(m_progress, m_remainSecond);
                        }
                    }

                } else if (list.at(1).endsWith("%") && size != 0 && list.at(5).startsWith("ir-chk")) {
                    m_rsyncProgress = list[1].replace("%", " ").toInt();
                    if (m_rsyncProgress > 0) {
                        m_autoIncProgress = false;
                        if (m_rsyncProgress > m_progress && m_rsyncProgress < 100) {
                            m_progress = m_rsyncProgress;
                            reportProgress(m_progress, m_remainSecond);
                        }
                    }
                }
            }

            if (line.contains("Number of files:")) {
                if (m_opType == OperateType::SystemBackup) {
                    // 系统备份增量更新结束后，rsync上报的进度一般不会很大，50是估计值，满足大多数场景
                    if (m_rsyncProgress < 50) {
                        // 系统备份增量更新场景，update-grub也需要个进度，激活定时器，模拟进度
                        m_autoIncProgress = true;
                    }
                } else {
                    m_progress = 99;
                    m_remainSecond = 0;
                    reportProgress(m_progress, m_remainSecond);
                }
            }
        }
    }
}

void RsyncTask::readAllStandardError()
{
    AsyncTask::readAllStandardError();
}

bool RsyncTask::buildArgumentsForBackup()
{
    m_args.clear();
    if (m_options.isEmpty() || m_src.isEmpty() || m_dest.isEmpty()) {
        return false;
    }
    m_args = m_options;
    if (m_dryRun) {
        m_args << "--dry-run";
    }

    QDir dir(m_dest);
    if (!dir.cdUp()) {
        qCritical() << "exclude.list directory error";
        return false;
    }
    QString filename = dir.absolutePath() + "/exclude.list";
    QFile file(filename);
    try {
        file.open(QIODevice::ReadWrite | QIODevice::Truncate);
        for (auto &e : m_excludes) {
            file.write(e.toUtf8());
            file.write("\n");
        }
        file.close();
    } catch (QException &e) {
        qCritical() << "create exclude.list failed";
        file.close();
        return false;
    }

    QString fileFromPath = dir.absolutePath() + "/fileFrom.list";
    if (!m_fileFrom.isEmpty()) {
        QFile fileFrom(fileFromPath);
        try {
            fileFrom.open(QIODevice::ReadWrite | QIODevice::Truncate);
            for (auto &e : m_fileFrom) {
                fileFrom.write(e.toUtf8());
                fileFrom.write("\n");
            }
            fileFrom.close();
        } catch (QException &e) {
            qCritical() << "create fileFrom.list failed";
            fileFrom.close();
            return false;
        }
    }

    m_args << QString("--log-file=%1").arg(dir.absolutePath() + "/rsync.log");

    if (!m_fileFrom.isEmpty()) {
        m_args << QString("--files-from") << fileFromPath;
    }

    m_args << QString("--exclude-from=%1").arg(filename);
    if (!m_linkDest.isEmpty()) {
        m_args << QString("--link-dest=%1").arg(m_linkDest);
    }
    m_args << m_src;
    m_args << m_dest;
    return true;
}

bool RsyncTask::buildArgumentsForRestore(const QString &fromDir)
{
    m_args.clear();
    if (m_options.isEmpty() || m_src.isEmpty() || m_dest.isEmpty()) {
        return false;
    }
    m_args = m_options;
    if (m_dryRun) {
        m_args << "--dry-run";
    }

    QDir dir(fromDir);
    if (!dir.cdUp()) {
        return false;
    }

    QString abPath = dir.absolutePath();
    m_args << QString("--log-file=%1").arg(abPath + QString("/rsync_%1.log")
        .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh_mm_ss")));
    m_args << QString("--exclude-from=%1").arg(abPath + "/exclude.list");
    if (!m_fileFrom.isEmpty()) {
        m_args << QString("--files-from") << QString(dir.absolutePath() + "/fileFrom.list");
    }

    m_args << m_src;
    m_args << m_dest;
    return true;
}

void RsyncTask::doResult()
{
    QJsonObject jsonObject;
    QString err = m_process->readAllStandardError();
    int errCode = m_process->exitCode();
    const int errRsyncOperationNotSupported = 23;
    const int errRsyncVanished = 24;
    if ((errRsyncOperationNotSupported == errCode || errRsyncVanished == errCode) && m_cmd == "rsync") {
        qWarning()<<"rsync file has vanished , ignore errCode = "<<errCode<<", err = "<<err;
        QDir dir(m_checkPath);
        if (!dir.exists()) {
            m_errLogs = QString("path not exit: %1").arg(m_checkPath);
            // qCritical() << m_errLogs;
        } else {
            errCode = 0;
        }
    }
    if (m_process->exitStatus() != QProcess::NormalExit || errCode != 0) {
        qCritical()<<"AsyncTask::run, errCode = "<<errCode<<", err: "<<err;
        qCritical() << QString("exec %1 failed, arguments:%2").arg(m_cmd).arg(m_args.join(" "));
        // qCritical()<<"errLogs: "<<m_errLogs;
        if (err.isEmpty()) {
            err = m_errLogs;
        }
        jsonObject.insert("progress", 0);
        jsonObject.insert("remainSecond", 0);
        jsonObject.insert("errMsg", err);
        jsonObject.insert("errCode", errCode);
        Q_EMIT error(jsonObject);
        return;
    }

    m_progress = 100;
    m_remainSecond = 0;
    reportProgress(m_progress, m_remainSecond);
    Q_EMIT success(jsonObject);
}

void RsyncTask::setCheckPath(const QString &path)
{
    m_checkPath = path;
}

void RsyncTask::setOptions(const QStringList &options)
{
    m_options = options;
}

void RsyncTask::setLinkDest(const QString &path)
{
    m_linkDest = path;
}

void RsyncTask::setSourcePath(const QString &path)
{
    m_src = path;
}

void RsyncTask::setDestinationPath(const QString &path)
{
    m_dest = path;
}

void RsyncTask::setExcludes(const QStringList &excludes)
{
    for (auto &e : excludes) {
        m_excludes.insert(e);
    }
}

void RsyncTask::setFileForm(const QStringList &fileFrom)
{
    for (auto &e : fileFrom) {
        m_fileFrom.insert(e);
    }
}

void RsyncTask::enableDryRun(bool enable)
{
    m_dryRun = enable;
}

int RsyncTask::parseProgress(const QString &info)
{
    auto content = info.split("=");
    if (content.size() == 2) {
        auto ratio = content.at(1).split("/");
        if (ratio.size() == 2) {
            int remain = ratio.at(0).trimmed().toInt();
            int total = QString(ratio.at(1)).replace(")", "").trimmed().toInt();
            int progress = (total - remain) * 100 / total;
            return progress;
        }
    }
    return -1;
}

void RsyncTask::reportProgress(int progress, int remainSecond)
{
    QJsonObject jsonObject;
    jsonObject.insert("progress", progress);
    jsonObject.insert("remainSecond", remainSecond);
    Q_EMIT progressChanged(jsonObject);
}

void RsyncTask::setOperateType(OperateType opType)
{
    m_opType = opType;
}

void RsyncTask::updateGrub()
{
    if (m_opType == OperateType::SystemBackup) {
        qInfo()<<"updateGrub task begin ...";
        m_opType = OperateType::Invalid;
        QString out;
        QString errMsg;
        QStringList args;
        if (Process::spawnCmd("update-grub", args, out, errMsg)) {
            qInfo()<<"updateGrub task end ...";
            m_progress = 100;
            m_remainSecond = 0;
            reportProgress(m_progress, m_remainSecond);
            return;
        }

        qWarning()<<"updateGrub task failed, errMsg = "<<errMsg<<", out = "<<out;
        QString err = "update grub failed: " + errMsg;
        QJsonObject jsonObject;
        jsonObject.insert("progress", 0);
        jsonObject.insert("remainSecond", 0);
        jsonObject.insert("errMsg", err);
        Q_EMIT error(jsonObject);
    }
}
