#include "linuxsystemdetector.h"

#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QSettings>
#include <QDebug>
#include <QDBusInterface>
#include <QNetworkInterface>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include<QStandardPaths>

#include <tool.h>
#include <linux/netlinkmonitor.h>

#include <stdio.h>
#include <sys/sysinfo.h>
#include <time.h>

#include "diskid.h"

QList<DiskInfo>  getDiskInfoList()
{
    QList<DiskInfo> disks;
    QDir sys_block("/sys/block/");
    for (auto diskname : sys_block.entryList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
        QString diskpath = QString("/dev/%1").arg(diskname);
        // filter cdrom
        if (diskpath.startsWith("/dev/sr"))  {
            continue;
        }
        auto disk = getDiskInfo(diskpath);
        if (!disk.name.isEmpty()) {
            QJsonObject diskObj;
            disks.push_back(disk);
        }
    }
    return disks;
}

/*
cat /etc/deepin-version
[Release]
Version=15.6
Type=Desktop
Type[zh_CN]=桌面版

[Addition]
Milestone=
*/
class DeepinVersion
{
public:
    QString Release;
    QString Version;
    QString Type;
    QString Edition;
    QString Copyright;

    QJsonObject toObject() const;
};

DeepinVersion getDeepinVersion()
{
    DeepinVersion dv;
    QSettings deepinVersion("/etc/deepin-version", QSettings::IniFormat);

    deepinVersion.beginGroup("Release");
    dv.Version = deepinVersion.value("Version").toString();
    dv.Type = deepinVersion.value("Type").toString();
    dv.Edition = deepinVersion.value("Edition").toString();
    dv.Copyright = deepinVersion.value("Copyright").toString();
    deepinVersion.endGroup();

    return dv;
}

/*
/etc/machine-id
/sys/class/dmi/id/board_serial
/sys/class/dmi/id/product_uuid
*/
ExternInfo queryExternInfo()
{
    ExternInfo info;
    auto dv = getDeepinVersion();
    info.deepinVersion = QString("%1 %2").arg(dv.Version).arg(dv.Type);
    info.deepinEdition = dv.Edition;
    info.deepinCopyright = dv.Copyright;

    auto fileReadAll = [](const QString & filepath) -> QString {
        QFile file(filepath);
        file.open(QIODevice::ReadOnly);
        return QString::fromUtf8(file.readAll());
    };

    info.machineID = fileReadAll("/etc/machine-id").trimmed();
    info.dmi_board_serial = fileReadAll("/sys/class/dmi/id/board_serial").trimmed();
    info.dmi_product_uuid = fileReadAll("/sys/class/dmi/id/product_uuid").trimmed();

    return info;
}

static  bool runCommand(
    const QString &command,
    const QStringList &arguments,
    QByteArray *result
)
{
    QProcess proc;
    proc.setProgram(command);
    proc.setArguments(arguments);
    proc.start();
    proc.waitForFinished();
    *result = proc.readAllStandardOutput();
    return proc.exitCode() == QProcess::NormalExit;
}

static QString getCommandOutput(
    const QString &command,
    const QStringList &arguments
)
{
    QString result;
    QByteArray *result_array = new QByteArray;
    runCommand(command, arguments, result_array);
    result = *result_array;
    return result;
}

static time_t getLastRebootTime()
{
    struct sysinfo info;
    time_t cur_time = 0;
    time_t boot_time = 0;
    if (sysinfo(&info)) {
        return -1;
    }
    time(&cur_time);
    if (cur_time > info.uptime) {
        boot_time = cur_time - info.uptime;
    } else {
        boot_time = info.uptime - cur_time;
    }
    return boot_time;
}


QString lastRebootTimePath()
{
    QString appConfigPath = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).at(0);
    QDir(appConfigPath).mkpath(appConfigPath);
    return QString("%1/lastReboot.json").arg(appConfigPath);
}

qint64 loadLastRebootTime()
{
    QFile file(lastRebootTimePath());
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Couldn't read  lastReboot time from file:" << lastRebootTimePath();
        return -1;
    }
    QByteArray data = file.readAll();
    file.close();

    QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
    QJsonObject obj = jsonDoc.object();
    QString lastRebootTime = obj.value("lastRebootTime").toString();
    return lastRebootTime.toLongLong();
}

void saveLastRebootTime(qint64 lastRebootTime)
{
    QJsonObject data {
        {"lastRebootTime", QString::number(lastRebootTime)},
    };

    QFile file(lastRebootTimePath());
    if (!file.open(QIODevice::WriteOnly)) {
        qDebug() << "Couldn't save lastReboot time to file:" << lastRebootTimePath();
        return;
    }

    QJsonDocument jsonDoc(data);
    file.write(jsonDoc.toJson());
    file.close();
}

