#include "serial_backend.h"

#include "serial_qt_transport.h"

#include <QDebug>
#include <QCoreApplication>
#include <QDir>
#include <QStringList>
#include <QMetaObject>
#include <QtGlobal>
#include <qcontainerfwd.h>
#include <qserialportinfo.h>
#include <vector>

SerialBackend::SerialBackend(QObject *parent)
    : QObject(parent), m_packetQueue(2048), m_frameQueue(2048), m_readThread(&m_packetQueue), m_decodeThread(&m_packetQueue, &m_frameQueue)
{
    m_request.dataLength = 24;

    const QString pluginDir = QDir(QCoreApplication::applicationDirPath())
                                  .filePath(QStringLiteral("plugins/decoders"));
    QStringList errors;
    const QStringList loaded = m_manager.loadPlugins(pluginDir, &errors);
    for (const QString& err : errors) {
        qWarning().noquote() << err;
    }
    if (!loaded.isEmpty()) {
        m_spec.model = m_manager.activeProtocol();
    } else {
        qWarning() << "No protocol plugins loaded.";
        m_spec.model = QStringLiteral("UNKNOWN");
    }

    m_sendWorker = new SerialSendWorker();
    m_sendWorker->moveToThread(&m_sendThread);
    connect(m_sendWorker, &SerialSendWorker::bytesReceived, this, [this](const QByteArray &data)
            {
#if 0
        if (!data.isEmpty())
            qDebug().noquote() << "Serial recv bytes:" << QString::fromLatin1(data.toHex(' '));
#endif
        m_readThread.enqueueBytes(data); });
    connect(m_sendWorker, &SerialSendWorker::requestBuilt, this, &SerialBackend::requestBuilt);
    connect(m_sendWorker, &SerialSendWorker::writeFailed, this, [](const QString &error)
            {
        if (!error.isEmpty())
            qWarning().noquote() << "Serial write failed:" << error; });

    connect(&m_readThread, &SerialReadThread::parseError, this, [](const QString &error)
            {
        if (!error.isEmpty())
            qWarning().noquote() << "Serial packet invalid:" << error; });
    connect(&m_decodeThread, &SerialDecodeThread::decodeError, this, [](const QString &error)
            {
        if (!error.isEmpty())
            qWarning().noquote() << "Serial decode failed:" << error; });
    connect(&m_decodeThread, &SerialDecodeThread::frameAvailable, this, &SerialBackend::drainFrames_);

    m_sendThread.start();
    setTransport(std::make_unique<QtSerialTransport>());
    updateProtocolBindings_();
    syncSendConfig_();
    syncSendRequest_();

    refreshPorts();
}

SerialBackend::~SerialBackend()
{
    close();
    stopPipeline_();

    if (m_sendWorker && m_sendThread.isRunning())
    {
        QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
                                  { worker->closeTransport(); }, Qt::BlockingQueuedConnection);
    }

    if (m_sendWorker && m_sendThread.isRunning())
    {
        QMetaObject::invokeMethod(m_sendWorker, &QObject::deleteLater, Qt::QueuedConnection);
    }

    if (m_sendThread.isRunning())
    {
        m_sendThread.quit();
        m_sendThread.wait();
    }

    m_sendWorker = nullptr;
}

QString SerialBackend::mode() const
{
    return (m_config.mode == DeviceMode::Slave) ? QStringLiteral("slave") : QStringLiteral("master");
}

QString SerialBackend::sensorGrid() const
{
    return QStringLiteral("%1x%2").arg(m_spec.rows).arg(m_spec.cols);
}

void SerialBackend::setPortName(const QString &name)
{
    if (m_config.portName == name)
        return;
    m_config.portName = name;
    syncSendConfig_();
    emit portNameChanged();
}

void SerialBackend::setBaudRate(int rate)
{
    if (m_config.baudRate == rate)
        return;
    m_config.baudRate = rate;
    syncSendConfig_();
    emit baudRateChanged();
}

