#include "DeviceListModel.h"
#include "BoostLog.h"
#include "DeviceConnection.h"
#include <QNetworkDatagram>
#include <QNetworkInterface>
#include <QTimer>
#include <QUdpSocket>
#include <boost/json/parse.hpp>

static QHostAddress calculateNetworkAddress(const QHostAddress &ip, const QHostAddress &netmask) {
    quint32 ipInt = ip.toIPv4Address();
    quint32 netmaskInt = netmask.toIPv4Address();
    quint32 networkInt = ipInt & netmaskInt;
    return QHostAddress(networkInt);
}

static bool isSameNetwork(const QHostAddress &ip1, const QHostAddress &netmask1, const QHostAddress &ip2) {
    QHostAddress network1 = calculateNetworkAddress(ip1, netmask1);
    QHostAddress network2 = calculateNetworkAddress(ip2, netmask1); // Using the same netmask for the target IP
    return network1 == network2;
}

static bool isTargetIPInSameNetwork(const QHostAddress &targetIP) {
    QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &iface : interfaces) {
        QList<QNetworkAddressEntry> entries = iface.addressEntries();
        for (const QNetworkAddressEntry &entry : entries) {
            QHostAddress ip = entry.ip();
            QHostAddress netmask = entry.netmask();

            if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
                bool sameNetwork = isSameNetwork(ip, netmask, targetIP);
                if (sameNetwork) {
                    return true;
                }
            }
        }
    }
    return false;
}

DeviceListModel::DeviceListModel(QObject *parent) : QAbstractListModel{parent} {
    m_broadcastSocket = new QUdpSocket(this);
}

int DeviceListModel::rowCount(const QModelIndex &parent) const {
    return m_devices.size();
}

QVariant DeviceListModel::data(const QModelIndex &index, int role) const {
    QVariant ret;
    auto row = index.row();
    if (row >= m_devices.size()) {
        return ret;
    }
    auto info = m_devices.at(row)->infomation();
    if (role == DeviceIdRole) {
        ret = info.deviceId;
    } else if (role == FirmwareVersionRole) {
        ret = info.firmwareVersion;
    } else if (role == SoftwareVersionRole) {
        ret = info.softwareVersion;
    } else if (role == IpRole) {
        ret = info.ip;
    } else if (role == OnlineStatusRole) {
        ret = m_devices.at(row)->isConnected();
    }
    return ret;
}

QHash<int, QByteArray> DeviceListModel::roleNames() const {
    QHash<int, QByteArray> roleNames;
    roleNames.insert(DeviceIdRole, "deviceId");
    roleNames.insert(FirmwareVersionRole, "firmwareVersion");
    roleNames.insert(SoftwareVersionRole, "softwareVersion");
    roleNames.insert(IpRole, "ip");
    roleNames.insert(OnlineStatusRole, "onlineStatus");
    return roleNames;
}

QVariantMap DeviceListModel::get(int index) const {
    QVariantMap map;
    if (index >= 0 && index < m_devices.size()) {
        auto info = m_devices.at(index)->infomation();
        map["firmwareVersion"] = info.firmwareVersion;
        map["softwareVersion"] = info.softwareVersion;
        map["deviceId"] = info.deviceId;
        map["ip"] = info.ip;
        map["onlineStatus"] = m_devices.at(index)->isConnected();
    }
    return map;
}

std::shared_ptr<DeviceConnection> DeviceListModel::device(int index) {
    std::shared_ptr<DeviceConnection> ret;
    if (index < m_devices.size()) {
        ret = m_devices.at(index);
    }
    return ret;
}

bool DeviceListModel::deviceConnected(int index) {
    bool ret = false;
    if (index >= 0 && index < m_devices.size()) {
        ret = m_devices.at(index)->isConnected();
    }
    return ret;
}

void DeviceListModel::startSearchDevice() {
    if (m_timerId >= 0) {
        LOG(error) << "app is searching device.";
        return;
    }
    if (!m_udpSockets.empty()) {
        for (auto &socket : m_udpSockets) {
            socket->deleteLater();
        }
        m_udpSockets.clear();
    }
    beginResetModel();
    m_devices.clear();
    endResetModel();
    auto interfaces = QNetworkInterface::allInterfaces();
    for (auto &interface : interfaces) {
        if (interface.flags() & QNetworkInterface::IsLoopBack) continue;
        if (interface.flags() & QNetworkInterface::IsUp && interface.flags() & QNetworkInterface::IsRunning) {
            const QList<QNetworkAddressEntry> entries = interface.addressEntries();
            for (const QNetworkAddressEntry &entry : entries) {
                if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) continue;
                QUdpSocket *socket = new QUdpSocket(this);
                if (socket->bind(entry.ip(), ListenPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
                    connect(socket, &QUdpSocket::readyRead, this, &DeviceListModel::onDeviceReplyReadyRead);
                    m_udpSockets.push_back(socket);
                    LOG(info) << "Listening on " << entry.ip().toString().toStdString() << ":" << ListenPort;
                } else {
                    LOG(error) << "Failed to bind UDP socket on" << entry.ip().toString().toStdString() << ":"
                               << ListenPort;
                    delete socket;
                }
            }
        }
    }
    if (!m_udpSockets.empty()) {
        m_retries = 0;
        emit searchProgressChanged();
        m_timerId = startTimer(1000);
        emit isSearchingChanged();
    }
}

bool DeviceListModel::isSearching() const {
    return m_timerId >= 0;
}

float DeviceListModel::searchProgress() const {
    return static_cast<float>(m_retries) * 100 / RetryCount;
}

