#include <softbus_edge/config/EdgeConfig.h>
#include <softbus_edge/egress/CommandExecutor.h>
#include <softbus_edge/egress/ShadowChannelStore.h>
#include <softbus_edge/egress/UplinkClient.h>
#include <softbus_edge/ingress/MockModbusIngress.h>

#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_core/models/SoftbusTypes.h>
#include <softbus_core/platform/PlatformSignal.h>
#include <softbus_core/time/HwClock.h>
#include <softbus_registry/PointRegistry.h>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <string>
#include <thread>

namespace {

struct EdgeOptions {
    std::string configPath{"config/edge.json"};
    std::string objectRef{"DemoSite/DemoSystem/DemoAsset/EdgeDevice_01/MotorTemperature"};
};

EdgeOptions parseArgs(int argc, char* argv[])
{
    EdgeOptions options;
    for (int i = 1; i < argc; ++i) {
        const std::string arg = argv[i];
        if (arg == "--config" && i + 1 < argc) {
            options.configPath = argv[++i];
        } else if (arg == "--object-ref" && i + 1 < argc) {
            options.objectRef = argv[++i];
        }
    }
    return options;
}

} // namespace

int main(int argc, char* argv[])
{
    const EdgeOptions options = parseArgs(argc, argv);

    softbus::edge::EdgeConfig config;
    if (!config.loadFromFile(options.configPath)) {
        std::cerr << "Failed to load edge config: " << options.configPath << std::endl;
        return 1;
    }

    softbus::registry::PointRegistry registry;
    const auto configDir = std::filesystem::path(config.pointTablePath).parent_path();
    registry.loadTopicRules((configDir / "topic_rule.json").string());
    if (!registry.loadPointTable(config.pointTablePath)) {
        std::cerr << "Failed to load point table: " << config.pointTablePath << std::endl;
        return 1;
    }

    softbus::edge::ShadowChannelStore shadowStore;
    softbus::edge::UplinkClient uplink(config.daemonHost, config.daemonPort);
    softbus::edge::CommandExecutor executor(&registry, &shadowStore, &uplink, config.deviceId);
    uplink.setCommandHandler([&executor](const softbus::api::ExecuteCommand& cmd) {
        const auto ack = executor.execute(cmd);
        if (ack.success) {
            SB_LOG_INFO_TRACE("Edge", cmd.traceId,
                "command executed point=", ack.pointId, " value=", ack.executedValue);
        } else {
            SB_LOG_WARN_TRACE("Edge", cmd.traceId, "command failed: ", ack.error);
        }
    });

    if (!uplink.connect()) {
        std::cerr << "Failed to connect to daemon at "
                  << config.daemonHost << ":" << config.daemonPort << std::endl;
        return 1;
    }

    softbus::edge::MockModbusIngress ingress(options.objectRef, config.deviceId);
    ingress.open();

    softbus::core::PlatformSignal::install([]() {
        SB_LOG_INFO("Edge", "Shutdown requested");
    });

    auto lastHeartbeat = std::chrono::steady_clock::now();

    while (!softbus::core::PlatformSignal::shouldStop()) {
        uplink.poll();

        const auto now = std::chrono::steady_clock::now();
        if (now - lastHeartbeat >= std::chrono::milliseconds(config.heartbeatPeriodMs)) {
            uplink.sendHeartbeat(config.deviceId);
            lastHeartbeat = now;
        }

        for (const auto& packet : ingress.poll()) {
            SB_LOG_INFO_TRACE("Edge", packet.traceId,
                "Uplink RawPacket objectRef=", packet.sourceId);
            uplink.sendRawPacket(packet);
        }

        for (const auto& entry : registry.entries()) {
            const auto value = shadowStore.read(entry.physicalChannel);
            if (!value) {
                continue;
            }
            softbus::core::RawPacket feedback;
            feedback.traceId = softbus::core::DeviceIdentity::generateTraceId();
            feedback.timestampNs = softbus::core::HwClock::nowNs();
            feedback.sourceId = entry.objectRef;
            feedback.protocolTag = "shadow";
            const int raw = shadowStore.rawRegisterValue(entry, *value);
            feedback.payload = {
                static_cast<uint8_t>(entry.registerAddress >> 8),
                static_cast<uint8_t>(entry.registerAddress & 0xFF),
                static_cast<uint8_t>(raw >> 8),
                static_cast<uint8_t>(raw & 0xFF)
            };
            uplink.sendRawPacket(feedback);
        }

        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    ingress.close();
    uplink.disconnect();
    return 0;
}
