#include <QThread>

#include "network_device_detector.h"
#include "network_device_monitor.h"
#include "device_monitor.h"
#include "zconfservicebrowser.h"
#include "lpinfo_browser.h"
#include "snmpwalk_browser.h"
#include "common.h"
#include "connect.h"
#include "udev_devices.h"
#include "information_collector.h"

QString getValueBySnmpwalk(const QString &name, const QString &key)
{
    std::string sname = name.toStdString();
    std::string skey = key.toStdString();

    const char * cname = sname.c_str();
    const char * ckey = skey.c_str();

    std::vector<OidNode> oids = {
        { ckey },
    };


    std::vector<HostNode> hosts = {
        { cname, "public" },
    };

    std::vector<std::string> buf;

    if (::snmpwalkBrowser_initialize(oids) != STAT_SUCCESS) {
        return QString ("error");
    }
    int ans = ::snmpwalkBrowser_synchronous(buf, hosts, oids);
    if (ans != STAT_SUCCESS) {
        return QString("error");
    }
    if (!buf.size()) {
        return QString("error");
    }
    QString res =  QString(buf.at(0).c_str());

    if (res.contains("IpAddress")
     || res.contains("STRING")) {

        res = res.split(":").at(1);
        res.remove(0,1);
        res.remove("\"");
        return res;
    }
    return "";
}

QString getProtocolStrFromUri(const QString &uri)
{
    if (uri.contains("://")) {
        return uri.left(uri.indexOf("://"));
    }
    return "none";
}

// *****************************************************
// NetworkDeviceDetector start

NetworkDeviceDetector *NetworkDeviceDetector::getInstance()
{
    static NetworkDeviceDetector *instance = nullptr;
    if (nullptr == instance) {
        instance = new NetworkDeviceDetector();
    }
    return instance;
}

NetworkDeviceDetector::~NetworkDeviceDetector()
{

}

