/*
 * Copyright (C) 2002  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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "systemdbusregister.h"
#include <polkitqt1-authority.h>
#include <QDBusConnection>
#include <QDBusError>
#include <QDBusMessage>
#include <QDebug>
#include <QThread>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QDBusConnectionInterface>

using namespace PolkitQt1;

SystemDbusRegister::SystemDbusRegister()
{

}

//excute dd command
void SystemDbusRegister::MakeStart(QString sourcePath,QString targetPath){
    //root authorization
    //   防命令注入
    //  1、形如：mkdir '`id&>id_bak_test.txt`'中的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*`.*`.*"))||targetPath.contains(QRegularExpression(".*`.*`.*")))
        return ;
    // 2、形如：$()的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*\\$\\(.*\\).*"))||targetPath.contains(QRegularExpression(".*\\$\\(.*\\).*")))
        return ;
    // 3、形如：${}的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*\\$\\{.*\\}.*"))||targetPath.contains(QRegularExpression(".*\\$\\{.*\\}.*")))
        return ;
    // 4、包含[;、&、|]等可以包含并执行系统命令或用于连续执行系统命令的符号
    if (sourcePath.contains(QRegularExpression("[;&|]+"))||targetPath.contains(QRegularExpression("[;&|]+")))
        return ;

    sourceFile = sourcePath;
    m_isdeviceexist=false;
    m_getmountpoint=false;
        qDebug()<<__LINE__<<__func__<<m_getmountpoint;
        Authority::Result result;
        SystemBusNameSubject subject(message().service());
        result = Authority::instance()->checkAuthorizationSync("com.kylinusbcreator.systemdbus.authoritycheck",
                                                               subject , Authority::AllowUserInteraction);
        if (result == Authority::Yes){
            //TODO: send authorization failed dbus message
            emit authorityStatus("success");
        }else{
            //there's two cases NO and Challenge
            emit authorityStatus("failed");
            return ;
        }

    env = QProcessEnvironment::systemEnvironment();
    uDiskPath = targetPath;
    QFileInfo info(sourcePath);
    sourceFileSize = info.size()/1000000;
    // if(m_formate=="true"){
    if(umountPath(uDiskPath)){
        qInfo("开始进入格式化");
        bash=new QProcess(this);
        connect(bash,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(mkfs_finished(int,QProcess::ExitStatus)));
        connect(bash, &QProcess::readyReadStandardOutput, this, [=]() {
            mkfsoutput=bash->readAllStandardOutput();
            qDebug()<<__LINE__<<__func__<<mkfsoutput;
        });
        connect(bash, &QProcess::readyReadStandardError, this, [=]() {
            mkfserror=bash->readAllStandardError();
            qDebug()<<__LINE__<<__func__<<mkfserror;
        });
        qInfo("开始进入格式化1");
        QStringList mkfsargs;
        qInfo("开始进入格式化2");
        qInfo()<<__LINE__<<__func__<<uDiskPath;
        mkfsargs<<"-F32"<<"-v"<<"-I"<<uDiskPath.toLocal8Bit();
        qInfo("开始进入格式化3");
        bash->start("/usr/sbin/mkfs.fat",mkfsargs);
        qInfo("开始进入格式化4");
        if (bash->waitForStarted(-1)){
            qInfo("开始进入格式5");
        }else{
            emit makeFinish("make_error");
            qInfo()<<__LINE__<<__func__<<"格式化未启动";
        }
    }else{
        emit makeFinish("make_error");
        qInfo()<<__LINE__<<__func__<<"卸载失败";
    }
    qInfo("开始进入格式化6");
}

void SystemDbusRegister::mkfs_finished(int exitCode, QProcess::ExitStatus exitStatus){
    if(exitStatus==QProcess::NormalExit&& exitCode==0){
        qInfo("格式化结束");
        QThread::sleep(1);
        qInfo("格式化结束1");
        getmountpoint();
        qInfo("格式化结束2");
        if(m_getmountpoint==true){
            command_dd = new QProcess();
            //指定输出语言
            env.insert("LANGUAGE","en_US:en");
            command_dd->setProcessEnvironment(env);
            connect(command_dd,&QProcess::readyReadStandardOutput,this,&SystemDbusRegister::readBashStandardErrorInfo);
            connect(command_dd,&QProcess::readyReadStandardError,this,&SystemDbusRegister::readBashStandardErrorInfo);
            connect(command_dd,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dd_finished(int,QProcess::ExitStatus)));
            m_isFinished = false;
            QTimer::singleShot(1*1000, this, &SystemDbusRegister::handletimeout);
            checkMd5FinishTimer = new QTimer;
            connect(checkMd5FinishTimer, &QTimer::timeout, this, &SystemDbusRegister::checkmd5finish);
            QStringList args;
            args<<"if="+sourceFile.toLocal8Bit()<<"of="+uDiskPath.toLocal8Bit()<<"status=progress"<<"bs=16M";
            command_dd->start("dd", args);
            if(command_dd->waitForStarted()){
                qInfo()<<"97command_dd->arguments()"<<command_dd->arguments();

            }
            qInfo()<<__LINE__<<__func__<<"dd开始";
            m_isStarted = true;
        }else{
            qInfo("格式化结束3");
            mountDevice(uDiskPath);
            qInfo("格式化结束4");
            QThread::sleep(1);
            command_dd = new QProcess();
            //指定输出语言
            env.insert("LANGUAGE","en_US:en");
            command_dd->setProcessEnvironment(env);
            connect(command_dd,&QProcess::readyReadStandardOutput,this,&SystemDbusRegister::readBashStandardErrorInfo);
            connect(command_dd,&QProcess::readyReadStandardError,this,&SystemDbusRegister::readBashStandardErrorInfo);
            connect(command_dd,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dd_finished(int,QProcess::ExitStatus)));
            m_isFinished = false;
            QTimer::singleShot(1*1000, this, &SystemDbusRegister::handletimeout);
            checkMd5FinishTimer = new QTimer;
            connect(checkMd5FinishTimer, &QTimer::timeout, this, &SystemDbusRegister::checkmd5finish);
            QStringList args;
            args<<"if="+sourceFile.toLocal8Bit()<<"of="+uDiskPath.toLocal8Bit()<<"status=progress"<<"bs=16M";
            command_dd->start("dd", args);
            if(command_dd->waitForStarted()){
                qInfo()<<"97command_dd->arguments()"<<command_dd->arguments();

            }
            qInfo()<<__LINE__<<__func__<<"dd开始";
            m_isStarted = true;
        }
    } else{
        qInfo()<<__LINE__<<__func__<<"格式化失败"<<"退出码:"<<exitCode<<bash->errorString();
        m_isFinished = true;
        emit makeFinish("fail");
        qInfo()<<__LINE__<<__func__<<"发送退出";
    }
}
void SystemDbusRegister::dd_finished(int exitCode, QProcess::ExitStatus exitStatus){ 
    if(exitStatus!=QProcess::NormalExit&& exitCode!=0){
        m_isStarted = false;
        m_isFinished = true;
        qInfo("make_error");
        qInfo()<<__func__<<__LINE__<<"dd异常退出"<<command_dd->errorString();
        emit makeFinish("make_error");
    } else if (m_isStarted) {
        m_isStarted = false;
        qInfo()<<__func__<<__LINE__<<"dd正常退出";
        finishEvent();
    } else {
        m_isStarted = false;
        m_isFinished = true;
    }
}

/**
 * @brief 判断程序是否已开启
 * @param processName
 * @return bool
 */