void SerialBackend::setPollIntervalMs(int intervalMs)
{
    intervalMs = qMax(1, intervalMs);
    if (m_config.pollIntervalMs == intervalMs)
        return;
    m_config.pollIntervalMs = intervalMs;
    syncSendConfig_();
    emit pollIntervalMsChanged();
}

void SerialBackend::setDeviceAddress(int address)
{
    const int capped = qBound(0, address, 255);
    if (m_config.deviceAddress == static_cast<quint8>(capped))
        return;
    m_config.deviceAddress = static_cast<quint8>(capped);
    syncSendConfig_();
    emit deviceAddressChanged();
}

void SerialBackend::setMode(const QString &mode)
{
    const QString lower = mode.trimmed().toLower();
    const DeviceMode next = (lower == QStringLiteral("master")) ? DeviceMode::Master : DeviceMode::Slave;
    if (m_config.mode == next)
        return;
    m_config.mode = next;
    syncSendConfig_();
    emit modeChanged();
}

void SerialBackend::setRequestFunction(int func)
{
    const int capped = qBound(0, func, 255);
    if (m_request.functionCode == static_cast<quint8>(capped))
        return;
    m_request.functionCode = static_cast<quint8>(capped);
    syncSendRequest_();
    emit requestFunctionChanged();
}

void SerialBackend::setRequestStartAddress(int addr)
{
    const quint32 capped = static_cast<quint32>(qMax(0, addr));
    if (m_request.startAddress == capped)
        return;
    m_request.startAddress = capped;
    syncSendRequest_();
    emit requestStartAddressChanged();
}

void SerialBackend::setRequestLength(int len)
{
    const int capped = qBound(0, len, 65535);
    if (m_request.dataLength == static_cast<quint16>(capped))
        return;
    m_request.dataLength = static_cast<quint16>(capped);
    syncSendRequest_();
    emit requestLengthChanged();
}

void SerialBackend::setProtocol(const QString &name)
{
    if (!m_manager.setActiveProtocol(name))
        return;
    updateProtocolBindings_();
    if (m_spec.model != name) {
        m_spec.model = name;
        emit sensorModelChanged();
    }
    emit protocolChanged();
}

void SerialBackend::applySensorSpec(const QString &model, int rows, int cols)
{
    const QString nextModel = model.trimmed();
    const int nextRows = qMax(0, rows);
    const int nextCols = qMax(0, cols);
    bool changed = false;

    if (!nextModel.isEmpty() && m_spec.model != nextModel)
    {
        m_spec.model = nextModel;
        emit sensorModelChanged();
        changed = true;
    }

    if (m_spec.rows != nextRows || m_spec.cols != nextCols)
    {
        m_spec.rows = nextRows;
        m_spec.cols = nextCols;
        emit sensorGridChanged();
        changed = true;
    }

    if (!changed)
        return;
}

void SerialBackend::setTransport(std::unique_ptr<ISerialTransport> transport)
{
    if (!transport || !m_sendWorker)
        return;
    if (!m_sendThread.isRunning())
        m_sendThread.start();
    if (transport->thread() != &m_sendThread)
        transport->moveToThread(&m_sendThread);
    QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, transport = std::move(transport)]() mutable
                              { worker->setTransport(std::move(transport)); }, Qt::BlockingQueuedConnection);
}

void SerialBackend::refreshPorts()
{
    // Placeholder for real port discovery (QtSerialPort or third-party transport).

    m_availablePorts.clear();
    auto device_found = QSerialPortInfo::availablePorts();
    for (auto item : device_found)
    {
        m_availablePorts.append(item.portName());
    }
    if (m_config.portName.isEmpty() && !m_availablePorts.isEmpty())
    {
        m_config.portName = m_availablePorts.first();
        emit portNameChanged();
    }
    emit availablePortsChanged();
}