QList<DeviceInformation> NetworkDeviceDetector::getDeviceInfoFromHost(const QString &host, const int port, ProtocolType protocolType)
{
    QString hhost;
    int pport;
    if (host.contains(":")) {
        hhost = host.split(":").at(0);
        pport = host.split(":").at(1).toInt();
    }
    else {
        hhost = host;
        pport = port;
    }
    QList<DeviceInformation> deviceInfoList; 
    DeviceInformation deviceInfo;
    deviceInfo.connectType = ConnectType::InfoFrom_Invalid;
    // 先检测host能否成功连接，如果无法成功连接则不用继续

    bool connect = false;
    if (::try_connect(::qstringTochar(hhost), pport) != 0) {
        qDebug() << "try to connect 631...";
        if (::try_connect(::qstringTochar(hhost), 631) != 0) {
            qDebug() << "port 631 failed!";
        }
        else {
            connect = true;
            pport = 631;
        }

        qDebug() << "try to connect 9100...";
        if (::try_connect(::qstringTochar(hhost), 9100) != 0) {
            qDebug() << "port 9100 failed!";
        }
        else {
            connect = true;
            pport = 9100;
        }
    }
    else {
        connect = true;
    }
    // lpd
    // if (!::try_connect(::qstringTochar(hhost), 515)) {
    //     qDebug("Cannot connect to %s:%d!\n", hhost, 515);
    //     return deviceInfo;
    // }

    if (connect == false) {
        deviceInfoList.append(deviceInfo);
        return deviceInfoList;
    }
    NetworkDeviceDetector *p = NetworkDeviceDetector::getInstance();

#ifdef REMOTE_PRINTER
    if (p->probeIpp(deviceInfoList, hhost, pport)) {
        return deviceInfoList;
    }
#endif
    if (p->probe(deviceInfo, hhost, pport, protocolType)) {
        deviceInfoList.append(deviceInfo);
        return deviceInfoList;
    }
    deviceInfoList.append(deviceInfo);
    return deviceInfoList;
}
bool NetworkDeviceDetector::probe(DeviceInformation &deviceInfo, const QString host, const int port, ProtocolType protocolType)
{
    if (port == 631) {
        return false;
    }
    NetworkDeviceDetector *p = NetworkDeviceDetector::getInstance();
    deviceInfo.host = host;
    p->setUri(deviceInfo, protocolType);
    
    // 如果自动打印搜到了直接返回结果
    if (p->m_mpDevInfo.contains(deviceInfo.uri)) {
        deviceInfo = (p->m_mpDevInfo)[deviceInfo.uri];
        deviceInfo.connectType = ConnectType::InfoFrom_PRINTER_IP;
        return true;
    }
    deviceInfo.connectType = ConnectType::InfoFrom_PRINTER_IP;
    p->newDeviceGet(deviceInfo);
    if (deviceInfo.connectType == ConnectType::InfoFrom_Invalid) {
        qDebug() << "input host error!";
        return false;
    }
    return true;
}
bool NetworkDeviceDetector::probeIpp(QList<DeviceInformation> &deviceInfoList, const QString host, const int port) {
    if (port == 9100) {
        return false;
    }
    deviceInfoList.clear();
    QStringList list = LpinfoBrowser::findRemotePrinter(host, port);
    for (auto str : list) {
        DeviceInformation deviceInfo;
        deviceInfo.connectType = ConnectType::InfoFrom_REMOTE_IP;
        deviceInfo.uri = str;
        deviceInfo.ppdName =  "raw";
        deviceInfoList.append(deviceInfo);
    }
    if (deviceInfoList.size()) {
        return true;
    }
    return false;
}
NetworkDeviceDetector::NetworkDeviceDetector(/* args */)
{
    /**
     * @brief zconfserver循环事件
     * 
     */
    ZConfServiceBrowser *p_zonfBrowser = ZConfServiceBrowser::getInstance();
    connect(p_zonfBrowser, &ZConfServiceBrowser::serviceEntryAdded,
            this, &NetworkDeviceDetector::zconfPreSet,
            Qt::QueuedConnection);

    // LpinfoBrowser *p_lpinfoBrowser = LpinfoBrowser::getInstance();
    // connect(p_lpinfoBrowser, &LpinfoBrowser::findLpinfoDevice,
            // this, &NetworkDeviceDetector::lpinfoPreSet,
            // Qt::QueuedConnection);

    /**
     * @brief 只有收到start才开始自动检测设备
     * serviceType 为_printer._tcp ,只会搜索打印机
     */
    connect (this, &NetworkDeviceDetector::autoDetectStart, [=](){
        p_zonfBrowser->browse();
        // p_lpinfoBrowser->browse();
    });

    connect (this, &NetworkDeviceDetector::newDeviceSend,
             this, &NetworkDeviceDetector::newDeviceGet,
             Qt::QueuedConnection);
}

DetectResult NetworkDeviceDetector::detectHost(DeviceInformation &deviceInfo)
{
    if (deviceInfo.networkNode.isEmpty())
        return DetectResult::DETECT_ERROR;

    QString res = ::getValueBySnmpwalk(deviceInfo.networkNode + ".local", "1.3.6.1.2.1.4.20.1.1");
    if (res.isEmpty()) {
        return DetectResult::DETECT_ERROR;
    }

    deviceInfo.host = res;
    return DetectResult::DETECT_OK;
}

DetectResult NetworkDeviceDetector::detectNetWorkNode(DeviceInformation &deviceInfo)
{
    deviceInfo.networkNode = ::getValueBySnmpwalk(deviceInfo.host, "1.3.6.1.2.1.1.5");
    if (deviceInfo.networkNode.isEmpty()) {
        return DetectResult::DETECT_ERROR;
    }
    return DetectResult::DETECT_OK;
}

/**
 * @brief TODO:只负责lpinfo 和 ippfind的就可以了
 * 后面可以使用正则识别uri，更准确
 * 
 */
DetectResult NetworkDeviceDetector::detectUri(DeviceInformation &deviceInfo)
{
    return DetectResult::DETECT_ERROR;
}

DetectResult NetworkDeviceDetector::detectProtocolType(DeviceInformation &deviceInfo)
{

    QString protocolStr = ::getProtocolStrFromUri(deviceInfo.uri);
    deviceInfo.protocolType = ::protocolMap[protocolStr];
    return DetectResult::DETECT_ERROR;
}