bool SystemDbusRegister::isRunning(const QString &processName)
{
    bool bRet = false;

    QProcess ps;
    QProcess grep;
    ps.setStandardOutputProcess(&grep);

    ps.start("ps", QStringList() << "-ef");
    grep.start("grep", QStringList() << processName);

    if (grep.waitForFinished()) {
        QString result(grep.readAll());
        //qDebug() << result;
        QStringList lines = result.split("\n");
        for (const QString& line : lines) {
            if (line.contains("grep"))
                continue;
            if (line.contains(processName)){
                bRet = true;
                break;
            }
        }
    }

    grep.close();
    ps.close();

    return bRet;
}

void SystemDbusRegister::handletimeout(){
    if (m_isFinished)
        return;

    QFile file(sourceFile);
    if (file.exists()) {
        if (m_isStarted) {
            qInfo("dd running...");
            // 判断dd是否还在，不在就报错
            if (!isRunning("/usr/bin/dd "))
            {
                qInfo()<<__LINE__<<__func__<<"dd不在运行"<<m_isStarted;
                command_dd->kill();
                // emit makeFinish("make_error");
            }
        }
        QTimer::singleShot(1*1000, this, &SystemDbusRegister::handletimeout);
    } else {
        qInfo("源文件不存在，dd退出");
        command_dd->kill();
        emit makeFinish("make_error");
    }
}

