#include "device_bus/monitor/NetlinkDeviceMonitor.h"

#if defined(__linux__)

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>

#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

#include "utils/logs/logging.h"

namespace softbus::device_bus::monitor
{

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

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

bool NetlinkDeviceMonitor::start()
{
    if (m_sock >= 0) return true;

    m_sock = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (m_sock < 0) {
        LOG_ERROR() << "NetlinkDeviceMonitor: socket failed errno=" << errno;
        return false;
    }

    sockaddr_nl addr{};
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_LINK;
    if (::bind(m_sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
        LOG_ERROR() << "NetlinkDeviceMonitor: bind failed errno=" << errno;
        stop();
        return false;
    }

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

    LOG_INFO() << "NetlinkDeviceMonitor started sock=" << m_sock;
    return true;
}

void NetlinkDeviceMonitor::stop()
{
    if (m_notifier) {
        m_notifier->setEnabled(false);
        m_notifier->deleteLater();
        m_notifier = nullptr;
    }
    if (m_sock >= 0) {
        ::close(m_sock);
        m_sock = -1;
    }
    m_seenIfIndex.clear();
    m_lastIfNameByIndex.clear();
    m_lastLinkUpByIndex.clear();
}

void NetlinkDeviceMonitor::onReadable()
{
    if (m_sock < 0) return;

    alignas(nlmsghdr) char buf[8192];
    const ssize_t len = ::recv(m_sock, buf, sizeof(buf), 0);
    if (len <= 0) {
        return;
    }

    int remaining = static_cast<int>(len);
    for (nlmsghdr* nh = reinterpret_cast<nlmsghdr*>(buf);
         NLMSG_OK(nh, static_cast<unsigned int>(remaining));
         nh = NLMSG_NEXT(nh, remaining)) {

        if (nh->nlmsg_type == NLMSG_DONE || nh->nlmsg_type == NLMSG_ERROR) {
            continue;
        }

        if (nh->nlmsg_type != RTM_NEWLINK && nh->nlmsg_type != RTM_DELLINK) {
            continue;
        }

        auto* ifi = reinterpret_cast<ifinfomsg*>(NLMSG_DATA(nh));
        const int ifindex = ifi ? ifi->ifi_index : -1;

        QString ifname;
        bool linkUp = false;

        // Parse attributes
        int attrLen = static_cast<int>(nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));
        for (rtattr* rta = IFLA_RTA(ifi); RTA_OK(rta, attrLen); rta = RTA_NEXT(rta, attrLen)) {
            if (rta->rta_type == IFLA_IFNAME) {
                ifname = QString::fromUtf8(reinterpret_cast<const char*>(RTA_DATA(rta)));
            }
        }

        // Link state (best-effort)
        if (ifi) {
            linkUp = (ifi->ifi_flags & IFF_RUNNING) != 0;
        }

        DeviceEvent ev;
        ev.timestamp = QDateTime::currentDateTimeUtc();
        ev.type = DeviceEventType::NetInterface;
        ev.subsystem = QStringLiteral("net");
        ev.ifindex = ifindex;
        ev.ifname = ifname;
        ev.linkUp = linkUp;

        if (nh->nlmsg_type == RTM_DELLINK) {
            ev.action = DeviceEventAction::Remove;
            m_seenIfIndex.remove(ifindex);
            m_lastIfNameByIndex.remove(ifindex);
            m_lastLinkUpByIndex.remove(ifindex);
        } else {
            // NEWLINK can be add or change; treat first time as add
            if (!m_seenIfIndex.contains(ifindex)) {
                ev.action = DeviceEventAction::Add;
                m_seenIfIndex.insert(ifindex);
                m_lastIfNameByIndex.insert(ifindex, ifname);
                m_lastLinkUpByIndex.insert(ifindex, linkUp);
            } else {
                const QString lastName = m_lastIfNameByIndex.value(ifindex);
                const bool lastLink = m_lastLinkUpByIndex.value(ifindex, linkUp);

                // Drop pure spam: only emit change if name or link state changes
                if (lastName == ifname && lastLink == linkUp) {
                    continue;
                }

                ev.action = DeviceEventAction::Change;
                m_lastIfNameByIndex.insert(ifindex, ifname);
                m_lastLinkUpByIndex.insert(ifindex, linkUp);
            }
        }

        emitEvent(ev);
    }
}

std::vector<DeviceEvent> NetlinkDeviceMonitor::snapshotExisting()
{
    std::vector<DeviceEvent> out;

    ifaddrs* ifaddr = nullptr;
    if (::getifaddrs(&ifaddr) != 0) {
        return out;
    }

    QSet<QString> seen;
    for (ifaddrs* it = ifaddr; it != nullptr; it = it->ifa_next) {
        if (!it->ifa_name) continue;

        const QString ifname = QString::fromUtf8(it->ifa_name);
        if (ifname.isEmpty() || seen.contains(ifname)) continue;
        seen.insert(ifname);

        DeviceEvent ev;
        ev.timestamp = QDateTime::currentDateTimeUtc();
        ev.type = DeviceEventType::NetInterface;
        ev.action = DeviceEventAction::Add;
        ev.subsystem = QStringLiteral("net");
        ev.ifname = ifname;
        ev.ifindex = ::if_nametoindex(it->ifa_name);
        ev.linkUp = (it->ifa_flags & IFF_RUNNING) != 0;
        out.push_back(ev);
    }

    ::freeifaddrs(ifaddr);
    return out;
}

} // namespace softbus::device_bus::monitor

#endif // __linux__

