#include "core/metadata/ProfileRegistry.h"

#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>

namespace softbus::core::metadata
{

namespace
{

QString normalizedProtocol(const QString& protocol)
{
    return protocol.trimmed().toLower();
}

} // namespace

bool ProfileRegistry::loadFromFile(const QString& filePath)
{
    m_lastError.clear();
    m_profilesByKey.clear();

    QFile f(filePath);
    if (!f.open(QIODevice::ReadOnly)) {
        m_lastError = QStringLiteral("failed to open profile file: %1").arg(filePath);
        return false;
    }

    QJsonParseError pe;
    const auto      doc = QJsonDocument::fromJson(f.readAll(), &pe);
    if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
        m_lastError = QStringLiteral("invalid profile json: %1").arg(pe.errorString());
        return false;
    }

    const auto root = doc.object();
    const auto arr  = root.value(QStringLiteral("profiles")).toArray();
    for (const auto& item : arr) {
        if (!item.isObject()) {
            continue;
        }
        const auto obj = item.toObject();

        softbus::core::models::DeviceDataProfile p;
        p.deviceId = obj.value(QStringLiteral("deviceId")).toString();
        p.protocol = normalizedProtocol(obj.value(QStringLiteral("protocol")).toString());

        const auto mappings = obj.value(QStringLiteral("mappings")).toArray();
        for (const auto& m : mappings) {
            if (!m.isObject()) {
                continue;
            }
            const auto one = parseMapping(m.toObject());
            if (one.has_value()) {
                p.mappings.append(*one);
            }
        }
        if (p.deviceId.isEmpty() || p.protocol.isEmpty() || p.mappings.isEmpty()) {
            continue;
        }
        m_profilesByKey.insert(makeKey(p.deviceId, p.protocol), std::move(p));
    }

    if (m_profilesByKey.isEmpty()) {
        m_lastError = QStringLiteral("profile file contains no valid entries");
        return false;
    }
    return true;
}

const softbus::core::models::DeviceDataProfile* ProfileRegistry::find(const QString& deviceId,
                                                                       const QString& protocol) const
{
    const auto protocolNorm = normalizedProtocol(protocol);
    const auto exactIt      = m_profilesByKey.constFind(makeKey(deviceId, protocolNorm));
    if (exactIt != m_profilesByKey.constEnd()) {
        return &exactIt.value();
    }
    const auto wildcard = m_profilesByKey.constFind(makeKey(QStringLiteral("*"), protocolNorm));
    if (wildcard != m_profilesByKey.constEnd()) {
        return &wildcard.value();
    }
    return nullptr;
}

QString ProfileRegistry::makeKey(const QString& deviceId, const QString& protocol)
{
    return deviceId + QStringLiteral("::") + protocol;
}

std::optional<softbus::core::models::DataPointMapping> ProfileRegistry::parseMapping(
    const QJsonObject& o)
{
    softbus::core::models::DataPointMapping m;
    m.pointId          = o.value(QStringLiteral("pointId")).toString();
    m.metadataId       = o.value(QStringLiteral("metadataId")).toString();
    m.registerAddress  = static_cast<std::uint32_t>(
        o.value(QStringLiteral("registerAddress")).toInt(static_cast<int>(m.registerAddress)));
    m.byteOffset       = static_cast<std::uint16_t>(o.value(QStringLiteral("byteOffset")).toInt(0));
    m.byteLength       = static_cast<std::uint16_t>(o.value(QStringLiteral("byteLength")).toInt(0));
    m.bitOffset        = static_cast<std::int8_t>(o.value(QStringLiteral("bitOffset")).toInt(-1));
    m.endianness       = parseEndianness(o.value(QStringLiteral("endianness")).toString());
    m.rawType          = parseDataType(o.value(QStringLiteral("rawType")).toString());
    m.scaleFactor      = o.value(QStringLiteral("scaleFactor")).toDouble(1.0);
    m.offsetFactor     = o.value(QStringLiteral("offsetFactor")).toDouble(0.0);
    m.expression       = o.value(QStringLiteral("expression")).toString();
    if (m.pointId.isEmpty() || m.metadataId.isEmpty() || m.byteLength == 0) {
        return std::nullopt;
    }
    return m;
}

softbus::core::models::DataType ProfileRegistry::parseDataType(const QString& s)
{
    const QString x = s.trimmed().toUpper();
    using softbus::core::models::DataType;
    if (x == QStringLiteral("BOOL")) {
        return DataType::BOOL;
    }
    if (x == QStringLiteral("INT32")) {
        return DataType::INT32;
    }
    if (x == QStringLiteral("UINT32")) {
        return DataType::UINT32;
    }
    if (x == QStringLiteral("INT64")) {
        return DataType::INT64;
    }
    if (x == QStringLiteral("FLOAT32")) {
        return DataType::FLOAT32;
    }
    if (x == QStringLiteral("FLOAT64")) {
        return DataType::FLOAT64;
    }
    if (x == QStringLiteral("STRING")) {
        return DataType::STRING;
    }
    if (x == QStringLiteral("BYTE_ARRAY")) {
        return DataType::BYTE_ARRAY;
    }
    return DataType::INT32;
}

softbus::core::models::Endianness ProfileRegistry::parseEndianness(const QString& s)
{
    const QString x = s.trimmed().toUpper();
    using softbus::core::models::Endianness;
    if (x == QStringLiteral("LITTLE_ENDIAN")) {
        return Endianness::LittleEndian;
    }
    if (x == QStringLiteral("BIG_ENDIAN_BYTE_SWAP")) {
        return Endianness::BigEndianByteSwap;
    }
    return Endianness::BigEndian;
}

} // namespace softbus::core::metadata
