#include <softbus_opcua/UaAddressSpaceBuilder.h>

#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_opcua/UaModelBinder.h>

#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif

namespace softbus::opcua {

UaAddressSpaceBuilder::UaAddressSpaceBuilder(UA_Server* server)
    : server_(server)
{
}

#if SOFTBUS_HAS_OPCUA
namespace {

UA_NodeId makeStringNodeId(const std::string& nsAndId)
{
    return UA_NODEID_STRING_ALLOC(1, nsAndId.c_str());
}

bool addObject(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
               UA_NodeId* outNodeId)
{
    UA_ObjectAttributes attr = UA_ObjectAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
    const UA_NodeId nodeId = makeStringNodeId(browseName);
    const UA_StatusCode status = UA_Server_addObjectNode(
        server,
        nodeId,
        parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        attr,
        nullptr,
        outNodeId);
    UA_ObjectAttributes_clear(&attr);
    return status == UA_STATUSCODE_GOOD;
}

bool addVariable(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
                 const std::string& nodeKey, UA_NodeId* outNodeId)
{
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
    attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
    UA_Double initial = 0.0;
    UA_Variant_setScalarCopy(&attr.value, &initial, &UA_TYPES[UA_TYPES_DOUBLE]);
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    const UA_NodeId nodeId = makeStringNodeId(nodeKey);
    const UA_StatusCode status = UA_Server_addVariableNode(
        server,
        nodeId,
        parent,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        attr,
        nullptr,
        outNodeId);
    UA_VariableAttributes_clear(&attr);
    return status == UA_STATUSCODE_GOOD;
}

} // namespace
#endif

bool UaAddressSpaceBuilder::buildFromModel(const softbus::core::ObjectModelTree& tree, UaModelBinder* binder)
{
#if !SOFTBUS_HAS_OPCUA
    (void)tree;
    return false;
#else
    if (!server_) {
        return false;
    }

    UA_NodeId siteNode;
    if (!addObject(server_, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), tree.site, &siteNode)) {
        return false;
    }

    UA_NodeId systemNode;
    if (!addObject(server_, siteNode, tree.system, &systemNode)) {
        return false;
    }

    UA_NodeId assetNode;
    if (!addObject(server_, systemNode, tree.asset, &assetNode)) {
        return false;
    }

    for (const auto& device : tree.devices) {
        UA_NodeId deviceNode;
        if (!addObject(server_, assetNode, device.stableDeviceKey, &deviceNode)) {
            continue;
        }
        for (const auto& point : device.points) {
            const auto objectRef = softbus::core::DeviceIdentity::buildObjectRef(
                tree, device.stableDeviceKey, point.name);
            UA_NodeId pointNode;
            if (addVariable(server_, deviceNode, point.name, objectRef.toPath(), &pointNode) && binder) {
                binder->registerPointNode(objectRef.toPath(), pointNode);
            }
        }
    }

    SB_LOG_INFO("UaAddressSpaceBuilder", "Address space built for ", tree.site);
    return true;
#endif
}

} // namespace softbus::opcua
