#include "udpdiscoveryservice.h"
#include <QNetworkInterface>
#include <QDebug>
#include <cstring>
#include <QJsonDocument>
#include <QJsonObject>

const quint16 PORT = 5555;

UdpDiscoveryService::UdpDiscoveryService(const QString& deviceName, QObject *parent)
    : QObject(parent), m_deviceName(deviceName), m_socketV4(nullptr), m_socketV6(nullptr), m_broadcastTimer(nullptr)
{
}

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

void UdpDiscoveryService::start() {
    m_socketV4 = new QUdpSocket(this);
    m_socketV6 = new QUdpSocket(this);

    // Bind IPv4
    if (m_socketV4->bind(QHostAddress::AnyIPv4, PORT, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
        connect(m_socketV4, &QUdpSocket::readyRead, this, &UdpDiscoveryService::readPendingDatagrams);
    } else {
        qWarning() << "Failed to bind IPv4 socket";
    }

    // Bind IPv6
    if (m_socketV6->bind(QHostAddress::AnyIPv6, PORT, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
        connect(m_socketV6, &QUdpSocket::readyRead, this, &UdpDiscoveryService::readPendingDatagrams);
        
        // Join Multicast group on all interfaces
        QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
        for (const QNetworkInterface& iface : interfaces) {
            if (iface.flags().testFlag(QNetworkInterface::CanMulticast)) {
                m_socketV6->joinMulticastGroup(QHostAddress("ff02::1"), iface);
            }
        }
    } else {
        qWarning() << "Failed to bind IPv6 socket";
    }

    m_broadcastTimer = new QTimer(this);
    connect(m_broadcastTimer, &QTimer::timeout, this, &UdpDiscoveryService::broadcast);
    m_broadcastTimer->start(10000); // 10 seconds

    updateLocalIps();
    broadcast();
}

void UdpDiscoveryService::stop() {
    if (m_broadcastTimer) {
        m_broadcastTimer->stop();
        m_broadcastTimer->deleteLater();
        m_broadcastTimer = nullptr;
    }
    if (m_socketV4) {
        m_socketV4->close();
        m_socketV4->deleteLater();
        m_socketV4 = nullptr;
    }
    if (m_socketV6) {
        m_socketV6->close();
        m_socketV6->deleteLater();
        m_socketV6 = nullptr;
    }
}

void UdpDiscoveryService::sendConnectRequest(const QString& targetIp, bool requestRearCamera, bool requestRemoteMic) {
    QHostAddress targetAddr(targetIp);
    QUdpSocket* socket = (targetAddr.protocol() == QAbstractSocket::IPv6Protocol) ? m_socketV6 : m_socketV4;
    if (socket) {
        QJsonObject payloadMap;
        payloadMap["type"] = "request_connect";
        payloadMap["from_device"] = m_deviceName;
        payloadMap["requestRearCamera"] = requestRearCamera;
        payloadMap["requestRemoteMic"] = requestRemoteMic;
        QByteArray data = createUnifiedPacket(1, payloadMap);
        socket->writeDatagram(data, targetAddr, PORT);
    }
}

void UdpDiscoveryService::sendReset(const QString& targetIp) {
    QHostAddress targetAddr(targetIp);
    QUdpSocket* socket = (targetAddr.protocol() == QAbstractSocket::IPv6Protocol) ? m_socketV6 : m_socketV4;
    if (socket) {
        QJsonObject payloadMap;
        payloadMap["type"] = "reset";
        payloadMap["from_device"] = m_deviceName;
        QByteArray data = createUnifiedPacket(1, payloadMap);
        socket->writeDatagram(data, targetAddr, PORT);
    }
}

void UdpDiscoveryService::sendSignalingMessage(const QString& targetIp, const QJsonObject& msg) {
    QHostAddress targetAddr(targetIp);
    QUdpSocket* socket = (targetAddr.protocol() == QAbstractSocket::IPv6Protocol) ? m_socketV6 : m_socketV4;
    if (socket) {
        QByteArray data = createUnifiedPacket(1, msg);
        qDebug() << "Sending unified signaling msg type:" << msg["type"].toString() << "to" << targetIp << ", size:" << data.size();
        qint64 bw = socket->writeDatagram(data, targetAddr, PORT);
        qDebug() << "Bytes written to UDP socket:" << bw;
    }
}

void UdpDiscoveryService::updateLocalIps() {
    m_localIps.clear();
    const QHostAddress &localhost = QHostAddress(QHostAddress::LocalHost);
    for (const QHostAddress &address: QNetworkInterface::allAddresses()) {
        if (address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol) {
            if (address != localhost && !address.isLoopback()) {
                m_localIps.append(address);
            }
        }
    }
}

QByteArray UdpDiscoveryService::createUnifiedPacket(int version, const QJsonObject& payloadMap) {
    QByteArray jsonBytes = QJsonDocument(payloadMap).toJson(QJsonDocument::Compact);
    QByteArray payloadBytes = qCompress(jsonBytes, 9);
    QByteArray packet;
    packet.resize(5 + payloadBytes.size());
    packet.fill(0);
    
    packet[0] = 0x55;
    packet[1] = 0x56;
    
    packet[3] = (version >> 8) & 0xFF;
    packet[4] = version & 0xFF;
    
    for (int i = 0; i < payloadBytes.size(); i++) {
        packet[5 + i] = payloadBytes[i];
    }
    
    uint8_t checksum = 0;
    for (int i = 0; i < packet.size(); i++) {
        if (i != 2) checksum ^= static_cast<uint8_t>(packet[i]);
    }
    packet[2] = checksum;
    
    return packet;
}

void UdpDiscoveryService::setWatcherMode(bool enabled) {
    m_watcherMode = enabled;
    qDebug() << "UdpDiscoveryService watcher mode set to:" << m_watcherMode;
}

void UdpDiscoveryService::broadcast() {
    updateLocalIps();
    
    bool nodesRemoved = false;
    for (auto it = m_discoveredNodes.begin(); it != m_discoveredNodes.end(); ) {
        it.value().ttl--;
        if (it.value().ttl <= 0) {
            qDebug() << "Removing stale node:" << it.value().deviceName << "at" << it.key();
            it = m_discoveredNodes.erase(it);
            nodesRemoved = true;
        } else {
            ++it;
        }
    }
    if (nodesRemoved) {
        emit nodesUpdated(m_discoveredNodes.values());
    }
    
    if (m_watcherMode) {
        // Watcher mode active: listen only, do not send outbound discovery packets
        return;
    }

    for (const QHostAddress& ip : m_localIps) {
        QJsonObject payloadMap;
        payloadMap["type"] = "discovery";
        payloadMap["deviceName"] = m_deviceName;
        payloadMap["capabilities"] = "audio,video";
        payloadMap["versionInfo"] = "1.0-Qt";
        QByteArray data = createUnifiedPacket(1, payloadMap);

        if (ip.protocol() == QAbstractSocket::IPv4Protocol && m_socketV4) {
            // Global Broadcast
            m_socketV4->writeDatagram(data, QHostAddress::Broadcast, PORT);
            
            // Subnet Broadcast (optional fallback, Qt handles 255.255.255.255 well usually, but we can do subnet too)
            for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
                for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
                    if (entry.ip() == ip && !entry.broadcast().isNull()) {
                        m_socketV4->writeDatagram(data, entry.broadcast(), PORT);
                    }
                }
            }
        } else if (ip.protocol() == QAbstractSocket::IPv6Protocol && m_socketV6) {
            m_socketV6->writeDatagram(data, QHostAddress("ff02::1"), PORT);
        }
    }
}