static QDateTime getlastStartupTime()
{
    QString last_startup_time;
    QString command, command_result;

    qint64 startuptime = getLastRebootTime();
    qint64 cacheStartTime = loadLastRebootTime();
    if (startuptime != -1) {
        if (qAbs(cacheStartTime - startuptime) >= 60) {
            last_startup_time = QString("%1").arg(startuptime * 1000);
            saveLastRebootTime(startuptime);
        } else {
            last_startup_time = QString("%1").arg(cacheStartTime * 1000);
        }
        return QDateTime::fromMSecsSinceEpoch(last_startup_time.toLongLong());
    } else {
        command_result = getCommandOutput("uptime", QStringList() << "-s");
        last_startup_time = command_result.remove("\n");
        return QDateTime::fromString(last_startup_time, Qt::ISODate);
    }
}

static QJsonObject QuerySoftInfoByName(const QString &appName)
{
    QJsonObject softInfo;
    QString name, version, office_serial_number;
    office_serial_number = "unknown";
    QDateTime office_install;
    // 使用dpkg-query来列出包版本
    // FIXME: Deepin only
    QString command, command_result;
    QStringList arguments;


    command = "dpkg-query";
    arguments.clear();
    arguments.append("--show");
    arguments.append("--showformat='${Package}:${Version}\n'");
    arguments.append(appName);
    command_result = getCommandOutput(command, arguments);

    if (command_result.length() > 0) {
        QStringList lines = command_result.trimmed().split("\n");

//        for (QString line : lines) {
        QString line = lines.value(0);
        name = appName;
        version = line.split(":").last().simplified();
//            break;
//        }

        // 获取安装时间
        command = "bash";
        arguments.clear();
        arguments.append("-c");
        QString bash_command =
            QString("dpkg -L %1 |grep '.desktop$'").arg(name);
        arguments.append(bash_command);
        command_result = getCommandOutput(command, arguments);

        // FIXME: 判断是否有输出
        for (QString line : command_result.split("\n")) {
            QString file_name = line.trimmed();
            QFileInfo info(file_name);
            office_install = info.created();
            qDebug() << "office install:" << office_install;
            break;
        }

        softInfo.insert("name", name);
        softInfo.insert("version", version);
        softInfo.insert("category", "office");
        softInfo.insert("authorized", false);
        softInfo.insert("installation_time", QString("%1").arg(office_install.toMSecsSinceEpoch()));
    }

    if (name.contains("wps-office")) {
        if (version.contains("professional")) {
            QStringList productKeys;
            QStringList homeDirs;
            QString ret = getCommandOutput("cat", QStringList() << "/etc/passwd");
            for (QString line : ret.split("\n")) {
                QStringList items = line.split(":");
                if (items.length() == 7) {
                    if (items[2].toInt() >= 1000) {
                        homeDirs << items[5];
                    }
                }
            }

            for (QString homeDir : homeDirs) {
                QString productkeyPath = QString("%1/.kingsoft/office6/data/license2.dat").arg(homeDir);
                for (QString key : Tool::getWPSProductkeys(productkeyPath)) {
                    if (!productKeys.contains(key)) {
                        productKeys.append(key);
                    }
                }
            }

            softInfo.insert("product_keys", QJsonArray::fromStringList(productKeys));
            softInfo.insert("authorized", !productKeys.isEmpty());
        } else {
            softInfo.insert("authorized", true);
        }
    }

    return softInfo;
}

static QString getEthernetAddress()
{
    QString macAddr;
    for (auto &interface : QNetworkInterface::allInterfaces()) {
        auto flags = interface.flags();
        if (!flags.testFlag(QNetworkInterface::IsLoopBack) && flags.testFlag(QNetworkInterface::CanMulticast)) {
            qDebug() << interface;
            if (interface.name().startsWith("en")) {
                macAddr = interface.hardwareAddress().toLower();
            }
        }
    }
    return macAddr;
}

class LinuxSystemDetectorPrivate
{
public:
    LinuxSystemDetectorPrivate(LinuxSystemDetector *parent) : q_ptr(parent) {}

    QJsonObject getOSInfo();

    LinuxSystemDetector *q_ptr;
    Q_DECLARE_PUBLIC(LinuxSystemDetector)
};

LinuxSystemDetector::LinuxSystemDetector(QObject *parent) :
    CommonSystemDetector(parent), dd_ptr(new LinuxSystemDetectorPrivate(this))
{
    NetLinkMonitor *monitor = new NetLinkMonitor(getSoftwareFilters(), this);
    monitor->start();

    connect(monitor, &NetLinkMonitor::softwareStarted, this, &LinuxSystemDetector::onSoftwareStarted);
    connect(monitor, &NetLinkMonitor::softwareStoped, this, &LinuxSystemDetector::onSoftwareStoped);
}

LinuxSystemDetector::~LinuxSystemDetector()
{

}

QString LinuxSystemDetector::macAddress() const
{
    return getEthernetAddress();
}

