#include <softbus_registry/PointRegistry.h>

#include <softbus_core/identity/DeviceIdentity.h>

#include <fstream>
#include <nlohmann/json.hpp>

namespace softbus::registry {

bool PointRegistry::loadPointTable(const std::string& path)
{
    std::ifstream ifs(path);
    if (!ifs) {
        return false;
    }
    nlohmann::json j;
    ifs >> j;

    tree_.site = j.value("site", "");
    tree_.system = j.value("system", "");
    tree_.asset = j.value("asset", "");
    tree_.devices.clear();
    entries_.clear();
    byObjectRef_.clear();
    byTopic_.clear();
    byOpcuaNodeId_.clear();
    byDevicePoint_.clear();

    if (!j.contains("devices")) {
        return tree_.isValid();
    }

    for (const auto& dev : j["devices"]) {
        softbus::core::DeviceDef device;
        device.stableDeviceKey = dev.value("stableDeviceKey", dev.value("deviceId", ""));
        device.protocol = dev.value("protocol", "modbus");
        const std::string deviceId = dev.value("deviceId", device.stableDeviceKey);

        if (!dev.contains("points")) {
            tree_.devices.push_back(std::move(device));
            continue;
        }

        for (const auto& pt : dev["points"]) {
            softbus::core::PointDef pointDef;
            pointDef.name = pt.value("name", pt.value("pointId", ""));
            pointDef.registerAddress = pt.value("register", 0);
            pointDef.domain = pt.value("domain", "Process");
            pointDef.signalType = pt.value("signalType", "AI");
            pointDef.physicalChannel = pt.value("physicalChannel", "");
            pointDef.unit = pt.value("unit", "");
            pointDef.scale = pt.value("scale", 1.0);
            pointDef.access = pt.value("access", "read");
            pointDef.writable = pt.value("writable", false);
            if (pt.contains("range") && pt["range"].is_array() && pt["range"].size() >= 2) {
                pointDef.minValue = pt["range"][0].get<double>();
                pointDef.maxValue = pt["range"][1].get<double>();
            }
            if (pointDef.access == "read_write" || pointDef.access == "write") {
                pointDef.writable = true;
            }
            device.points.push_back(pointDef);

            PointTableEntry entry;
            entry.deviceId = deviceId;
            entry.pointId = pt.value("pointId", pointDef.name);
            entry.name = pointDef.name;
            entry.displayName = pt.value("displayName", entry.name);
            entry.signalType = pointDef.signalType;
            entry.physicalChannel = pointDef.physicalChannel;
            entry.unit = pointDef.unit;
            entry.minValue = pointDef.minValue;
            entry.maxValue = pointDef.maxValue;
            entry.access = pointDef.access;
            entry.writable = pointDef.writable;
            entry.registerAddress = pointDef.registerAddress;
            entry.scale = pointDef.scale;

            const auto objectRef = softbus::core::DeviceIdentity::buildObjectRef(
                tree_, device.stableDeviceKey, entry.name);
            entry.objectRef = objectRef.toPath();

            entry.opcuaNodeId = pt.value(
                "opcuaNodeId", "ns=1;s=" + entry.objectRef);
            entry.dataTopic = pt.value(
                "dataTopic", topicRules_.formatData(entry.deviceId, entry.pointId));
            entry.cmdTopic = pt.value(
                "cmdTopic", topicRules_.formatCommand(entry.deviceId, entry.pointId));
            entry.ackTopic = pt.value(
                "ackTopic", topicRules_.formatAck(entry.deviceId, entry.pointId));

            entries_.push_back(entry);
            indexEntry(entries_.back());
        }
        tree_.devices.push_back(std::move(device));
    }
    return tree_.isValid() && !entries_.empty();
}

bool PointRegistry::loadTopicRules(const std::string& path)
{
    std::ifstream ifs(path);
    if (!ifs) {
        return false;
    }
    nlohmann::json j;
    ifs >> j;
    topicRules_.data = j.value("data", topicRules_.data);
    topicRules_.command = j.value("command", topicRules_.command);
    topicRules_.ack = j.value("ack", topicRules_.ack);
    topicRules_.event = j.value("event", topicRules_.event);
    topicRules_.heartbeat = j.value("heartbeat", topicRules_.heartbeat);
    return true;
}

void PointRegistry::indexEntry(const PointTableEntry& entry)
{
    const std::size_t idx = entries_.size() - 1;
    byObjectRef_[entry.objectRef] = idx;
    byOpcuaNodeId_[entry.opcuaNodeId] = idx;
    byDevicePoint_[entry.deviceId + "/" + entry.pointId] = idx;
    if (!entry.dataTopic.empty()) {
        byTopic_[entry.dataTopic] = idx;
    }
    if (!entry.cmdTopic.empty()) {
        byTopic_[entry.cmdTopic] = idx;
    }
    if (!entry.ackTopic.empty()) {
        byTopic_[entry.ackTopic] = idx;
    }
}

std::optional<PointTableEntry> PointRegistry::findByObjectRef(const std::string& objectRef) const
{
    const auto it = byObjectRef_.find(objectRef);
    if (it == byObjectRef_.end()) {
        return std::nullopt;
    }
    return entries_[it->second];
}

std::optional<PointTableEntry> PointRegistry::findByTopic(const std::string& topic) const
{
    const auto it = byTopic_.find(topic);
    if (it == byTopic_.end()) {
        return std::nullopt;
    }
    return entries_[it->second];
}

std::optional<PointTableEntry> PointRegistry::findByOpcuaNodeId(const std::string& nodeId) const
{
    const auto it = byOpcuaNodeId_.find(nodeId);
    if (it == byOpcuaNodeId_.end()) {
        return std::nullopt;
    }
    return entries_[it->second];
}

std::optional<PointTableEntry> PointRegistry::findByDevicePoint(const std::string& deviceId,
                                                                const std::string& pointId) const
{
    const auto it = byDevicePoint_.find(deviceId + "/" + pointId);
    if (it == byDevicePoint_.end()) {
        return std::nullopt;
    }
    return entries_[it->second];
}

std::optional<softbus::core::ObjectRef> PointRegistry::findPointByRegister(int registerAddress) const
{
    for (const auto& entry : entries_) {
        if (entry.registerAddress == registerAddress) {
            return softbus::core::ObjectRef::fromPath(entry.objectRef);
        }
    }
    return std::nullopt;
}

bool PointRegistry::validateWriteValue(const PointTableEntry& entry, double value,
                                       std::string* error) const
{
    if (!entry.writable) {
        if (error) {
            *error = "point is not writable";
        }
        return false;
    }
    if (value < entry.minValue || value > entry.maxValue) {
        if (error) {
            *error = "value out of range [" + std::to_string(entry.minValue) + ","
                + std::to_string(entry.maxValue) + "]";
        }
        return false;
    }
    return true;
}

} // namespace softbus::registry