DetectResult NetworkDeviceDetector::detectVendorAndModel(DeviceInformation &deviceInfo)
{
    // 直接在1.3.6.1.2.1.25.3.2.1.3有问题，暂时使用cups的方法获取
    // QString res = ::getValueBySnmpwalk(deviceInfo.host, "1.3.6.1.2.1.25.3.2.1.3");
#if 1
    QString res = ::getRetFromCommand(QStringList{"/usr/lib/cups/backend/snmp", deviceInfo.host});
#else
    // TODO： 通过1.3.6.1.4.1.x 获取打印机信息
    
#endif
    if (res.isEmpty()) {
        // qDebug () << deviceInfo.host << " not found!";
        return DetectResult::DETECT_ERROR;
    }
    QStringList resList;
    resList = res.split("\"");
    if (resList.size() >= 6) {
        res = resList.at(5);
    }
    else {
        return DetectResult::DETECT_ERROR;
    }
    device_id_s id;
    parse_device_id(QTC(res), &id);
    if (id.mfg.isEmpty() || id.mdl.isEmpty()) {
        return DetectResult::DETECT_ERROR;
    }
    deviceInfo.vendor = id.mfg;
    deviceInfo.model = id.mdl;
    return DetectResult::DETECT_OK;
}

DetectResult NetworkDeviceDetector::detectUUID(DeviceInformation &deviceInfo)
{
    QString uuid;
    switch (deviceInfo.protocolType)
    {
    /**
     * @brief dnssd 协议的打印机可以从uri中获取uuid
     * 
     */
    case (ProtocolType::DNSSD):
        uuid = deviceInfo.uri.split("=").at(1);
        if (!uuid.isEmpty()) {
            return DetectResult::DETECT_OK;
        }
    /**
     * @brief TODO：其他协议可以从avahi中读取
     * 
     */
    default:
        break;
    }
    return DetectResult::DETECT_ERROR;
}

DetectResult NetworkDeviceDetector::detectSerial(DeviceInformation &deviceInfo)
{
    deviceInfo.serial = ::getValueBySnmpwalk(deviceInfo.host, "1.3.6.1.2.1.43.5.1.1.17");
    if (deviceInfo.serial.isEmpty()) {
        return DetectResult::DETECT_ERROR;
    }
    return DetectResult::DETECT_OK;
}

/**
 * @brief 从uri解析网络节点
 * 
 * @param deviceInfo 
 * @return SetResult 
 */
SetResult NetworkDeviceDetector::setNetWorkNode(DeviceInformation &deviceInfo)
{
    if (deviceInfo.uri.isEmpty() || deviceInfo.protocolType != ProtocolType::LPD) {
        return SetResult::SET_ERROR;
    }
    
    QString res = deviceInfo.uri;
    res.remove("lpd://");
    
    res = res.split("/").at(0);
    if (res.isEmpty()) {
        return SetResult::SET_ERROR;
    }

    deviceInfo.networkNode = res;
    return SetResult::SET_OK;
}

/**
 * @brief TODO: http setUri
 * 
 * @param deviceInfo 
 * @param protocolType 
 * @return SetResult 
 */
SetResult NetworkDeviceDetector::setUri(DeviceInformation &deviceInfo, const ProtocolType &protocolType)
{
    // uri 存在则不用重复设置
    // if (!deviceInfo.uri.isEmpty()) {
    //     return SetResult::SET_ERROR;
    // }

    // host 为空，可能是获取的网络节点
    if (deviceInfo.host.isEmpty()) {

        // 如果网络节点也是空的，则无法获取
        if (deviceInfo.networkNode.isEmpty()) {
            return SetResult::SET_ERROR;
        }

        // 无法检测到host，error
        /**
         * TODO: 网络节点构造uri
         * 
         */
        if (this->detectHost(deviceInfo) == DetectResult::DETECT_ERROR) {
            return SetResult::SET_ERROR;
        }
    }

    switch (protocolType)
    {
    case (ProtocolType::SOCKET) :
        deviceInfo.uri = QString("socket://") + deviceInfo.host + (":9100");
        break;
    case (ProtocolType::HTTP) :
        deviceInfo.uri = QString("http://") + deviceInfo.host;
        break;
    case (ProtocolType::IPP) :
        deviceInfo.uri = QString("ipp://") + deviceInfo.host +":631/ipp/print";
        break;
    default:
        return SetResult::SET_ERROR;
    }
    deviceInfo.protocolType = protocolType;
    return SetResult::SET_OK;
}