//kill task process
void SystemDbusRegister::MakeExit(){
    //    qDebug()<<"exit dd process";
    //    if(!command_dd) return ;
    //    if(command_dd->Running){
    //        qDebug()<<"dd Runing";
    //        command_dd->kill();
    //        return ;
    //    }
    if(isRunning("/usr/bin/kylin-usb-creator-sysdbus")){
        qDebug()<<"exit kylin-usb-creator-sysdbus process";
        if(bash != nullptr)
        {
            bash->kill();
        }
        if(command_dd != nullptr)
        {
            command_dd->kill();
        }
        if(umount.state() == QProcess::Running)
        {
            umount.kill();
        }
        if(mount.state() == QProcess::Running)
        {
            mount.kill();
        }
        if(md5 != nullptr)
        {
            md5->kill();
        }
        exit(-1);
        qDebug()<<"exit kylin-usb-creator-sysdbus process quit";
        return ;
    }
    return ;
}

void SystemDbusRegister::readBashStandardErrorInfo()
{
    QByteArray cmdout = command_dd->readAllStandardError();
    cmdout.trimmed();
    if(!cmdout.isEmpty() && cmdout != "\r" && cmdout != "\n"){
        QString str = cmdout;
        qInfo()<<"dd写入.."<<str;
        str = str.replace(" ","");
        //        if(str =="" || str.contains("[sudo]")) {return;}
        str = str.replace("\r","");
        QStringList bytes2 = str.split("bytes");
        QString size_progress = bytes2.first();
        bool ok = false;
        qulonglong progress_num = size_progress.toDouble(&ok)/1048576;
        int mission_percent = progress_num*100/sourceFileSize;
        qInfo()<<"mission_percent "<<mission_percent;

        if(!mission_percent){
            qInfo()<<"mission_percent is 0"<<str;
            return ;
        }
        QString ii=QString::number(mission_percent);
        if(ii.isEmpty()){
            return;
        }
        //send mission percent debus message every output
        emit workingProgress(mission_percent);
    }else{
        qInfo()<<"cmdout is empty,return";
        return;
    }
}

