#include "winsystemdetector.h"

#include <chrono>

#include <Windows.h>

#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QFileInfo>
#include <QSettings>
#include <QDebug>
#include <QDateTime>
#include <QTimer>
#include <QNetworkInterface>

#include "genuine.h"
#include "program.h"
#include "iphelper.h"
#include "wmiobject.h"
#include "microsoftoffice.h"
#include "wpsoffice.h"
#include "registry.h"
#include "tool.h"
#include "disk.h"
#include "util.h"

static QDateTime getlastStartupTime()
{
    auto winOS = Genuine::GetWin32OperatingSystem();
    auto lastBootUpTime = winOS.value("LastBootUpTime").toDateTime();
    return QDateTime::fromMSecsSinceEpoch(lastBootUpTime.toMSecsSinceEpoch() / 1000 * 1000);
}

#include <LM.h>
#pragma comment(lib, "netapi32.lib")

DWORD PASCAL NetGetVersion(void)
{
    DWORD dwVersion = 0;
    WKSTA_INFO_100 *wkstaInfo = NULL;
    NET_API_STATUS netStatus = NetWkstaGetInfo(NULL, 100, (BYTE **)&wkstaInfo);
    if (netStatus == NERR_Success) {
        DWORD dwMajVer = wkstaInfo->wki100_ver_major;
        DWORD dwMinVer = wkstaInfo->wki100_ver_minor;
        dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
        NetApiBufferFree(wkstaInfo);
    }
    return dwVersion;
}
ExternInfo queryExternInfo()
{
    ExternInfo info;

    HKEY keyHandle;
    WCHAR rgValue[1024];
    DWORD dwType;
    DWORD dwLength = 1023;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle) == ERROR_SUCCESS) {
        auto retCode = RegQueryValueEx(keyHandle, L"ProductId", NULL, &dwType, (LPBYTE)rgValue, &dwLength);
        if (retCode != ERROR_SUCCESS) {
            qDebug() << "RegQueryValueEx " << retCode << "DataLength" << dwLength;
        }

        QVariant value;
        switch (dwType) {
        case 1: // string
            value = QString::fromWCharArray((wchar_t *)rgValue, dwLength / 2 - 1);
            break;
        default:
            break;
        }

        info.dmi_product_uuid = value.toString();

        RegCloseKey(keyHandle);
    }
    return info;
}

class WinSystemDetectorPrivate
{
public:
    WinSystemDetectorPrivate(WinSystemDetector *parent) : q_ptr(parent)
    {
        QTimer::singleShot(1000 * 2, [ = ]() {
            cacheSoftwareList();
        });
    }

    void cacheSoftwareList();
    QJsonObject queryOSInformation();
    QJsonArray querySoftware(const SoftwareFilter &filter);
    QList<AdapterInfo> GetValidEthernetAddress() const;

    QList<Software> cacheProgramList;

    WinSystemDetector *q_ptr;
    Q_DECLARE_PUBLIC(WinSystemDetector)
};

WinSystemDetector::WinSystemDetector(QObject *parent) :
    CommonSystemDetector(parent), dd_ptr(new WinSystemDetectorPrivate(this))
{
    Win::FixVBSAssoc();
    Software::registerStatusHandle("Microsoft Office 2003", new Win::MicrosoftOffice);
    Software::registerStatusHandle("Microsoft Office 2007", new Win::MicrosoftOffice);
    Software::registerStatusHandle("Microsoft Office 2010", new Win::MicrosoftOffice);
    Software::registerStatusHandle("Microsoft Office 2013", new Win::MicrosoftOffice);
    Software::registerStatusHandle("Microsoft Office 2016", new Win::MicrosoftOffice);

    QTimer::singleShot(1000 * 1, [ = ]() {
        auto obj = Win::GetWmiObject();
        connect(obj, &Win::WmiObject::wmiEventIndicate,
        this, [ = ](const QString & type, QMap<QString, QVariant> data) {
            auto processID = data.value("ProcessId").toInt();
            auto executablePath = data.value("ExecutablePath").toString();
            if (executablePath.isEmpty()) {
                executablePath = data.value("CommandLine").toString();
            }
            auto creationDate = data.value("CreationDate").toLongLong();
//            qDebug() << processID << executablePath;
            for (auto filter : getSoftwareFilters()) {
                if (filter.matchBinary(executablePath)) {
                    qDebug() << "event type" << type << processID << executablePath << creationDate;
                    if (type == "__InstanceCreationEvent") {
                        onSoftwareStarted(processID, executablePath, creationDate);
                    }
                    if (type == "__InstanceDeletionEvent") {
                        onSoftwareStoped(processID, executablePath, creationDate);
                    }
                }
            }
        });
        obj->Monitor("");
    });
}