SetResult NetworkDeviceDetector::setName(DeviceInformation &deviceInfo)
{
    if (!deviceInfo.name.isEmpty())
        return SetResult::SET_ERROR;
    if (!deviceInfo.networkNode.isEmpty()) {
        deviceInfo.name = deviceInfo.networkNode;
        return SetResult::SET_OK;
    }
    return SetResult::SET_ERROR;
}

DeviceInformation NetworkDeviceDetector::newDeviceGet(DeviceInformation &deviceInfo)
{
    // qDebug() << "newDeviceGet!!!";
    // qDebug() << deviceInfo.uri;
    if (deviceInfo.uri.isEmpty()) {
        if (this->detectUri(deviceInfo) == DetectResult::DETECT_ERROR)
            return DeviceInformation();
    }
    
    if (deviceInfo.host.isEmpty())
        this->detectHost(deviceInfo);
    else if (deviceInfo.networkNode.isEmpty())
        this->detectNetWorkNode(deviceInfo);

    if (deviceInfo.host == "error" || deviceInfo.networkNode == "error") {
        deviceInfo.connectType = ConnectType::InfoFrom_Invalid;
        return DeviceInformation();
    }

    if (deviceInfo.host.isEmpty() && deviceInfo.networkNode.isEmpty()) {
        deviceInfo.connectType = ConnectType::InfoFrom_Invalid;
        return DeviceInformation();
    }

    if (!ipIsValid(deviceInfo.host)) {
        deviceInfo.connectType = ConnectType::InfoFrom_Invalid;
        return DeviceInformation();
    }
    
    this->detectVendorAndModel (deviceInfo);
    this->detectUUID           (deviceInfo);
    this->detectSerial         (deviceInfo);

    if (deviceInfo.uri.isEmpty()
     || deviceInfo.vendor.isEmpty()
     || deviceInfo.model.isEmpty()  ) {

        deviceInfo.connectType = ConnectType::InfoFrom_Invalid;
    }

    this->setName(deviceInfo);

    switch (deviceInfo.connectType)
    {
    case (ConnectType::InfoFrom_Invalid):
    case (ConnectType::InfoFrom_USB):
        return DeviceInformation();

    case (ConnectType::InfoFrom_NETWORK_DETECT):
        if (!m_mpDevInfo.contains(deviceInfo.uri)){
            m_mpDevInfo.insert(deviceInfo.uri, deviceInfo);
            InformationCollector::getInstance().addMessage(QString("network printer device add: ") + devInfoPrint(deviceInfo));
            emit NetworkDeviceMonitor::getInstance()->findNetworkConnect(deviceInfo);
        }
        return DeviceInformation();
    case (ConnectType::InfoFrom_PRINTER_IP):
        return deviceInfo;
    }
    
    return DeviceInformation();
}

void NetworkDeviceDetector::lpinfoPreSet(const QString &uri) {
    DeviceInformation deviceInfo;
    deviceInfo.connectType = ConnectType::InfoFrom_NETWORK_DETECT;
    deviceInfo.uri = uri;
    this->detectProtocolType(deviceInfo);
    if (deviceInfo.protocolType == ProtocolType::LPD
        && this->setNetWorkNode(deviceInfo) == SetResult::SET_OK) {
        emit this->newDeviceSend(deviceInfo);
    }
    return ;
}

void NetworkDeviceDetector::zconfPreSet(QString name) {
    DeviceInformation deviceInfo;
    deviceInfo.connectType = ConnectType::InfoFrom_NETWORK_DETECT;
    deviceInfo.host = ZConfServiceBrowser::getInstance()->serviceEntry(name).ip;

    if (this->setUri(deviceInfo, ProtocolType::SOCKET) == SetResult::SET_OK)
        emit this->newDeviceSend(deviceInfo);
    if (this->setUri(deviceInfo, ProtocolType::IPP) == SetResult::SET_OK)
        emit this->newDeviceSend(deviceInfo);
    if (this->setUri(deviceInfo, ProtocolType::HTTP) == SetResult::SET_OK)
        emit this->newDeviceSend(deviceInfo);
    return ;
}

// NetworkDeviceDetector end