#include <softbus_opcua/UaMethodBinder.h>

#include <softbus_core/logging/Logger.h>

#include <nlohmann/json.hpp>

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

namespace softbus::opcua {

#if SOFTBUS_HAS_OPCUA
namespace {
UaMethodBinder* g_methodBinder = nullptr;

UA_StatusCode executeCommandMethod(UA_Server* server,
                                   const UA_NodeId* sessionId,
                                   void* sessionContext,
                                   const UA_NodeId* methodId,
                                   void* methodContext,
                                   const UA_NodeId* objectId,
                                   void* objectContext,
                                   size_t inputSize,
                                   const UA_Variant* input,
                                   size_t outputSize,
                                   UA_Variant* output)
{
    (void)server;
    (void)sessionId;
    (void)sessionContext;
    (void)methodId;
    (void)methodContext;
    (void)objectId;
    (void)objectContext;
    (void)outputSize;

    if (!g_methodBinder || inputSize < 1) {
        return UA_STATUSCODE_BADINVALIDARGUMENT;
    }

    const UA_String* jsonInput = static_cast<const UA_String*>(input[0].data);
    if (!jsonInput) {
        return UA_STATUSCODE_BADTYPEMISMATCH;
    }

    const std::string jsonStr(reinterpret_cast<const char*>(jsonInput->data), jsonInput->length);
    const auto j = nlohmann::json::parse(jsonStr, nullptr, false);
    if (j.is_discarded()) {
        return UA_STATUSCODE_BADINVALIDARGUMENT;
    }

    softbus::api::ExecuteCommand cmd = softbus::api::ExecuteCommand::fromJson(j);
    std::string error;
    if (!cmd.validate(&error)) {
        const std::string result = std::string(R"({"success":false,"error":")") + error + "\"}";
        UA_String out = UA_STRING_ALLOC(result.c_str());
        UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
        UA_String_clear(&out);
        return UA_STATUSCODE_GOOD;
    }

    const std::string result = g_methodBinder->dispatchCommand(cmd);
    UA_String out = UA_STRING_ALLOC(result.c_str());
    UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
    UA_String_clear(&out);
    return UA_STATUSCODE_GOOD;
}
} // namespace
#endif

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

std::string UaMethodBinder::dispatchCommand(const softbus::api::ExecuteCommand& command)
{
    if (!handler_) {
        return R"({"success":false,"error":"handler not bound"})";
    }
    SB_LOG_INFO_TRACE("UaMethodBinder", command.traceId,
        "ExecuteCommand invoked via OPC UA method");
    return handler_(command);
}

bool UaMethodBinder::bindExecuteCommand(ExecuteCommandHandler handler)
{
#if !SOFTBUS_HAS_OPCUA
    (void)handler;
    return false;
#else
    handler_ = std::move(handler);
    g_methodBinder = this;

    UA_ObjectAttributes objAttr = UA_ObjectAttributes_default;
    objAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "SoftBusControl");
    UA_NodeId controlObjectId = UA_NODEID_STRING_ALLOC(1, "SoftBusControl");
    UA_NodeId createdObjectId;
    UA_StatusCode status = UA_Server_addObjectNode(
        server_,
        controlObjectId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME_ALLOC(1, "SoftBusControl"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        objAttr,
        nullptr,
        &createdObjectId);
    UA_ObjectAttributes_clear(&objAttr);
    if (status != UA_STATUSCODE_GOOD) {
        return false;
    }

    UA_Argument inputArgument;
    UA_Argument_init(&inputArgument);
    inputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Command JSON");
    inputArgument.name = UA_STRING_ALLOC("commandJson");
    inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    inputArgument.valueRank = UA_VALUERANK_SCALAR;

    UA_Argument outputArgument;
    UA_Argument_init(&outputArgument);
    outputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Result JSON");
    outputArgument.name = UA_STRING_ALLOC("resultJson");
    outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    outputArgument.valueRank = UA_VALUERANK_SCALAR;

    UA_MethodAttributes methodAttr = UA_MethodAttributes_default;
    methodAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "ExecuteCommand");
    methodAttr.executable = true;
    methodAttr.userExecutable = true;

    status = UA_Server_addMethodNode(
        server_,
        UA_NODEID_STRING_ALLOC(1, "ExecuteCommand"),
        createdObjectId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME_ALLOC(1, "ExecuteCommand"),
        methodAttr,
        &executeCommandMethod,
        1,
        &inputArgument,
        1,
        &outputArgument,
        nullptr,
        nullptr);

    UA_Argument_clear(&inputArgument);
    UA_Argument_clear(&outputArgument);
    UA_MethodAttributes_clear(&methodAttr);
    return status == UA_STATUSCODE_GOOD;
#endif
}

} // namespace softbus::opcua
