#include "device_bus/tree/DeviceTreeModel.h"

#include <QJsonArray>
#include <QJsonObject>

#include "utils/logs/logging.h"

bool DeviceTreeModel::loadFromDaemonConfig(const QJsonObject& root, QString* errorMessage)
{
    m_nodes.clear();
    m_indexById.clear();

    if (!root.contains(QStringLiteral("device_tree"))) {
        if (errorMessage) {
            *errorMessage = QStringLiteral("daemon_config.json missing 'device_tree' field");
        }
        return false;
    }

    const QJsonValue treeVal = root.value(QStringLiteral("device_tree"));
    if (!treeVal.isArray()) {
        if (errorMessage) {
            *errorMessage = QStringLiteral("'device_tree' must be an array");
        }
        return false;
    }

    const QJsonArray arr = treeVal.toArray();
    m_nodes.reserve(arr.size());

    for (const QJsonValue& v : arr) {
        if (!v.isObject()) {
            LOG_WARNING() << "DeviceTreeModel: skip non-object node in device_tree";
            continue;
        }
        const QJsonObject obj = v.toObject();

        DeviceTreeNode node;
        node.id   = obj.value(QStringLiteral("id")).toString();
        node.name = obj.value(QStringLiteral("name")).toString();
        node.type = obj.value(QStringLiteral("type")).toString();
        node.endpoint = obj.value(QStringLiteral("endpoint")).toString();
        node.driver = obj.value(QStringLiteral("driver")).toString();
        node.capabilitiesRef = obj.value(QStringLiteral("capabilitiesRef")).toString();
        node.params = obj.value(QStringLiteral("params")).toObject();
        node.enabled = obj.value(QStringLiteral("enabled")).toBool(true);

        // children: array of ids
        const QJsonValue childrenVal = obj.value(QStringLiteral("children"));
        if (childrenVal.isArray()) {
            const QJsonArray childrenArr = childrenVal.toArray();
            for (const QJsonValue& c : childrenArr) {
                if (c.isString()) {
                    node.childrenIds.push_back(c.toString());
                }
            }
        }

        if (node.id.isEmpty()) {
            LOG_WARNING() << "DeviceTreeModel: node without id, skipping";
            continue;
        }

        const int index = m_nodes.size();
        m_nodes.push_back(node);
        m_indexById.insert(node.id, index);
    }

    LOG_INFO() << "DeviceTreeModel loaded" << m_nodes.size() << "nodes from daemon_config";
    return true;
}

void DeviceTreeModel::rebuildIndex()
{
    m_indexById.clear();
    for (int i = 0; i < m_nodes.size(); ++i) {
        const auto& n = m_nodes.at(i);
        if (!n.id.isEmpty()) {
            m_indexById.insert(n.id, i);
        }
    }
}

QJsonArray DeviceTreeModel::toDeviceTreeJson() const
{
    QJsonArray arr;

    for (const auto& n : m_nodes) {
        QJsonObject obj;
        obj.insert(QStringLiteral("id"), n.id);
        if (!n.name.isEmpty()) obj.insert(QStringLiteral("name"), n.name);
        if (!n.type.isEmpty()) obj.insert(QStringLiteral("type"), n.type);
        if (!n.endpoint.isEmpty()) obj.insert(QStringLiteral("endpoint"), n.endpoint);
        if (!n.driver.isEmpty()) obj.insert(QStringLiteral("driver"), n.driver);
        if (!n.capabilitiesRef.isEmpty()) obj.insert(QStringLiteral("capabilitiesRef"), n.capabilitiesRef);
        if (!n.params.isEmpty()) obj.insert(QStringLiteral("params"), n.params);
        obj.insert(QStringLiteral("enabled"), n.enabled);

        if (!n.childrenIds.isEmpty()) {
            QJsonArray children;
            for (const auto& c : n.childrenIds) {
                children.append(c);
            }
            obj.insert(QStringLiteral("children"), children);
        }

        arr.append(obj);
    }
    return arr;
}

QJsonObject DeviceTreeModel::toDaemonConfigRoot() const
{
    QJsonObject root;
    root.insert(QStringLiteral("device_tree"), toDeviceTreeJson());
    return root;
}

const DeviceTreeNode* DeviceTreeModel::findById(const QString& id) const
{
    const int index = findIndexById(id);
    if (index < 0 || index >= m_nodes.size()) {
        return nullptr;
    }
    return &m_nodes.at(index);
}

int DeviceTreeModel::findIndexById(const QString& id) const
{
    const auto it = m_indexById.constFind(id);
    if (it == m_indexById.constEnd()) {
        return -1;
    }
    return it.value();
}

int DeviceTreeModel::findIndexByStableKey(const QString& stableKey) const
{
    if (stableKey.isEmpty()) {
        return -1;
    }
    for (int i = 0; i < m_nodes.size(); ++i) {
        const auto& n = m_nodes.at(i);
        if (n.params.value(QStringLiteral("stableKey")).toString() == stableKey) {
            return i;
        }
    }
    return -1;
}

int DeviceTreeModel::findIndexByEndpoint(const QString& endpoint) const
{
    if (endpoint.isEmpty()) {
        return -1;
    }
    for (int i = 0; i < m_nodes.size(); ++i) {
        const auto& n = m_nodes.at(i);
        if (n.endpoint == endpoint) {
            return i;
        }
    }
    return -1;
}

QString DeviceTreeModel::upsertNodeByStableKey(const QString& stableKey,
                                               const QString& preferredId,
                                               const QString& type,
                                               const QString& endpoint,
                                               const QString& name,
                                               const QJsonObject& extraParams,
                                               bool online,
                                               const QString& lastSeenTs)
{
    int idx = findIndexByStableKey(stableKey);
    if (idx < 0 && !preferredId.isEmpty()) {
        idx = findIndexById(preferredId);
    }

    if (idx < 0) {
        DeviceTreeNode node;
        node.id = preferredId;
        node.type = type;
        node.endpoint = endpoint;
        node.name = name;
        node.enabled = true;
        node.params = extraParams;
        node.params.insert(QStringLiteral("stableKey"), stableKey);
        node.params.insert(QStringLiteral("online"), online);
        if (!lastSeenTs.isEmpty()) {
            node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
        }
        m_nodes.push_back(node);
        rebuildIndex();
        return node.id;
    }

    auto& node = m_nodes[idx];
    if (!type.isEmpty()) node.type = type;
    if (!endpoint.isEmpty()) node.endpoint = endpoint;
    if (!name.isEmpty()) node.name = name;
    node.enabled = true;

    for (auto it = extraParams.begin(); it != extraParams.end(); ++it) {
        node.params.insert(it.key(), it.value());
    }
    node.params.insert(QStringLiteral("stableKey"), stableKey);
    node.params.insert(QStringLiteral("online"), online);
    if (!lastSeenTs.isEmpty()) {
        node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
    }
    return node.id;
}

bool DeviceTreeModel::markOfflineByStableKey(const QString& stableKey, const QString& lastSeenTs)
{
    const int idx = findIndexByStableKey(stableKey);
    if (idx < 0) {
        return false;
    }
    auto& node = m_nodes[idx];
    node.params.insert(QStringLiteral("online"), false);
    if (!lastSeenTs.isEmpty()) {
        node.params.insert(QStringLiteral("lastSeenTs"), lastSeenTs);
    }
    return true;
}