void SystemDbusRegister::finishEvent(){
    qInfo()<<"dd结束";
    QTimer *diskRefreshDelay = new QTimer;
    connect(diskRefreshDelay,&QTimer::timeout,[=]{
        diskRefreshDelay->stop();
        isMakingSucess();
        if(m_isdeviceexist==true){
            //send production success dbus message
            qInfo()<<"make success开始同步";
            sync=new QProcess();
            connect(sync,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(syncreadStandardOutput(int,QProcess::ExitStatus)));
            sync->start("sync");
            sync->waitForStarted();
            emit makeFinish("sync");
        }else{
            //send production failure dbus message
            qInfo()<<"多分区U盘"<<"make failed";
            m_isFinished = true;
            emit makeFinish("fail");
        }
    });
    diskRefreshDelay->start(2000);
}
void SystemDbusRegister::syncreadStandardOutput(int exitCode, QProcess::ExitStatus exitStatus){
    QString syncstr;
    QString syncerr;
    if(QProcess::NormalExit==exitStatus&&exitCode==0){
        qInfo()<<"同步正常结束";
        qInfo()<<"sync success!";
        emit makeFinish("unsync");

        qInfo()<<sync->arguments();
        QThread::sleep(1);
        if(umountPath(uDiskPath))
        {
            QThread::sleep(1);
            getmountpoint();
            if(m_getmountpoint==true){
                qDebug()<<__LINE__<<__func__<<"进入自动挂载点校验";
                QThread::sleep(1);
                checkmd5();
            }else{
                QThread::sleep(1);
                if(mountDevice(uDiskPath)){
                    QThread::sleep(1);
                    checkmd5();
                }
            }
        }
        m_isFinished = true;
    }else{
        qWarning()<<"An unknown error occurred! Mount process exit code:"<<sync->exitCode();
        syncstr=sync->errorString();
        syncerr=sync->readAllStandardError();
        qInfo()<<__LINE__<<__func__<<"挂载输出"<<syncerr;
        qInfo()<<__LINE__<<__func__<<"挂载err输出"<<syncstr;
        emit makeFinish("fail");
        emit makeFinish("unsync");
        qInfo()<<"同步失败";
    }

}
bool SystemDbusRegister::mountDevice(QString target){

    QString mountstr;
    QString mounterr;
    qInfo()<<"start mounting disk:"<<target;
    QDir dir;
    mountpath = "/mnt/kylinusbcreator/";
    QDir::setCurrent("/mnt/");
    dir.mkdir("kylinusbcreator");
    QDir::setCurrent(QDir::rootPath());
    QStringList stringlist;
    stringlist<<target.toLocal8Bit()<<mountpath.toLocal8Bit()<<"-v";
    mount.start("mount",stringlist);
    mount.waitForStarted();
    mount.waitForFinished(-1);
    if(QProcess::NormalExit==mount.exitStatus()&& mount.exitCode()==0){
        qInfo()<<mount.arguments();
        qInfo()<<"Mount success!";
        return true;
    }else{
        qWarning()<<"An unknown error occurred! Mount process exit code:"<<mount.exitCode();
        mountstr=mount.errorString();
        mounterr=mount.readAllStandardError();
        m_getmountpoint=false;
        qInfo()<<__LINE__<<__func__<<"挂载输出"<<mountstr;
        qInfo()<<__LINE__<<__func__<<"挂载err输出"<<mounterr;
        getmountpoint();
        if(m_getmountpoint==true){
            qInfo()<<__LINE__<<__func__<<"U盘被挂载";
            return true;
        }
        else{
            emit makeFinish("fail");
            qInfo()<<"挂载失败";
            return false;
        }
    }

}

void SystemDbusRegister::isMakingSucess()
{
    qInfo()<<"开始判断U盘是否正常挂载";
    QList<QStorageInfo> storageInfo = QStorageInfo::mountedVolumes();
    qDebug()<<__LINE__<<__func__<<storageInfo.size();
    for(int i=0;i<storageInfo.size();i++){
        qDebug()<<__LINE__<<__func__<<storageInfo.at(i).device();
        if(storageInfo.at(i).device() == uDiskPath){
            qDebug()<<__LINE__<<__func__<<uDiskPath;
            m_isdeviceexist=true;
            //return true;
        }
    }
}