void UdpDiscoveryService::readPendingDatagrams() {
    QUdpSocket* socket = qobject_cast<QUdpSocket*>(sender());
    if (!socket) return;

    while (socket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(int(socket->pendingDatagramSize()));
        QHostAddress senderAddress;
        quint16 senderPort;

        socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);

        // Normalize IPv4-mapped IPv6 addresses to pure IPv4
        bool isIPv4 = false;
        quint32 ipv4 = senderAddress.toIPv4Address(&isIPv4);
        if (isIPv4) {
            senderAddress = QHostAddress(ipv4);
        }

        if (datagram.size() >= 5 && static_cast<uint8_t>(datagram[0]) == 0x55 && static_cast<uint8_t>(datagram[1]) == 0x56) {
            uint8_t checksum = 0;
            for (int i = 0; i < datagram.size(); i++) {
                if (i != 2) checksum ^= static_cast<uint8_t>(datagram[i]);
            }
            if (checksum != static_cast<uint8_t>(datagram[2])) {
                qDebug() << "Checksum mismatch!";
                continue;
            }
            
            QByteArray compressedPayload = datagram.mid(5);
            QByteArray payloadBytes = qUncompress(compressedPayload);
            if (payloadBytes.isEmpty()) {
                qDebug() << "Failed to uncompress unified payload";
                continue;
            }
            QJsonDocument doc = QJsonDocument::fromJson(payloadBytes);
            if (doc.isObject()) {
                QJsonObject msg = doc.object();
                QString type = msg["type"].toString();
                
                if (type == "discovery") {
                    QString deviceName = msg["deviceName"].toString();
                    QString ipAddress = senderAddress.toString();
                    
                    bool isSelf = false;
                    for (const QHostAddress& localIp : m_localIps) {
                        if (localIp.toString() == ipAddress || deviceName == m_deviceName) {
                            isSelf = true;
                            break;
                        }
                    }
                    if (isSelf) continue;
                    
                    NodeInfo info;
                    info.deviceName = deviceName;
                    info.ipAddress = ipAddress;
                    info.versionInfo = msg["versionInfo"].toString();
                    info.capabilities = msg["capabilities"].toString();
                    info.ttl = 12;
                    
                    qDebug() << "Discovered Node:" << info.deviceName << "at" << info.ipAddress;
                    m_discoveredNodes[ipAddress] = info;
                    emit nodesUpdated(m_discoveredNodes.values());
                } else if (type == "request_connect") {
                    bool reqRear = msg["requestRearCamera"].toBool(false);
                    bool reqMic = msg["requestRemoteMic"].toBool(false);
                    emit connectRequested(senderAddress.toString(), reqRear, reqMic);
                } else if (type == "reset") {
                    qDebug() << "Received reset from" << senderAddress.toString();
                    emit resetRequested(senderAddress.toString());
                } else if (type == "offer" || type == "answer" || type == "ice_candidate" || type == "leave" || type == "toggle_light" || type == "toggle_buzzer") {
                    emit signalingMessageReceived(senderAddress.toString(), msg);
                }
            }
        }
    }
}