bool SerialBackend::open()
{
    if (m_connected)
        return true;

    startPipeline_();

    bool ok = false;
    QString error;
    if (m_sendWorker)
    {
        QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config = m_config, &ok, &error]()
                                  { ok = worker->openTransport(config, &error); }, Qt::BlockingQueuedConnection);
    }
    if (!ok)
    {
        qWarning().noquote() << "Serial open failed:" << error;
        return false;
    }

    m_connected = true;
    emit connectedChanged();

    return true;
}

void SerialBackend::close()
{
    if (!m_connected)
        return;
    if (m_sendWorker && m_sendThread.isRunning())
    {
        QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
                                  { worker->closeTransport(); }, Qt::BlockingQueuedConnection);
    }
    stopPipeline_();
    m_connected = false;
    emit connectedChanged();
}

void SerialBackend::requestOnce()
{
    if (!m_sendWorker)
        return;
    QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
                              { worker->requestOnce(); }, Qt::QueuedConnection);
}

void SerialBackend::feedBytes(const QByteArray &data)
{
    if (!m_readThread.isRunning())
        startPipeline_();
    m_readThread.enqueueBytes(data);
}

void SerialBackend::setSensorWidth(int w)
{
    m_spec.cols = w;
    syncSendConfig_();
}

void SerialBackend::setSensorHeight(int h)
{
    m_spec.rows = h;
    syncSendConfig_();
}

void SerialBackend::drainFrames_()
{
    if (!m_frameCallback)
        return;
    DataFrame frame;
    while (m_frameQueue.tryPop(&frame))
        m_frameCallback(frame);
}

void SerialBackend::startPipeline_()
{
    if (m_readThread.isRunning() || m_decodeThread.isRunning())
        stopPipeline_();
    m_packetQueue.reset();
    m_frameQueue.reset();
    m_readThread.clear();
    updateProtocolBindings_();

    if (!m_readThread.isRunning())
        m_readThread.start();
    if (!m_decodeThread.isRunning())
        m_decodeThread.start();
}

void SerialBackend::stopPipeline_()
{
    if (m_readThread.isRunning())
    {
        m_readThread.stop();
        m_readThread.wait();
    }
    if (m_decodeThread.isRunning())
    {
        m_decodeThread.stop();
        m_decodeThread.wait();
    }
    m_packetQueue.reset();
    m_frameQueue.reset();
    m_readThread.clear();
}

void SerialBackend::updateProtocolBindings_()
{
    const auto bundle = m_manager.activeBundle();

    SerialReadThread::ParseFunc parseFunc;
    if (bundle.format)
    {
        parseFunc = [format = bundle.format](QByteArray *buffer, QByteArray *packet, QString *error)
        {
            return format->tryParse(buffer, packet, error);
        };
    }
    m_readThread.setParseFunc(std::move(parseFunc));

    SerialDecodeThread::DecodeFunc decodeFunc;
    if (bundle.decoder)
    {
        decodeFunc = [decoder = bundle.decoder](const QByteArray &packet, DataFrame *frame, QString *error)
        {
            return decoder->decodeFrame(packet, frame, error);
        };
    }
    m_decodeThread.setDecodeFunc(std::move(decodeFunc));

    SerialSendWorker::BuildRequestFunc requestFunc;
    if (bundle.codec)
    {
        requestFunc = [codec = bundle.codec](const SerialConfig &config, const SensorRequest &request)
        {
            return codec->buildRequest(config, request);
        };
    }

    if (m_sendWorker)
    {
        QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, requestFunc]() mutable
                                  { worker->setBuildRequestFunc(std::move(requestFunc)); }, Qt::QueuedConnection);
    }
}

void SerialBackend::syncSendConfig_()
{
    if (!m_sendWorker)
        return;
    const SerialConfig config = m_config;
    QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config]()
                              { worker->setConfig(config); }, Qt::QueuedConnection);
}

void SerialBackend::syncSendRequest_()
{
    if (!m_sendWorker)
        return;
    const SensorRequest request = m_request;
    QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, request]()
                              { worker->setRequest(request); }, Qt::QueuedConnection);
}