void  SystemDbusRegister::getmountpoint()
{
    m_getmountpoint = false;
    qInfo()<<"in getmountpoint function ";
    QProcess lsblk;
    lsblk.start("lsblk", QStringList() << "-J" << "-o" << "NAME,TYPE,MOUNTPOINT,TRAN");
    lsblk.waitForFinished();

    QJsonDocument doc = QJsonDocument::fromJson(lsblk.readAllStandardOutput());
    if (doc.isNull()) {
      qWarning() << "Failed to parse lsblk output";
      return;
    }

    QJsonArray devices = doc.object()["blockdevices"].toArray();
    for (const QJsonValue &deviceVal : devices) {
      QJsonObject device = deviceVal.toObject();

      // 检查是否为USB磁盘
      if (device["type"].toString() == "disk" &&
          device["tran"].toString() == "usb") {

          QString devName = "/dev/" + device["name"].toString();

          // 设备本身已挂载
          if (!device["mountpoint"].isNull() && uDiskPath == devName) {
              mountpath = device["mountpoint"].toString();
              m_getmountpoint = true;
              qInfo() << "Device mounted at:" << mountpath;
              return;
          }

          // 检查分区
          QJsonArray children = device["children"].toArray();
          for (const QJsonValue &childVal : children) {
              QJsonObject child = childVal.toObject();
              QString partName = "/dev/" + child["name"].toString();

              if (child["type"].toString() == "part" &&
                  !child["mountpoint"].isNull() &&
                  partName == uDiskPath) {
                  mountpath = child["mountpoint"].toString();
                  m_getmountpoint = true;
                  qInfo() << "Partition mounted at:" << mountpath;
                  return;
              }
          }
      }
    }
    qWarning() << "No matching mounted USB device found:" << uDiskPath;
}

QJsonArray SystemDbusRegister::QStringToJsonArray(const QString jsonString){
    QJsonParseError err;
    QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toLocal8Bit().data(),&err);
    if(jsonDocument.isNull())
    {
        qDebug()<< "JsonDocument is NULL.Origin data is:"<< jsonString.toLocal8Bit().data();

    }
    if(err.error != QJsonParseError::NoError){
        qDebug()<<"Parase json"<<jsonString<<" error:"<<err.error;
        //TODO：这里的错误处理后期还可以优化,目前处理错误了就会调用exit()退出程序
        qInfo()<<"U盘被移除";
        //        exit(-1);
    }
    QJsonObject obj = jsonDocument.object();
    return obj["blockdevices"].toArray();
}

void SystemDbusRegister::checkmd5(){
    qInfo("开始检测MD5");
    m_isMd5Finish=false;
    ismakesuccess=false;
    md5=new QProcess();
    connect(md5,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(readStandardOutput(int,QProcess::ExitStatus)));
    QDir::setCurrent(mountpath);
    qDebug()<<QDir::currentPath();
    checkMd5FinishTimer->start(2000);
    md5->start("md5sum",QStringList{"-c","md5sum.txt"});
    connect(md5, &QProcess::readyReadStandardOutput, this, [=]() {
        MD5str=this->md5->readAllStandardOutput();
        qDebug()<<"calculating md5: "<<MD5str;
    });
}

void SystemDbusRegister::readStandardOutput(int exitCode,QProcess::ExitStatus exitStatus){
    qDebug()<<"md5退出"<<exitCode;
    if(exitStatus==QProcess::NormalExit && exitCode==0){
        if(MD5str.isEmpty()){
            qDebug()<<__LINE__<<__func__<<"输出为空"<<MD5str;
            QDir::setCurrent(QDir::rootPath());
            qDebug()<<QDir::currentPath();
            ismakesuccess=false;
        }else{
            if (MD5str.contains(tr("Fail")) || MD5str.contains(tr("FAILED")))
            {
                qDebug()<<__LINE__<<__func__<<"有错误字符"<<MD5str;
                qDebug()<<"md5 fail!";
                QDir::setCurrent(QDir::rootPath());
                qDebug()<<__LINE__<<__func__<<QDir::currentPath();
                ismakesuccess=false;
            }
            else{
                qDebug()<<__LINE__<<__func__<<"完全正确"<<MD5str;
                QDir::setCurrent(QDir::rootPath());
                qDebug()<<__LINE__<<__func__<<QDir::currentPath();
                ismakesuccess=true;
            }
        }

        qDebug()<<"md5 success!";
    }else{
        QDir::setCurrent(QDir::rootPath());
        qInfo()<<__func__<<__LINE__<<md5->errorString();
        qDebug()<<__LINE__<<__func__<<QDir::currentPath();
        qDebug()<<"md5 fail!";
    }
    qDebug()<<"md5退出"<<exitCode;
    m_isMd5Finish=true;

}

