#include "device_bus/monitor/UdevDeviceMonitor.h"

#if defined(__linux__)

#include <unistd.h>
#include <vector>
#include <QRegularExpression>

#include <QFileInfo>

#include "utils/logs/logging.h"

namespace softbus::device_bus::monitor
{
namespace {

bool isWhitelistedSerialDevnode(const QString& devnode)
{
    if (devnode.startsWith(QStringLiteral("/dev/ttyUSB"))) return true;
    if (devnode.startsWith(QStringLiteral("/dev/ttyACM"))) return true;

    // Board UART allowlist: ttyS1 ~ ttyS6
    static const QRegularExpression kTtySRe(QStringLiteral("^/dev/ttyS(\\d+)$"));
    const auto m = kTtySRe.match(devnode);
    if (m.hasMatch()) {
        bool ok = false;
        const int n = m.captured(1).toInt(&ok);
        if (ok && n >= 1 && n <= 6) return true;
    }
    return false;
}

bool isConsoleLikeTty(const QString& devnode)
{
    if (devnode == QStringLiteral("/dev/console")) return true;
    if (devnode == QStringLiteral("/dev/tty")) return true;
    if (devnode == QStringLiteral("/dev/ptmx")) return true;
    if (devnode.startsWith(QStringLiteral("/dev/pts/"))) return true;

    // Linux VT consoles: /dev/tty0.../dev/tty63 (without 'S')
    static const QRegularExpression kVtRe(QStringLiteral("^/dev/tty(\\d+)$"));
    return kVtRe.match(devnode).hasMatch();
}

} // namespace

UdevDeviceMonitor::UdevDeviceMonitor(QObject* parent)
    : IDeviceMonitor(parent)
{
}

UdevDeviceMonitor::~UdevDeviceMonitor()
{
    stop();
}

bool UdevDeviceMonitor::start()
{
    if (m_mon) return true;

    m_udev = udev_new();
    if (!m_udev) {
        LOG_ERROR() << "UdevDeviceMonitor: udev_new failed";
        return false;
    }

    m_mon = udev_monitor_new_from_netlink(m_udev, "udev");
    if (!m_mon) {
        LOG_ERROR() << "UdevDeviceMonitor: udev_monitor_new_from_netlink failed";
        udev_unref(m_udev);
        m_udev = nullptr;
        return false;
    }

    // Filter subsystems
    udev_monitor_filter_add_match_subsystem_devtype(m_mon, "tty", nullptr);
    udev_monitor_filter_add_match_subsystem_devtype(m_mon, "usb", nullptr);
    udev_monitor_filter_add_match_subsystem_devtype(m_mon, "block", nullptr);

    if (udev_monitor_enable_receiving(m_mon) != 0) {
        LOG_ERROR() << "UdevDeviceMonitor: enable_receiving failed";
        stop();
        return false;
    }

    m_fd = udev_monitor_get_fd(m_mon);
    if (m_fd < 0) {
        LOG_ERROR() << "UdevDeviceMonitor: monitor fd invalid";
        stop();
        return false;
    }

    m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
    connect(m_notifier, &QSocketNotifier::activated, this, [this]() { onReadable(); });

    LOG_INFO() << "UdevDeviceMonitor started fd=" << m_fd;
    return true;
}

void UdevDeviceMonitor::stop()
{
    if (m_notifier) {
        m_notifier->setEnabled(false);
        m_notifier->deleteLater();
        m_notifier = nullptr;
    }
    m_fd = -1;

    if (m_mon) {
        udev_monitor_unref(m_mon);
        m_mon = nullptr;
    }
    if (m_udev) {
        udev_unref(m_udev);
        m_udev = nullptr;
    }
}

void UdevDeviceMonitor::onReadable()
{
    if (!m_mon) return;

    while (true) {
        struct udev_device* dev = udev_monitor_receive_device(m_mon);
        if (!dev) break;

        const char* action = udev_device_get_action(dev);
        DeviceEvent ev = toEvent(dev, action);

        if (!shouldKeepEvent(ev)) {
            udev_device_unref(dev);
            continue;
        }

        emitEvent(ev);
        udev_device_unref(dev);
    }
}

std::vector<DeviceEvent> UdevDeviceMonitor::snapshotExisting()
{
    std::vector<DeviceEvent> out;
    if (!m_udev) {
        // Snapshot can be requested before start(); create temp context.
        m_udev = udev_new();
        if (!m_udev) {
            LOG_ERROR() << "UdevDeviceMonitor: snapshot udev_new failed";
            return out;
        }
    }

    udev_enumerate* en = udev_enumerate_new(m_udev);
    if (!en) {
        return out;
    }

    udev_enumerate_add_match_subsystem(en, "tty");
    udev_enumerate_add_match_subsystem(en, "usb");
    udev_enumerate_add_match_subsystem(en, "block");
    if (udev_enumerate_scan_devices(en) != 0) {
        udev_enumerate_unref(en);
        return out;
    }

    udev_list_entry* devices = udev_enumerate_get_list_entry(en);
    udev_list_entry* entry = nullptr;
    udev_list_entry_foreach(entry, devices) {
        const char* path = udev_list_entry_get_name(entry);
        if (!path) continue;

        udev_device* dev = udev_device_new_from_syspath(m_udev, path);
        if (!dev) continue;

        DeviceEvent ev = toEvent(dev, "add");
        if (shouldKeepEvent(ev)) {
            out.push_back(ev);
        }

        udev_device_unref(dev);
    }

    udev_enumerate_unref(en);
    return out;
}

bool UdevDeviceMonitor::shouldKeepEvent(const DeviceEvent& ev) const
{
    // Drop unknown action (usually noise / incomplete events)
    if (ev.action == DeviceEventAction::Unknown) {
        return false;
    }

    // Serial tty: keep only business serial nodes.
    if (ev.subsystem == QStringLiteral("tty")) {
        if (ev.devnode.isEmpty()) {
            LOG_ERROR() << "UdevDeviceMonitor: devnode is empty" << ev.devnode;
            return false;
        }
        if (isConsoleLikeTty(ev.devnode)) {
            LOG_INFO() << "UdevDeviceMonitor: console like tty" << ev.devnode;
            return false;
        }
        if (!isWhitelistedSerialDevnode(ev.devnode)) {
            LOG_INFO() << "UdevDeviceMonitor: not whitelisted serial devnode" << ev.devnode;
            return false;
        }
    }

    // USB: only keep usb_device (drop interface-level noise)
    if (ev.subsystem == QStringLiteral("usb")) {
        const QString devtype = ev.props.value(QStringLiteral("DEVTYPE"));
        if (!devtype.isEmpty() && devtype != QStringLiteral("usb_device")) {
            return false;
        }

        // Drop most "change" spam for usb (keep add/remove)
        if (ev.action == DeviceEventAction::Change) {
            return false;
        }

        // If it carries no stable identifiers, it's not useful
        if (ev.devnode.isEmpty() && ev.vid.isEmpty() && ev.pid.isEmpty() && ev.serial.isEmpty()) {
            return false;
        }

        // Drop Linux root hubs/controller pseudo devices.
        if (ev.vid == QStringLiteral("1d6b")) {
            return false;
        }
        const QString modelLower = ev.model.toLower();
        if (modelLower.contains(QStringLiteral("hub")) ||
            modelLower.contains(QStringLiteral("host_controller")) ||
            modelLower.contains(QStringLiteral("xhci"))) {
            return false;
        }
    }

    // Block: keep only usb-backed storage add/remove, drop non-storage blocks.
    if (ev.subsystem == QStringLiteral("block")) {
        if (ev.type != DeviceEventType::Storage) {
            return false;
        }
        if (ev.action == DeviceEventAction::Change) {
            return false;
        }
    }

    return true;
}

QString UdevDeviceMonitor::getProp(struct udev_device* dev, const char* key)
{
    if (!dev || !key) return {};
    const char* v = udev_device_get_property_value(dev, key);
    return v ? QString::fromUtf8(v) : QString();
}

DeviceEvent UdevDeviceMonitor::toEvent(struct udev_device* dev, const char* action)
{
    DeviceEvent ev;
    ev.timestamp = QDateTime::currentDateTimeUtc();

    const char* subsys = udev_device_get_subsystem(dev);
    if (subsys) ev.subsystem = QString::fromUtf8(subsys);

    const char* devtype = udev_device_get_devtype(dev);
    if (devtype) ev.props.insert(QStringLiteral("DEVTYPE"), QString::fromUtf8(devtype));

    const char* syspath = udev_device_get_syspath(dev);
    if (syspath) ev.props.insert(QStringLiteral("SYSPATH"), QString::fromUtf8(syspath));

    const char* devnode = udev_device_get_devnode(dev);
    if (devnode) ev.devnode = QString::fromUtf8(devnode);

    const QString act = action ? QString::fromUtf8(action) : QString();
    if (act == QStringLiteral("add")) ev.action = DeviceEventAction::Add;
    else if (act == QStringLiteral("remove")) ev.action = DeviceEventAction::Remove;
    else if (act == QStringLiteral("change")) ev.action = DeviceEventAction::Change;
    else ev.action = DeviceEventAction::Unknown;

    if (ev.subsystem == QStringLiteral("tty")) {
        ev.type = DeviceEventType::Serial;
        // devnode like /dev/ttyUSB0
    } else if (ev.subsystem == QStringLiteral("usb")) {
        ev.type = DeviceEventType::Usb;
    } else if (ev.subsystem == QStringLiteral("block")) {
        // Prefer only usb-backed block devices
        const QString idBus = getProp(dev, "ID_BUS");
        if (idBus == QStringLiteral("usb")) {
            ev.type = DeviceEventType::Storage;
        } else {
            // check parent chain for usb
            struct udev_device* p = udev_device_get_parent_with_subsystem_devtype(dev, "usb", nullptr);
            if (p) {
                ev.type = DeviceEventType::Storage;
            } else {
                ev.type = DeviceEventType::Unknown;
            }
        }
    } else {
        ev.type = DeviceEventType::Unknown;
    }

    // Common USB-ish props (best-effort)
    ev.vid = getProp(dev, "ID_VENDOR_ID");
    ev.pid = getProp(dev, "ID_MODEL_ID");
    ev.serial = getProp(dev, "ID_SERIAL_SHORT");
    ev.model = getProp(dev, "ID_MODEL");
    ev.vendor = getProp(dev, "ID_VENDOR");

    // Fallback to sysfs attributes for usb subsystem
    if (ev.vid.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
        const char* a = udev_device_get_sysattr_value(dev, "idVendor");
        if (a) ev.vid = QString::fromUtf8(a);
    }
    if (ev.pid.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
        const char* a = udev_device_get_sysattr_value(dev, "idProduct");
        if (a) ev.pid = QString::fromUtf8(a);
    }
    if (ev.serial.isEmpty() && ev.subsystem == QStringLiteral("usb")) {
        const char* a = udev_device_get_sysattr_value(dev, "serial");
        if (a) ev.serial = QString::fromUtf8(a);
    }

    if (!ev.devnode.isEmpty()) {
        ev.props.insert(QStringLiteral("DEVNAME"), ev.devnode);
    }
    if (!ev.subsystem.isEmpty()) {
        ev.props.insert(QStringLiteral("SUBSYSTEM"), ev.subsystem);
    }

    return ev;
}

} // namespace softbus::device_bus::monitor

#endif // __linux__