void DeviceListModel::onDeviceConnected() {
    auto device = dynamic_cast<DeviceConnection *>(sender());
    auto iterator =
        std::find_if(m_devices.cbegin(), m_devices.cend(),
                     [device](const std::shared_ptr<DeviceConnection> &item) { return item.get() == device; });
    if (iterator != m_devices.cend()) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        QList<int> roles;
#else
        QVector<int> roles;
#endif
        roles << OnlineStatusRole;
        int row = std::distance(m_devices.cbegin(), iterator);
        emit dataChanged(index(row), index(row), roles);
        LOG(info) << "device " << row << " connected.";
    }
}

void DeviceListModel::onDeviceDisconnected() {
    auto device = dynamic_cast<DeviceConnection *>(sender());
    auto iterator =
        std::find_if(m_devices.cbegin(), m_devices.cend(),
                     [device](const std::shared_ptr<DeviceConnection> &item) { return item.get() == device; });
    if (iterator != m_devices.cend()) {
        int row = std::distance(m_devices.cbegin(), iterator);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        QList<int> roles;
#else
        QVector<int> roles;
#endif
        roles << OnlineStatusRole;
        emit dataChanged(index(row), index(row), roles);
        LOG(info) << "device " << row << " disconnected.";
    }
}

void DeviceListModel::onDeviceReplyReadyRead() {
    auto udp = dynamic_cast<QUdpSocket *>(sender());
    while (udp->hasPendingDatagrams()) {
        QNetworkDatagram datagram = udp->receiveDatagram();
        if (isTargetIPInSameNetwork(datagram.senderAddress())) { // 设备开机时可能会广播一个 192.168.2.10 的地址
            auto replyVale = boost::json::parse(datagram.data().toStdString());
            auto &reply = replyVale.as_object();
            DeviceConnection::Infomation device;
            if (reply.contains("devid")) {
                device.deviceId = QString::fromStdString(std::string(reply.at("devid").as_string()));
            }
            if (reply.contains("fw_ver")) {
                device.firmwareVersion = QString::fromStdString(std::string(reply.at("fw_ver").as_string()));
            }
            if (reply.contains("sw_ver")) {
                device.softwareVersion = QString::fromStdString(std::string(reply.at("sw_ver").as_string()));
            }
            if (!device.softwareVersion.startsWith("RD_T009")) continue; // 其它设备不予显示
            device.ip = datagram.senderAddress().toString();

            auto iterator = std::find_if(m_devices.cbegin(), m_devices.cend(),
                                         [&device](const std::shared_ptr<DeviceConnection> &item) {
                                             return item->infomation().deviceId == device.deviceId;
                                         });
            if (iterator == m_devices.cend()) {
                if (m_timerId >= 0) { // 只有在搜索设备的过程中,才加入新设备
                    auto connection = std::shared_ptr<DeviceConnection>(
                        new DeviceConnection(), [](DeviceConnection *self) { self->deleteLater(); });
                    connect(connection.get(), &DeviceConnection::connected, this, &DeviceListModel::onDeviceConnected);
                    connect(connection.get(), &DeviceConnection::disconnected, this,
                            &DeviceListModel::onDeviceDisconnected);
                    connection->connect(device);
                    beginInsertRows(QModelIndex(), m_devices.size(), m_devices.size());
                    m_devices.push_back(connection);
                    endInsertRows();
                }
            } else {
                auto info = (*iterator)->infomation();
                if ((m_timerId < 0) ||
                    ((info.ip == DeviceConnection::WirelessAddress) && (device.ip != DeviceConnection::WirelessAddress)) ||
                    ((device.ip != DeviceConnection::WirelessAddress) && (device.ip != info.ip))) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                    QList<int> roles;
#else
                    QVector<int> roles;
#endif
                    roles << OnlineStatusRole << IpRole;
                    int row = std::distance(m_devices.cbegin(), iterator);
                    emit dataChanged(index(row), index(row), roles);
                    (*iterator)->connect(device);
                    LOG(info) << "device: " << device.deviceId.toStdString() << " back online";
                }
            }
        }
        LOG(info) << datagram.destinationAddress().toString().toStdString() << ":" << datagram.destinationPort()
                  << " received datagram from " << datagram.senderAddress().toString().toStdString() << ":"
                  << datagram.senderPort() << ", Data: " << datagram.data().toStdString()
                  << ", same network: " << isTargetIPInSameNetwork(datagram.senderAddress());
    }
}

void DeviceListModel::broadcast() {
    if (m_udpSockets.empty()) return;
    QByteArray datagram = "FACEPASS_V2";
    auto interfaces = QNetworkInterface::allInterfaces();
    for (auto &interface : interfaces) {
        if (interface.flags() & QNetworkInterface::IsLoopBack) continue;
        if (interface.flags() & QNetworkInterface::IsUp && interface.flags() & QNetworkInterface::IsRunning) {
            const QList<QNetworkAddressEntry> entries = interface.addressEntries();
            for (const QNetworkAddressEntry &entry : entries) {
                if (entry.broadcast().toIPv4Address()) {
                    m_broadcastSocket->writeDatagram(datagram, entry.broadcast(), BroadcastPort);
                    // LOG(info) << "Broadcasted datagram: " << datagram.toStdString() << " to "
                    //           << entry.broadcast().toString().toStdString() << ":" << BroadcastPort;
                }
            }
        }
    }
}

void DeviceListModel::stopSearchDevice() {
    if (m_timerId >= 0) {
        killTimer(m_timerId);
        m_timerId = -1;
    }
    emit isSearchingChanged();
}

void DeviceListModel::timerEvent(QTimerEvent *event) {
    broadcast();
    m_retries++;
    emit searchProgressChanged();
    if (m_retries >= RetryCount) {
        QTimer::singleShot(0, this, &DeviceListModel::stopSearchDevice);
    }
}