WinSystemDetector::~WinSystemDetector()
{

}

void WinSystemDetector::test()
{
    Q_D(WinSystemDetector);
    auto diskinfoList = Win::GetWmiObject()->QueryDiskInfo();
    for (auto info : diskinfoList) {
        qDebug() << info.name << info.serial_no << info.model;
    }
    diskinfoList = Win::GetWmiObject()->QueryXPDiskInfo();
    for (auto info : diskinfoList) {
        qDebug() << info.name << info.serial_no << info.model;
    }
    auto adapterList = d->GetValidEthernetAddress();
    for (auto adapter : adapterList) {
        qDebug() << adapter.description << adapter.hardwareAddress << adapter.flags;
    }

    qDebug() << d->queryOSInformation();
    return;

//    qDebug() << Genuine::QueryGenuineInfo().toJson();
//    return;
}

QString WinSystemDetector::macAddress() const
{
    Q_D(const WinSystemDetector);
    auto adapterList = d->GetValidEthernetAddress();
    if (adapterList.size() > 0) {
        return adapterList.first().hardwareAddress.toLower();
    }

    for (auto &networkIf : QNetworkInterface::allInterfaces()) {
        auto flags = networkIf.flags();
        if (!flags.testFlag(QNetworkInterface::IsLoopBack) && flags.testFlag(QNetworkInterface::CanMulticast)) {
            bool isWireless = Tool::isWireless(networkIf.name().toStdString().data());
            if (!isWireless) {
                return  networkIf.hardwareAddress().toLower();
            }
        }
    }
    qCritical() << "can not get macaddress";
    return "";
}

QJsonArray WinSystemDetector::getSoftwareInfo()
{
    Q_D(WinSystemDetector);
    QJsonObject usedHistories = getSoftwareUsedHistory();
    qDebug() << "usedHistories" << usedHistories;
    QJsonArray softinfo = CommonSystemDetector::getSoftwareInfo();

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

    auto softwareFilters = getSoftwareFilters();
    for (auto &filter : softwareFilters) {
//        qDebug() << filter.id << filter.nameFilters;
        QJsonArray softwareList = d->querySoftware(filter);

        for (auto val : softwareList) {
            auto softObj = val.toObject();
            if (softObj.value("name").toString().isEmpty()) {
                continue;
            }
            auto binaryFilters = filter.binaryFilters;

            QJsonObject historyObj;
            // TODO: merge history
            for (auto &binaryFilter : binaryFilters) {
                QRegExp regx(binaryFilter);
                for (auto key : usedHistories.keys()) {
                    qDebug() << binaryFilter << key << regx.indexIn(key);
                    if (regx.indexIn(key) >= 0) {
                        historyObj.insert(key, usedHistories.value(key));
                    }
                }
            }
            softObj.insert("used_history", historyObj);
            softinfo.append(softObj);
        }
    }
    qDebug() << softinfo;
    return softinfo;
}

QSharedPointer<SystemInfo> WinSystemDetector::fetchSystemInfo()
{
    Q_D(WinSystemDetector);
    auto sysInfo = CommonSystemDetector::fetchSystemInfo();
    sysInfo->setSoftInfo(getSoftwareInfo());
    // Override mac addr
    sysInfo->setMacAddress(macAddress());
    if (Genuine::IsWindowsVistaOrGreater()) {
        sysInfo->setDiskInfo(Win::GetWmiObject()->QueryDiskInfo());
    } else {
        sysInfo->setDiskInfo(Win::GetWmiObject()->QueryXPDiskInfo());
    }
    sysInfo->setAdapterInfo(d->GetValidEthernetAddress());
    sysInfo->setExternInfo(queryExternInfo());

    return sysInfo;
}

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

void WinSystemDetectorPrivate::cacheSoftwareList()
{
    cacheProgramList = Win::SoftManager::List();
}

