#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h"

#include <algorithm>
#include <cstring>

#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
#include "utils/logs/logging.h"

namespace softbus::message_bus::pipeline::protocol::modbusrtu
{

namespace
{

class ModbusRtuFramerSession final : public softbus::core::plugin_system::IProtocolFramerSession
{
public:
    ModbusRtuFramerSession(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
                           ModbusRtuFramerPluginConfig cfg,
                           QString endpoint)
        : m_pool(std::move(pool))
        , m_cfg(cfg)
        , m_endpoint(std::move(endpoint))
    {
        m_buf.resize(m_cfg.maxAdu);
    }

    void feedChunk(softbus::message_bus::pipeline::PipelineContext& in,
                   std::vector<softbus::message_bus::pipeline::PipelineContext>& out) override
    {
        using softbus::message_bus::pipeline::FrameKind;
        if (!m_pool || !in.payload.valid()) {
            return;
        }
        const std::uint8_t* p = in.payload.bytes();
        const std::size_t n = in.payloadSize ? in.payloadSize : in.payload.length;
        if (!p || n == 0) {
            return;
        }
        m_lastRx = std::chrono::steady_clock::now();

        if (m_cfg.interFrameTimeoutMs > 0 && m_len > 0) {
            const auto age = std::chrono::duration_cast<std::chrono::milliseconds>(m_lastRx - m_lastActivity).count();
            if (age >= m_cfg.interFrameTimeoutMs) {
                m_len = 0;
            }
        }
        m_lastActivity = m_lastRx;

        if (m_len + n > m_cfg.maxAdu) {
            LOG_WARNING() << "ModbusRtuFramerPlugin: overflow endpoint=" << m_endpoint;
            m_len = 0;
            return;
        }
        std::memcpy(m_buf.data() + m_len, p, n);
        m_len += n;

        while (m_len > 0) {
            auto pred = predictAduLength(m_buf.data(), m_len, m_cfg.maxAdu);
            if (!pred || m_len < *pred) {
                break;
            }
            if (!crcOk(m_buf.data(), *pred)) {
                if (m_len <= 1) {
                    m_len = 0;
                    break;
                }
                std::memmove(m_buf.data(), m_buf.data() + 1, m_len - 1);
                --m_len;
                continue;
            }

            auto blk = m_pool->allocate(*pred);
            if (!blk || !blk->data || blk->capacity < *pred) {
                return;
            }
            std::memcpy(blk->data, m_buf.data(), *pred);
            blk->size = *pred;

            softbus::message_bus::pipeline::PipelineContext framed;
            framed.deviceId = in.deviceId;
            framed.stableKey = in.stableKey;
            framed.endpoint = in.endpoint;
            framed.timestamp = in.timestamp;
            framed.protocolHint = in.protocolHint;
            framed.traceId = in.traceId;
            framed.direction = in.direction;
            framed.frameKind = FrameKind::CompleteFrame;
            framed.payload.block = std::move(blk);
            framed.payload.offset = 0;
            framed.payload.length = *pred;
            framed.payloadSize = *pred;
            out.push_back(std::move(framed));

            const std::size_t rest = m_len - *pred;
            if (rest > 0) {
                std::memmove(m_buf.data(), m_buf.data() + *pred, rest);
            }
            m_len = rest;
        }

        in.payload = {};
        in.payloadSize = 0;
        in.frameKind = FrameKind::StreamChunk;
    }

    void reset() override { m_len = 0; }
    void shutdown() override { m_len = 0; }

private:
    std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
    ModbusRtuFramerPluginConfig m_cfg;
    QString m_endpoint;
    std::vector<std::uint8_t> m_buf;
    std::size_t m_len{0};
    std::chrono::steady_clock::time_point m_lastRx{std::chrono::steady_clock::now()};
    std::chrono::steady_clock::time_point m_lastActivity{std::chrono::steady_clock::now()};
};

} // namespace

ModbusRtuFramerPlugin::ModbusRtuFramerPlugin(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
                                             ModbusRtuFramerPluginConfig cfg)
    : m_pool(std::move(pool))
    , m_cfg(cfg)
{
}

QString ModbusRtuFramerPlugin::pluginId() const
{
    return QStringLiteral("modbus_rtu_framer");
}

bool ModbusRtuFramerPlugin::supports(const QString& protocolHint) const
{
    return isModbusRtuHint(protocolHint);
}

void ModbusRtuFramerPlugin::bindMemoryPool(std::shared_ptr<softbus::core::memory::MemoryPool> pool)
{
    m_pool = std::move(pool);
}

std::unique_ptr<softbus::core::plugin_system::IProtocolFramerSession>
ModbusRtuFramerPlugin::createFramerSession(const QString& endpoint, const QJsonObject& params)
{
    if (!m_pool) {
        return {};
    }
    ModbusRtuFramerPluginConfig cfg = m_cfg;
    if (params.contains(QStringLiteral("modbusRtuMaxFrameBytes"))) {
        cfg.maxAdu = static_cast<std::size_t>(
            std::max(8, params.value(QStringLiteral("modbusRtuMaxFrameBytes")).toInt(static_cast<int>(cfg.maxAdu))));
    }
    if (params.contains(QStringLiteral("modbusRtuInterFrameTimeoutMs"))) {
        cfg.interFrameTimeoutMs = params.value(QStringLiteral("modbusRtuInterFrameTimeoutMs")).toInt(cfg.interFrameTimeoutMs);
    }
    return std::make_unique<ModbusRtuFramerSession>(m_pool, cfg, endpoint);
}

std::shared_ptr<softbus::core::plugin_system::IProtocolFramerPlugin> makeModbusRtuFramerPlugin(
    std::shared_ptr<softbus::core::memory::MemoryPool> pool, ModbusRtuFramerPluginConfig cfg)
{
    return std::make_shared<ModbusRtuFramerPlugin>(std::move(pool), cfg);
}

softbus::core::plugin_system::IProtocolFramerPlugin* createModbusRtuFramerPluginRaw()
{
    return new ModbusRtuFramerPlugin(nullptr, {});
}

} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