void SystemDbusRegister::checkmd5finish(){
    qDebug()<<"in checkmd5finish function";
    if(m_isMd5Finish==true){
        checkMd5FinishTimer->stop();
        if(ismakesuccess==true){
            qDebug()<<"md5 checked success,begin to umount";
            if(umountPath(mountpath)){
                emit makeFinish("success");
            }
            qDebug()<<"status"<<m_isFinishstatus;
        }else if(ismakesuccess==false){
            qDebug()<<"md5 checked failed,begin to umount";
            if(umountPath(mountpath)){
                emit makeFinish("fail");
            }
            qDebug()<<"ismakesuccess"<<ismakesuccess;
            qDebug()<<"status"<<m_isFinishstatus;
        }
    }
}

QStringList SystemDbusRegister::findPartitions(QString devname) {
    qInfo() << "in findPartitions function" << devname;
    QStringList partitions;
    QProcess process;

    process.start("lsblk", QStringList() << "-J" << "-o" << "NAME,TYPE,MOUNTPOINT");
    process.waitForFinished();

    QJsonDocument doc = QJsonDocument::fromJson(process.readAllStandardOutput());
    if (doc.isNull()) {
        qWarning() << "Failed to parse lsblk output";
        return partitions;
    }

    QJsonObject root = doc.object();
    QJsonArray devices = root["blockdevices"].toArray();

    // 查找目标设备及其分区
    for (const QJsonValue &deviceVal : devices) {
        QJsonObject device = deviceVal.toObject();
        if (device["name"].toString() == devname && device["type"].toString() == "disk") {
            QJsonArray children = device["children"].toArray();
            for (const QJsonValue &childVal : children) {
                QJsonObject child = childVal.toObject();
                if (child["type"].toString() == "part" && !child["mountpoint"].isNull()) {
                    partitions << "/dev/" + child["name"].toString();
                }
            }
            break;
        }
    }

    qInfo() << "Found partitions to unmount:" << partitions;
    return partitions;;
}

bool SystemDbusRegister::unmountPartition(const QString& partition) {
    QProcess process;
    process.start("umount", QStringList() << partition << "-v");
    process.waitForFinished(-1);

    if (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0) {
      qInfo() << "Successfully unmounted" << partition;
      return true;
    } else {
      qWarning() << "Failed to unmount" << partition
                << "Exit code:" << process.exitCode()
                << "Error:" << process.readAllStandardError();
      return false;
    }
}

bool SystemDbusRegister::umountPath(QString Path)
{
    qInfo() << "Unmounting device:" << Path;
    // 卸载设备本身
    if (unmountPartition(Path)) {
      qInfo() << "Device " << Path << "was mounted and has been unmounted";
      m_getmountpoint = false;
      return true;
    }

    // 获取设备名（如从/dev/sda1得到sda）
    QString devname = Path.mid(5); // 去掉"/dev/"
    if (devname.contains(QRegExp("[0-9]$"))) {
      devname = devname.left(devname.size()-1);
    }

    // 查找并卸载所有分区
    QStringList partitions = findPartitions(devname);
    bool allSuccess = true;

    for (const QString &partition : partitions) {
      if (!unmountPartition(partition)) {
          allSuccess = false;
          qWarning() << "Failed to unmount partition" << partition;
      }
    }

    if (partitions.isEmpty()) {
      qInfo() << "No mounted partitions found for device" << Path;
      return true;
    }

    if (!allSuccess) {
      emit makeFinish("fail");
      qInfo() << "Failed to unmount some partitions";
    }else{
        m_getmountpoint = false;
    }
    return allSuccess;
}