QJsonObject WinSystemDetectorPrivate::queryOSInformation()
{
    Q_Q(WinSystemDetector);

    OSVERSIONINFOEX info;
    ZeroMemory(&info, sizeof(OSVERSIONINFO));
    info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx((LPOSVERSIONINFOW)&info);

    auto majorVer = info.dwMajorVersion;
    auto minorVer = info.dwMinorVersion;

    auto CurrentVersionRegistryKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\";
    QSettings currentVersion(CurrentVersionRegistryKey, QSettings::NativeFormat);

    QString os_name = currentVersion.value("ProductName").toString();

    if (majorVer == 6 && minorVer == 2) {
        DWORD ver = NetGetVersion();
        majorVer = ver >> 16 ;
        minorVer = ver & 0x0000FFFF;
    }
    qDebug() << majorVer << minorVer;
    QString osVersion = QString("%1.%2").arg(majorVer).arg(minorVer);

    auto winOS = Genuine::GetWin32OperatingSystem();
    auto installDate = winOS.value("InstallDate").toDateTime();
    auto lastBootUpTime = winOS.value("LastBootUpTime").toDateTime();
    // Keep timestamp to second, because precision of GetTickCount
    auto lastStartupTime = (lastBootUpTime.toMSecsSinceEpoch() / 1000) * 1000;

    Genuine::LicenseInfo licenseInfo = Genuine::QueryGenuineInfo();

    QJsonObject osInfo;
    osInfo.insert("name", os_name);
    osInfo.insert("version", osVersion);
    osInfo.insert("category", "os");
    osInfo.insert("authorized", licenseInfo.IsGenuine);
    osInfo.insert("installation_time", QString("%1").arg(installDate.toMSecsSinceEpoch()));
    osInfo.insert("license", licenseInfo.toObject());

    auto lastStartupTimeStr = QString("%1").arg(lastStartupTime);
    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", lastStartupTimeStr);
        osHistories.append(obj);

        for (auto history : osHistories) {
            historyMap.insert(history.toObject().value("startup").toString(), 1);
        }
    } else {
        historyMap.insert(lastStartupTimeStr, 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);

    osInfo.insert("product_keys", QJsonArray{Genuine::GetWindowsKey()});

    return osInfo;
}

QJsonArray WinSystemDetectorPrivate::querySoftware(const SoftwareFilter &filter)
{
    QJsonArray matchSoftwareList;
    if (cacheProgramList.isEmpty()) {
        cacheProgramList = Win::SoftManager::List();
    }

    for (auto program : cacheProgramList) {
//        if (program.DisplayName.toLower().contains("wps")) {
//            qDebug() << program.DisplayName << filter.nameFilters;
//        }
        if (filter.matchName(program.DisplayName)) {
            qDebug() << "add" << program.DisplayName << program.InstallDate << program.key();;
            program.updateStatus(filter);
            matchSoftwareList.push_back(program.toObject());
        }
    }
    return matchSoftwareList;
}

QList<AdapterInfo> WinSystemDetectorPrivate::GetValidEthernetAddress() const
{
    auto adapterList = Win::GetEthernetAddress();

    QMap<QString, int> whiteList = {
        {"PCI", 1},
        {"Qualcomm", 1},
        {"Realtek", 1},
        {"Qualcomm", 1},
    };
    QList<AdapterInfo> whiteListMatch;

    for (auto adapter : adapterList) {
        for (auto desc : whiteList.keys()) {
            if (adapter.description.toLower().contains(desc.toLower())) {
                whiteListMatch.push_back(adapter);
            }
        }
    }
    if (whiteListMatch.size() > 0) {
        return whiteListMatch;
    }

    QMap<QString, int> macBlackList = {
        {"00:ff:aa:bb:cc:dd", 1}
    };
    QList<AdapterInfo> blackListMatch;
    for (auto adapter : adapterList) {
        for (auto mac : macBlackList.keys()) {
            if (!adapter.hardwareAddress.toLower().contains(mac.toLower())) {
                blackListMatch.push_back(adapter);
            }
        }
    }
    QMap<QString, int> blackList = {
        {"Microsoft Virtual", 1},
        {"Gateway NC Adapter", 1},
        {"VMware", 1}
    };
    QList<AdapterInfo> blackList2Match;
    for (auto adapter : blackListMatch) {
        for (auto desc : macBlackList.keys()) {
            if (!adapter.description.toLower().contains(desc.toLower())) {
                blackList2Match.push_back(adapter);
            }
        }
    }
    return blackList2Match;
}