void LinuxSystemDetector::saveOSHistory()
{
    onSoftwareStarted(0, OS_HISTORY_KEY, getlastStartupTime().toMSecsSinceEpoch());
}

QJsonArray LinuxSystemDetector::getSoftwareInfo()
{
    Q_D(LinuxSystemDetector);
    QJsonObject softwareUsedHistory = getSoftwareUsedHistory();
    QJsonArray softinfo = CommonSystemDetector::getSoftwareInfo();

    softinfo.append(d->getOSInfo());

    QList<SoftwareFilter> filters = getSoftwareFilters();
    for (auto filter : filters) {
        QJsonObject softObj;
        auto name = filter.nameFilters.value(0);
        qDebug() << "query software info for" << name;
        softObj = QuerySoftInfoByName(name);
        if (softObj.value("name").toString().isEmpty()) {
            continue;
        }
        QStringList cmds = filter.binaryFilters;
        QJsonObject historyObj;
        for (const QString &cmd : cmds) {
            if (softwareUsedHistory.contains(cmd)) {
                historyObj.insert(cmd, softwareUsedHistory.value(cmd));
            }
        }
        softObj.insert("used_history", historyObj);
        softinfo.append(softObj);
    }

    return softinfo;
}

QSharedPointer<SystemInfo> LinuxSystemDetector::fetchSystemInfo()
{
    QSharedPointer<SystemInfo> sysInfo = CommonSystemDetector::fetchSystemInfo();

    // get system version
    sysInfo->setMacAddress(getEthernetAddress());
    sysInfo->setSoftInfo(getSoftwareInfo());
    sysInfo->setDiskInfo(getDiskInfoList());
    sysInfo->setAdapterInfo(queryAdapterInfo());
    sysInfo->setExternInfo(queryExternInfo());

    return sysInfo;
}

QJsonObject LinuxSystemDetectorPrivate::getOSInfo()
{
    Q_Q(LinuxSystemDetector);

    QString os_name, os_version, installation_time, last_startup_time;
    QDateTime os_install = QDateTime::currentDateTime();
    QString command, command_result;
    QStringList arguments;
    // 获取系统名称
    command = "lsb_release";
    arguments << "-i";
    command_result = getCommandOutput(command, arguments);

    if (command_result.length() > 0) {
        os_name = command_result.split(':').last().simplified();
    } else {
        os_name = "UNKNOWN";
    }

    // 获取系统版本
    command_result = "";
    arguments.clear();
    arguments << "-r";
    command_result = getCommandOutput(command, arguments);
    if (command_result.length() > 0) {
        os_version = command_result.split(':').last().simplified();
    } else {
        os_version = "UNKNOWN";
    }

    // FIXME: Deepin-only, 需要改为通用
    QFileInfo file_info("/etc/machine-id");
    os_install = file_info.created();
    qDebug() << "os install:" << os_install;
    installation_time = QString("%1").arg(os_install.toMSecsSinceEpoch());

    QDBusInterface licenseInterface(
        "com.deepin.license",
        "/com/deepin/license/Info",
        "com.deepin.license.Info", QDBusConnection::systemBus());

    auto status = licenseInterface.property("LicenseStatus").toInt();
    auto state = licenseInterface.property("LicenseState").toInt();
    const auto AuthorizedCode = 61695;
    bool authorized = (status == AuthorizedCode) || (state == AuthorizedCode);

    auto dv = getDeepinVersion();
    if (dv.Edition.isEmpty()) {
        authorized = true;
    }

    QJsonObject osInfo;
    osInfo.insert("name", os_name);
    osInfo.insert("version", os_version);
    osInfo.insert("category", "os");
    osInfo.insert("authorized", authorized);
    osInfo.insert("installation_time", installation_time);

    last_startup_time = QString("%1").arg(getlastStartupTime().toMSecsSinceEpoch());
    qDebug() << "last_startup_time:" << last_startup_time;
    QJsonObject softwareUsedHistory = q->getSoftwareUsedHistory();

    QMap<QString, int> historyMap;
    if (softwareUsedHistory.contains(OS_HISTORY_KEY)) {
        auto osHistories = softwareUsedHistory.value(OS_HISTORY_KEY).toArray();
        QJsonObject obj;
        obj.insert("pid", 0);
        obj.insert("startup", last_startup_time);
        osHistories.append(obj);

        for (auto history : osHistories) {
            historyMap.insert(history.toObject().value("startup").toString(), 1);
        }
    } else {
        historyMap.insert(last_startup_time, 1);
    }

    QJsonArray osTimes;
    for (auto startup : historyMap.keys()) {
        QJsonObject osTime;
        osTime.insert("startup", startup);
        osTime.insert("shutdown", "");
        osTimes.append(osTime);
    }

    QJsonObject usedHistory;
    usedHistory.insert(os_name, osTimes);

    osInfo.insert("used_history", usedHistory);

    return osInfo;
}
