// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest

#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <set>
#include <stdexcept>
#include <thread>
#include <utility>

#include <date/date.h>
#include <fmt/core.h>
#include <utils/date.hpp>

#include "everest/logging.hpp"
#include "helper.hpp"
#include "powermeterImpl.hpp"

namespace {
using em580::registers::MODBUS_BASE_ADDRESS;
using em580::registers::MODBUS_DEVICE_STATE_ADDRESS;
using em580::registers::MODBUS_FIRMWARE_COMMUNICATION_MODULE_ADDRESS;
using em580::registers::MODBUS_FIRMWARE_MEASURE_MODULE_ADDRESS;
using em580::registers::MODBUS_IDENTIFICATION_CODE_ADDRESS;
using em580::registers::MODBUS_OCMF_CHARGING_POINT_ID_START_ADDRESS;
using em580::registers::MODBUS_OCMF_CHARGING_POINT_ID_TYPE_ADDRESS;
using em580::registers::MODBUS_OCMF_CHARGING_POINT_ID_WORD_COUNT;
using em580::registers::MODBUS_OCMF_CHARGING_STATUS_ADDRESS;
using em580::registers::MODBUS_OCMF_COMMAND_ADDRESS;
using em580::registers::MODBUS_OCMF_COMMAND_END;
using em580::registers::MODBUS_OCMF_COMMAND_START;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_DATA_START_ADDRESS;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_DATA_WORD_COUNT;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_FLAGS_COUNT;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_FLAGS_START_ADDRESS;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_LEVEL_ADDRESS;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_STATUS_ADDRESS;
using em580::registers::MODBUS_OCMF_IDENTIFICATION_TYPE_ADDRESS;
using em580::registers::MODBUS_OCMF_LAST_TRANSACTION_ID_ADDRESS;
using em580::registers::MODBUS_OCMF_LAST_TRANSACTION_ID_WORD_COUNT;
using em580::registers::MODBUS_OCMF_SESSION_MODALITY_ADDRESS;
using em580::registers::MODBUS_OCMF_SESSION_MODALITY_CHARGING_VEHICLE;
using em580::registers::MODBUS_OCMF_STATE_ADDRESS;
using em580::registers::MODBUS_OCMF_STATE_FILE_ADDRESS;
using em580::registers::MODBUS_OCMF_STATE_NOT_READY;
using em580::registers::MODBUS_OCMF_STATE_READY;
using em580::registers::MODBUS_OCMF_STATE_RUNNING;
using em580::registers::MODBUS_OCMF_STATE_SIZE_ADDRESS;
using em580::registers::MODBUS_OCMF_TARIFF_TEXT_ADDRESS;
using em580::registers::MODBUS_OCMF_TARIFF_TEXT_WORD_COUNT;
using em580::registers::MODBUS_OCMF_TIME_SYNC_STATUS_ADDRESS;
using em580::registers::MODBUS_OCMF_TRANSACTION_ID_GENERATION_ADDRESS;
using em580::registers::MODBUS_PRODUCTION_YEAR_ADDRESS;
using em580::registers::MODBUS_PRODUCTION_YEAR_ADDRESS_EM300_SERIES;
using em580::registers::MODBUS_PUBLIC_KEY_ADDRESS;
using em580::registers::MODBUS_PUBLIC_KEY_DER_ADDRESS;
using em580::registers::MODBUS_PUBLIC_KEY_DER_WORD_COUNT_256;
using em580::registers::MODBUS_PUBLIC_KEY_DER_WORD_COUNT_384;
using em580::registers::MODBUS_REAL_TIME_ENERGY_ADDRESS;
using em580::registers::MODBUS_REAL_TIME_ENERGY_ADDRESS_EM300_SERIES;
using em580::registers::MODBUS_REAL_TIME_ENERGY_COUNT;
using em580::registers::MODBUS_REAL_TIME_ENERGY_COUNT_EM300_SERIES;
using em580::registers::MODBUS_REAL_TIME_VALUES_ADDRESS;
using em580::registers::MODBUS_REAL_TIME_VALUES_COUNT;
using em580::registers::MODBUS_SERIAL_NUMBER_REGISTER_COUNT;
using em580::registers::MODBUS_SERIAL_NUMBER_START_ADDRESS;
using em580::registers::MODBUS_SIGNATURE_TYPE_ADDRESS;
using em580::registers::MODBUS_TEMPERATURE_ADDRESS;
using em580::registers::MODBUS_TIMEZONE_OFFSET_ADDRESS;
using em580::registers::MODBUS_UTC_TIMESTAMP_ADDRESS;

using em580::registers::MODBUS_SIGNED_MAP_ADDRESS;
using em580::registers::MODBUS_SIGNED_MAP_WORD_COUNT_256;
using em580::registers::MODBUS_SIGNED_MAP_WORD_COUNT_384;

const char* ocmf_state_to_string(std::uint16_t ocmf_state) {
    switch (ocmf_state) {
    case MODBUS_OCMF_STATE_READY:
        return "READY";
    case MODBUS_OCMF_STATE_RUNNING:
        return "RUNNING";
    case MODBUS_OCMF_STATE_NOT_READY:
        return "NOT_READY";
    default:
        return "UNKNOWN";
    }
}
} // namespace

// Byte offsets for Modbus register 300001-300055 (physical addresses
// 0000h-0036h) Each INT32 register is 4 bytes, each INT16 register is 2 bytes
namespace Offsets {
// Voltage registers (INT32, 4 bytes each)
constexpr std::size_t V_L1_N = 0; // 300001 (0000h)
constexpr std::size_t V_L2_N = 4; // 300003 (0002h)
constexpr std::size_t V_L3_N = 8; // 300005 (0004h)

// Current registers (INT32, 4 bytes each)
constexpr std::size_t A_L1 = 24; // 300013 (000Ch)
constexpr std::size_t A_L2 = 28; // 300015 (000Eh)
constexpr std::size_t A_L3 = 32; // 300017 (0010h)

// Power registers (INT32, 4 bytes each)
constexpr std::size_t W_L1 = 36;  // 300019 (0012h)
constexpr std::size_t W_L2 = 40;  // 300021 (0014h)
constexpr std::size_t W_L3 = 44;  // 300023 (0016h)
constexpr std::size_t W_SYS = 80; // 300041 (0028h)

// Reactive power registers (INT32, 4 bytes each)
constexpr std::size_t VAR_L1 = 60;  // 300031 (001Eh)
constexpr std::size_t VAR_L2 = 64;  // 300033 (0020h)
constexpr std::size_t VAR_L3 = 68;  // 300035 (0022h)
constexpr std::size_t VAR_SYS = 88; // 300045 (002Ch)

// Phase sequence register (INT16, 2 bytes)
constexpr std::size_t PHASE_SEQUENCE = 100; // 300051 (0032h)

// Frequency register (INT16, 2 bytes)
constexpr std::size_t FREQUENCY = 102; // 300052 (0033h)

// Energy registers (INT64, 8 bytes each) - within extended read range
constexpr std::size_t ENERGY_IMPORT = 0;  // 301281 (0500h) - kWh (+) TOT, byte offset 0 (52*2)
constexpr std::size_t ENERGY_EXPORT = 56; // 301309 (051Ch) - kWh (-) TOT, byte offset 28 (28*2)

// Energy registers (INT32, 4 bytes each) — Table 2.5-1 EM/ET300 Modbus (rev 2.17)
constexpr std::size_t ENERGY_IMPORT_INT = 0;  // 301025 (0400h) - kWh (+) TOT INT, byte offset 0 (0*2)
constexpr std::size_t ENERGY_IMPORT_DEC = 4;  // 301027 (0402h) - kWh (+) TOT DEC, byte offset 4 (2*2)
constexpr std::size_t ENERGY_EXPORT_INT = 16; // 301033 (0408h) - kWh (-) TOT INT, byte offset 16 (8*2)
constexpr std::size_t ENERGY_EXPORT_DEC = 20; // 301035 (040Ah) - kWh (-) TOT DEC, byte offset 20 (10*2)
} // namespace Offsets

// Scaling factors from Modbus document
namespace Factors {
constexpr float VOLTAGE = 0.1F;        // Value weight: Volt*10
constexpr float CURRENT = 0.001F;      // Value weight: Ampere*1000
constexpr float POWER = 0.1F;          // Value weight: Watt*10
constexpr float REACTIVE_POWER = 0.1F; // Value weight: var*10
constexpr float FREQUENCY = 0.1F;      // Value weight: Hz*10
constexpr float TEMPERATURE = 0.1F;    // Value weight: Temperature*10
constexpr float ENERGY_INT = 1000.0F;  // Value weight: kWh*1
constexpr float ENERGY_DEC = 1.0F;     // Value weight: kWh*1000
} // namespace Factors

namespace module::main {

namespace {

/// Build a stop-transaction reply with explicit fields (avoids brace-init field-order mistakes vs
/// types/powermeter.yaml).
[[nodiscard]] types::powermeter::TransactionStopResponse
make_transaction_stop_response(types::powermeter::TransactionRequestStatus status) {
    types::powermeter::TransactionStopResponse response;
    response.status = status;
    return response;
}

[[nodiscard]] types::powermeter::TransactionStopResponse
make_transaction_stop_response(types::powermeter::TransactionRequestStatus status, std::string error) {
    types::powermeter::TransactionStopResponse response;
    response.status = status;
    response.error = std::move(error);
    return response;
}

/// Build a start-transaction reply with explicit fields (types/powermeter.yaml: status, error, min/max stop time).
[[nodiscard]] types::powermeter::TransactionStartResponse
make_transaction_start_response(types::powermeter::TransactionRequestStatus status) {
    types::powermeter::TransactionStartResponse response;
    response.status = status;
    return response;
}

[[nodiscard]] types::powermeter::TransactionStartResponse
make_transaction_start_response(types::powermeter::TransactionRequestStatus status, std::string error) {
    types::powermeter::TransactionStartResponse response;
    response.status = status;
    response.error = std::move(error);
    return response;
}

} // namespace

powermeterImpl::~powermeterImpl() {
    stop_requested_.store(true);
    stop_cv_.notify_all();
    if (live_measure_thread_.joinable()) {
        live_measure_thread_.join();
    }
    if (time_sync_thread_.joinable()) {
        time_sync_thread_.join();
    }
}

void powermeterImpl::init() {
    m_pending_closed_transaction = false;
    // Set up error handler for CommunicationFault
    transport::ErrorHandler error_handler = [this](const std::string& error_message) {
        // Check if error is already active to avoid duplicate errors
        if (!error_state_monitor->is_error_active("powermeter/CommunicationFault", "CommunicationError")) {
            EVLOG_error << "Raising CommunicationFault: " << error_message;
            auto error = error_factory->create_error("powermeter/CommunicationFault", "CommunicationError",
                                                     error_message, Everest::error::Severity::High);
            raise_error(error);
        }
    };

    // Set up clear error handler for CommunicationFault
    transport::ClearErrorHandler clear_error_handler = [this]() {
        // Clear CommunicationFault error if it's active
        if (error_state_monitor->is_error_active("powermeter/CommunicationFault", "CommunicationError")) {
            EVLOG_info << "Clearing CommunicationFault: Communication restored";
            clear_error("powermeter/CommunicationFault", "CommunicationError");
        }
    };

    const transport::SerialCommHubTransport::RetryConfig retry_config{
        config.initial_connection_retry_count,
        config.initial_connection_retry_delay_ms,
        config.communication_retry_count,
        config.communication_retry_delay_ms,
    };

    const transport::SerialCommHubTransport::TransportConfig transport_config{
        config.powermeter_device_id,
        MODBUS_BASE_ADDRESS,
        retry_config,
    };

    p_modbus_transport = std::make_unique<transport::SerialCommHubTransport>(*mod->r_modbus, transport_config,
                                                                             error_handler, clear_error_handler);
}

void powermeterImpl::read_signature_config() {
    EVLOG_info << "Read the signature public key...";

    enum SignatureType {
        SIGNATURE_256_BIT,
        SIGNATURE_384_BIT,
        SIGNATURE_NONE
    };

    auto read_signature_type = [this]() {
        transport::DataVector data = p_modbus_transport->fetch(MODBUS_SIGNATURE_TYPE_ADDRESS, 1);
        return static_cast<SignatureType>(modbus_utils::to_uint16(data, modbus_utils::ByteOffset{0}));
    };

    auto read_public_key_in_hex = [this](int lengthInBits) {
        const transport::DataVector data =
            p_modbus_transport->fetch(MODBUS_PUBLIC_KEY_ADDRESS, static_cast<std::uint16_t>((lengthInBits >> 3) + 1));
        // Table 4.18/4.19: last byte is unused and always 0x00; keep the 0x04
        // prefix, drop the unused byte.
        return modbus_utils::to_hex_string(data, modbus_utils::ByteOffset{0},
                                           modbus_utils::ByteLength{data.size() - 1});
    };

    auto read_public_key_der_in_hex = [this](std::uint16_t der_word_count) {
        // Table 4.20/4.21: mandatory to read whole block starting at 2600h.
        const transport::DataVector der_data = p_modbus_transport->fetch(MODBUS_PUBLIC_KEY_DER_ADDRESS, der_word_count);
        std::size_t der_len = der_data.size();
        if (der_data.size() >= 2 && der_data[0] == 0x30) {
            // DER header is: 0x30 <len> ...
            der_len = std::min<std::size_t>(der_data.size(), static_cast<std::size_t>(2 + der_data[1]));
        }
        return modbus_utils::to_hex_string(der_data, modbus_utils::ByteOffset{0}, modbus_utils::ByteLength{der_len});
    };

    const SignatureType signature_type = read_signature_type();
    std::string signature_type_string;
    std::uint16_t der_word_count = 0;

    switch (signature_type) {
    case SIGNATURE_256_BIT:
        m_public_key_length_in_bits = 256;
        signature_type_string = "256-bit";
        m_signature_method_string = "ECDSA-brainpoolP256r1-SHA256";
        der_word_count = MODBUS_PUBLIC_KEY_DER_WORD_COUNT_256;
        // Spec Table 4.5: 256-bit signature is 32 words (= CHAR[64] = 64 bytes)
        m_signed_map_word_count = MODBUS_SIGNED_MAP_WORD_COUNT_256;
        break;
    case SIGNATURE_384_BIT:
        m_public_key_length_in_bits = 384;
        signature_type_string = "384-bit";
        m_signature_method_string = "ECDSA-brainpoolP384r1-SHA256";
        der_word_count = MODBUS_PUBLIC_KEY_DER_WORD_COUNT_384;
        // Spec Table 4.6: 384-bit signature is 48 words (= CHAR[96] = 96 bytes)
        m_signed_map_word_count = MODBUS_SIGNED_MAP_WORD_COUNT_384;
        break;
    default:
        signature_type_string = "none";
        throw std::runtime_error("no signature keys are configured, device is not eichrecht compliant");
    }
    EVLOG_info << "Signature type detected: " << signature_type_string;

    if (config.public_key_format == "binary") {
        m_public_key_hex = read_public_key_in_hex(m_public_key_length_in_bits);
        EVLOG_info << "Public key (raw, hex): " << m_public_key_hex;
    } else if (config.public_key_format == "der") {
        m_public_key_hex = read_public_key_der_in_hex(der_word_count);
        EVLOG_info << "Public key (DER, hex): " << m_public_key_hex;
    } else {
        throw std::invalid_argument("invalid public key format: " + config.public_key_format);
    }
    publish_public_key_ocmf(m_public_key_hex);
}

void powermeterImpl::read_identification() {
    static const std::set<std::uint16_t> em300_series_ids = {331, 332, 335, 336, 340, 341, 345, 346, 355};

    // Read the identification code to detect meter model
    transport::DataVector cgc_id_data = p_modbus_transport->fetch(MODBUS_IDENTIFICATION_CODE_ADDRESS, 1);
    std::uint16_t cgc_id = modbus_utils::to_uint16(cgc_id_data, modbus_utils::ByteOffset{0});

    EVLOG_info << "Carlo Gavazzi Controls identification code: " << (int)cgc_id;

    // check for EM300/ET300 series
    if (em300_series_ids.count(cgc_id)) {
        m_transaction_support = false;
    }
}

void powermeterImpl::read_firmware_versions() {
    EVLOG_info << "Read the firmware versions...";

    // Read measure module firmware version/revision (register 300771)
    transport::DataVector measure_fw_data = p_modbus_transport->fetch(MODBUS_FIRMWARE_MEASURE_MODULE_ADDRESS, 1);
    std::uint16_t measure_fw_value = modbus_utils::to_uint16(measure_fw_data, modbus_utils::ByteOffset{0});

    // Parse firmware version: MSB bits 0-3 = Minor, bits 4-7 = Major, LSB =
    // Revision
    std::uint8_t major = (measure_fw_value >> 8) & 0xF0;
    major = major >> 4; // Shift right to get actual major version (0-15)
    std::uint8_t minor = (measure_fw_value >> 8) & 0x0F;
    std::uint8_t revision = measure_fw_value & 0xFF;

    m_measure_module_firmware_version = fmt::format("{}.{}.{}", major, minor, revision);
    EVLOG_info << "Measure module firmware version: " << m_measure_module_firmware_version;

    // Read communication module firmware version/revision (register 300772)
    transport::DataVector comm_fw_data = p_modbus_transport->fetch(MODBUS_FIRMWARE_COMMUNICATION_MODULE_ADDRESS, 1);
    std::uint16_t comm_fw_value = modbus_utils::to_uint16(comm_fw_data, modbus_utils::ByteOffset{0});

    // Parse firmware version: MSB bits 0-3 = Minor, bits 4-7 = Major, LSB =
    // Revision
    major = (comm_fw_value >> 8) & 0xF0;
    major = major >> 4; // Shift right to get actual major version (0-15)
    minor = (comm_fw_value >> 8) & 0x0F;
    revision = comm_fw_value & 0xFF;

    m_communication_module_firmware_version = fmt::format("{}.{}.{}", major, minor, revision);
    EVLOG_info << "Communication module firmware version: " << m_communication_module_firmware_version;
}

void powermeterImpl::read_serial_number() {
    EVLOG_info << "Read the serial number...";
    // Read serial number (registers 320481-320487, 7 UINT16 registers = 14 bytes)
    transport::DataVector serial_data =
        p_modbus_transport->fetch(MODBUS_SERIAL_NUMBER_START_ADDRESS, MODBUS_SERIAL_NUMBER_REGISTER_COUNT);

    std::string serial_str;
    if (m_transaction_support) {
        // Convert bytes to string (serial number is stored as ASCII)
        // Modbus returns data in big-endian format: each UINT16 register is [MSB,
        // LSB] So for 7 registers, we get: [reg0_MSB, reg0_LSB, reg1_MSB, reg1_LSB,
        // ...] We assume the string contains only printable characters and null
        // terminator is correctly set or at the end
        serial_str.reserve(14);
        for (const auto& byte : serial_data) {
            char byte_char = static_cast<char>(byte);
            // Stop at null terminator if present
            if (byte_char == '\0') {
                break;
            }
            serial_str += byte_char;
        }
    } else {
        // on older devices like EM300 series, only the LSB is used
        serial_str.reserve(7);
        for (auto byte = serial_data.begin() + 1; byte < serial_data.end(); byte += 2) {
            char byte_char = static_cast<char>(*byte);
            // Stop at null terminator if present
            if (byte_char == '\0') {
                break;
            }
            serial_str += byte_char;
        }
    }

    // production year register moved in newer devices:
    // register 320488 on newer device like EM580,
    // register 320497 on EM300 series
    // we coupled it here to the transaction support to keep things simple
    transport::DataVector year_data = p_modbus_transport->fetch(
        m_transaction_support ? MODBUS_PRODUCTION_YEAR_ADDRESS : MODBUS_PRODUCTION_YEAR_ADDRESS_EM300_SERIES, 1);
    std::uint16_t production_year = modbus_utils::to_uint16(year_data, modbus_utils::ByteOffset{0});

    // Combine serial number and production year with a dot separator
    m_serial_number = serial_str + "." + std::to_string(production_year);
    EVLOG_info << "Serial number: " << m_serial_number;
}

void powermeterImpl::read_transaction_state_and_id() {
    transport::DataVector state_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_ADDRESS, 1);
    std::uint16_t ocmf_state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});

    // Read transaction id from the tariff text (6900h, TT) which we write as:
    // "<tariff_text><=><transaction_id>".
    try {
        transport::DataVector tt_data =
            p_modbus_transport->fetch(MODBUS_OCMF_TARIFF_TEXT_ADDRESS, MODBUS_OCMF_TARIFF_TEXT_WORD_COUNT);
        auto null_pos = std::find(tt_data.begin(), tt_data.end(), 0);
        std::string tt_str(tt_data.begin(), null_pos);
        const auto tx_id_opt =
            ocmf::extract_transaction_id_from_tariff_text(tt_str, powermeterImpl::TARIFF_TEXT_TRANSACTION_ID_MARKER);
        if (tx_id_opt.has_value()) {
            m_transaction_id = *tx_id_opt;
            EVLOG_info << "Recovered transaction id from tariff text (6900h): " << m_transaction_id;
        }
    } catch (const std::exception& e) {
        EVLOG_warning << "Failed to read tariff text (6900h): " << e.what();
    }

    if (ocmf_state == MODBUS_OCMF_STATE_READY) {
        m_pending_closed_transaction = true;
        EVLOG_info << "Detected a closed transaction with data pending to be read";
    }
    if (ocmf_state == MODBUS_OCMF_STATE_RUNNING) {
        EVLOG_info << "Detected a running transaction, waiting for a stop transaction command with transaction id: "
                   << m_transaction_id << " or an empty transaction id";
        m_transaction_active.store(true);
    }
}

void powermeterImpl::configure_device() {
    EVLOG_info << "Configure the device...";
    read_identification();
    read_firmware_versions();
    read_serial_number();
    if (m_transaction_support) {
        read_signature_config();
        // need a delay here because if the device comes from a power outage, the time
        // sync will fail
        std::this_thread::sleep_for(std::chrono::seconds(2));
        // Initial time synchronization
        synchronize_time();
        // Set timezone offset
        set_timezone(config.timezone_offset_minutes);
        // see if there is a pending closed transaction that needs to be read
        read_transaction_state_and_id();
    }
    EVLOG_info << "Device configured";
}

void powermeterImpl::ready() {
    // Retry logic is now handled by SerialCommHubTransport
    live_measure_thread_ = std::thread([this] {
        std::atomic_bool device_not_configured = true;
        auto last_device_state_read = std::chrono::steady_clock::time_point{};
        while (!stop_requested_.load()) {
            const auto measurement_interval = std::chrono::milliseconds{config.live_measurement_interval_ms};
            const auto device_state_interval = std::chrono::milliseconds{config.device_state_read_interval_ms};
            try {
                if (device_not_configured.load()) {
                    configure_device();
                    device_not_configured = false;
                    last_device_state_read = std::chrono::steady_clock::time_point{}; // force state read
                }
                read_powermeter_values();
                if (m_transaction_support) {
                    const auto now = std::chrono::steady_clock::now();
                    if (last_device_state_read == std::chrono::steady_clock::time_point{} ||
                        (now - last_device_state_read) >= device_state_interval) {
                        read_device_state();
                        last_device_state_read = now;
                    }
                }
            } catch (const std::invalid_argument& e) {
                EVLOG_error << "Configuration error (will not retry): " << e.what();
                break;
            } catch (const std::exception& e) {
                EVLOG_error << "Failed to communicate with the device, try again in "
                            << config.communication_error_pause_delay_s << " seconds: " << e.what();
                device_not_configured = true;
                {
                    std::unique_lock<std::mutex> lock(stop_mutex_);
                    stop_cv_.wait_for(lock, std::chrono::seconds{config.communication_error_pause_delay_s},
                                      [this] { return stop_requested_.load(); });
                }
            }
            {
                std::unique_lock<std::mutex> lock(stop_mutex_);
                stop_cv_.wait_for(lock, measurement_interval, [this] { return stop_requested_.load(); });
            }
        }
    });

    // Start time synchronization thread
    time_sync_thread_ = std::thread([this]() { time_sync_thread(); });
}

void powermeterImpl::write_transaction_registers(const types::powermeter::TransactionReq& transaction_req) {
    // 1. Write OCMF Identification Status (register 328673, 7000h)
    // 0 = NOT_ASSIGNED (False), 1 = ASSIGNED (True)
    std::uint16_t identification_status_value =
        (transaction_req.identification_status == types::powermeter::OCMFUserIdentificationStatus::ASSIGNED) ? 1 : 0;
    std::vector<std::uint16_t> status_data = {identification_status_value};
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_IDENTIFICATION_STATUS_ADDRESS, status_data);

    // 2. Write OCMF Identification Level (register 328674, 7001h) - optional
    std::uint16_t identification_level_value = 0; // Default: NONE
    if (transaction_req.identification_level.has_value()) {
        identification_level_value = ocmf::level_to_value(transaction_req.identification_level.value());
    }
    std::vector<std::uint16_t> level_data = {identification_level_value};
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_IDENTIFICATION_LEVEL_ADDRESS, level_data);

    // 3. Write OCMF Identification Flags (registers 328675-328678, 7002h-7005h) -
    // up to 4 flags
    std::vector<std::uint16_t> flags_data(MODBUS_OCMF_IDENTIFICATION_FLAGS_COUNT, 0);
    for (size_t i = 0; i < transaction_req.identification_flags.size() && i < MODBUS_OCMF_IDENTIFICATION_FLAGS_COUNT;
         ++i) {
        flags_data[i] = ocmf::flag_to_value(transaction_req.identification_flags[i]);
    }
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_IDENTIFICATION_FLAGS_START_ADDRESS, flags_data);

    // 4. Write OCMF Identification Type (register 328679, 7006h)
    std::uint16_t identification_type_value = ocmf::type_to_value(transaction_req.identification_type);
    std::vector<std::uint16_t> type_data = {identification_type_value};
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_IDENTIFICATION_TYPE_ADDRESS, type_data);

    // 5. Write OCMF Identification Data (registers 328680-328699, 7007h-701Ah) -
    // CHAR[40] = 20 words.
    std::string client_id_str = transaction_req.identification_data.value_or("");
    modbus_utils::log_truncation_warning_if_needed("OCMF Identification Data", client_id_str,
                                                   MODBUS_OCMF_IDENTIFICATION_DATA_WORD_COUNT);
    std::vector<std::uint16_t> id_data =
        modbus_utils::string_to_modbus_char_array(client_id_str, MODBUS_OCMF_IDENTIFICATION_DATA_WORD_COUNT);
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_IDENTIFICATION_DATA_START_ADDRESS, id_data);

    // 6. Write OCMF Charging point identifier type (register 328700, 701Bh)
    // 0 = EVSEID, 1 = CBIDC (default to EVSEID)
    std::uint16_t charging_point_id_type = 0; // EVSEID
    std::vector<std::uint16_t> id_type_data = {charging_point_id_type};
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_CHARGING_POINT_ID_TYPE_ADDRESS, id_type_data);

    // 7. Write OCMF Charging point identifier (registers 328701-328720,
    // 701Ch-702Fh) - CHAR[40] = 20 words (evse_id)
    modbus_utils::log_truncation_warning_if_needed("OCMF Charging Point Identifier (EVSE ID)", transaction_req.evse_id,
                                                   MODBUS_OCMF_CHARGING_POINT_ID_WORD_COUNT);
    std::vector<std::uint16_t> evse_id_data =
        modbus_utils::string_to_modbus_char_array(transaction_req.evse_id, MODBUS_OCMF_CHARGING_POINT_ID_WORD_COUNT);
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_CHARGING_POINT_ID_START_ADDRESS, evse_id_data);

    // 8. Write tariff text (register 326881, 6900h) - CHAR[252] = 126 words
    // The device accepts partial writes as long as the string is 0-terminated.
    std::string tariff_text;
    const std::string& base = transaction_req.tariff_text.value_or("");
    tariff_text.reserve(base.size() + TARIFF_TEXT_TRANSACTION_ID_MARKER.size() + transaction_req.transaction_id.size());
    tariff_text.append(base);
    tariff_text.append(TARIFF_TEXT_TRANSACTION_ID_MARKER);
    tariff_text.append(transaction_req.transaction_id);
    modbus_utils::log_truncation_warning_if_needed("OCMF Tariff Text (TT)", tariff_text,
                                                   MODBUS_OCMF_TARIFF_TEXT_WORD_COUNT);
    const std::vector<std::uint16_t> tariff_text_data =
        modbus_utils::string_to_modbus_char_array(tariff_text, MODBUS_OCMF_TARIFF_TEXT_WORD_COUNT);
    p_modbus_transport->write_multiple_registers(MODBUS_OCMF_TARIFF_TEXT_ADDRESS, tariff_text_data);
}

std::string powermeterImpl::read_ocmf_file() {
    transport::DataVector size_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_SIZE_ADDRESS, 1);
    std::uint16_t size = modbus_utils::to_uint16(size_data, modbus_utils::ByteOffset{0});
    if (size == 0) {
        throw std::runtime_error("OCMF file size is 0");
    }
    transport::DataVector file_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_FILE_ADDRESS, size);
    return std::string{file_data.begin(), file_data.end()};
}

void powermeterImpl::clear_transaction_states() {
    transport::DataVector state_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_ADDRESS, 1);
    std::uint16_t ocmf_state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});

    if (ocmf_state == MODBUS_OCMF_STATE_READY) {
        EVLOG_info << "Current OCMF state: " << ocmf_state_to_string(ocmf_state) << "(" << ocmf_state << ")";
        EVLOG_info << "Cleanup necessary ...";
        read_ocmf_file();
        // write 0 to the OCMF state to confirm the reading of the OCMF file
        std::vector<std::uint16_t> ocmf_confirmation_data = {MODBUS_OCMF_STATE_NOT_READY};
        p_modbus_transport->write_multiple_registers(MODBUS_OCMF_STATE_ADDRESS, ocmf_confirmation_data);
        EVLOG_info << "Cleanup done.";
    }
}

types::powermeter::TransactionStartResponse
powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& treq) {
    if (not m_transaction_support) {
        EVLOG_info << "start transaction rejected: meter model does not support transactions";
        return make_transaction_start_response(types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
                                               "This meter model does not support transactions.");
    }
    try {
        EVLOG_info << "Starting transaction with transaction id: " << treq.transaction_id
                   << " evse id: " << treq.evse_id << " identification status: " << treq.identification_status
                   << " identification type: "
                   << types::powermeter::ocmfidentification_type_to_string(treq.identification_type)
                   << " identification level: "
                   << types::powermeter::ocmfidentification_level_to_string(
                          treq.identification_level.value_or(types::powermeter::OCMFIdentificationLevel::NONE))
                   << " identification data: " << treq.identification_data.value_or("")
                   << " tariff text: " << treq.tariff_text.value_or("none");
        // Check OCMF state and ensure it's NOT_READY before starting a transaction
        // According to the Modbus document, the OCMF state must be NOT_READY (0) to
        // start a new transaction
        transport::DataVector state_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_ADDRESS, 1);
        std::uint16_t ocmf_state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});
        EVLOG_info << "Current OCMF state: " << ocmf_state_to_string(ocmf_state) << "(" << ocmf_state << ")";

        if (ocmf_state != MODBUS_OCMF_STATE_NOT_READY) {
            EVLOG_warning << "Spurious transaction detected, clearing transaction states ...";
            clear_transaction_states();
            m_pending_closed_transaction = false;
            return make_transaction_start_response(types::powermeter::TransactionRequestStatus::OK);
        }

        // Write transaction registers first
        EVLOG_info << "Write transaction registers...";
        write_transaction_registers(treq);

        EVLOG_info << "Write session modality ... to charging vehicle";
        std::vector<std::uint16_t> session_modality_data = {MODBUS_OCMF_SESSION_MODALITY_CHARGING_VEHICLE};
        p_modbus_transport->write_multiple_registers(MODBUS_OCMF_SESSION_MODALITY_ADDRESS, session_modality_data);

        // Write 'B' command to start transaction (Table 4.35, register 328737)
        std::vector<std::uint16_t> command_data1 = {MODBUS_OCMF_COMMAND_START};
        p_modbus_transport->write_multiple_registers(MODBUS_OCMF_COMMAND_ADDRESS, command_data1);
        EVLOG_info << "Transaction " << treq.transaction_id << " started";

        // Track local state (only used internally, not in device dump)
        m_transaction_active.store(true);
        m_transaction_id = treq.transaction_id;

        // Capture signed meter value for transaction start (returned on stop)
        m_start_signed_meter_value.emplace(read_signed_meter_value());

        return make_transaction_start_response(types::powermeter::TransactionRequestStatus::OK);
    } catch (const std::exception& e) {
        EVLOG_error << e.what();
        return make_transaction_start_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                               fmt::format("can't start transaction: {}", e.what()));
    }
}

types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
    if (not m_transaction_support) {
        EVLOG_info << "stop transaction rejected: meter model does not support transactions";
        return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
                                              "This meter model does not support transactions.");
    }

    EVLOG_info << "Stopping transaction with transaction id: " << (transaction_id.empty() ? "empty" : transaction_id);
    // if the transaction id is empty, we need to clean up the transaction states
    // we do our best to clean up the transaction states
    if (transaction_id.empty()) {
        EVLOG_info << "Cleaning up the transaction request.";
        try {
            if (!m_pending_closed_transaction and m_transaction_active.load()) {
                std::vector<std::uint16_t> command_data = {MODBUS_OCMF_COMMAND_END};
                p_modbus_transport->write_multiple_registers(MODBUS_OCMF_COMMAND_ADDRESS, command_data);
                EVLOG_info << "Transaction " << transaction_id << " stopped";
            }
            m_pending_closed_transaction = false;
            clear_transaction_states();
        } catch (const std::exception& e) {
            EVLOG_error << e.what();
        }
        m_pending_closed_transaction = false;
        return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::OK);
    }
    try {
        if (m_pending_closed_transaction) {
            // the received transaction id is different from the current transaction
            // id since there is a pending closed transaction, I assume a power loss
            // occurred we need to check if the transaction id is equal to the
            // transaction id from OCMF file
            EVLOG_info << "Power loss occurred, checking if the transaction id == "
                          "transaction id from OCMF file";
            std::string ocmf_file = read_ocmf_file();
            const auto ocmf_file_transaction_id_opt = ocmf::extract_transaction_id_from_ocmf_record(ocmf_file);
            if (!ocmf_file_transaction_id_opt.has_value()) {
                EVLOG_error << "Failed to extract transaction id from OCMF file TT field";
                return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                                      "Failed to extract transaction id from OCMF file");
            }
            const std::string& ocmf_file_transaction_id = *ocmf_file_transaction_id_opt;
            EVLOG_info << "OCMF file transaction id: " << ocmf_file_transaction_id;
            if (ocmf_file_transaction_id != transaction_id) {
                return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                                      "Transaction id mismatch");
            }
            EVLOG_info << "Transaction id matches, sending successful transaction "
                          "stop response with OCMF file";
            m_pending_closed_transaction = false;
            auto signed_meter_value = types::units_signed::SignedMeterValue{ocmf_file, "", "OCMF"};
            signed_meter_value.public_key.emplace(m_public_key_hex);
            ocmf::confirm_file_read(*p_modbus_transport);
            return types::powermeter::TransactionStopResponse{types::powermeter::TransactionRequestStatus::OK,
                                                              {}, // Empty start_signed_meter_value
                                                              signed_meter_value};
        } else if (m_transaction_id == transaction_id) {
            EVLOG_info << "Sending the end transaction command to the device";
            // Write 'E' command to end transaction (Table 4.35, register 328737)
            std::vector<std::uint16_t> command_data = {MODBUS_OCMF_COMMAND_END};
            p_modbus_transport->write_multiple_registers(MODBUS_OCMF_COMMAND_ADDRESS, command_data);
            EVLOG_info << "Transaction " << transaction_id << " stopped";
            m_transaction_active.store(false);

            // check if the OCMF state is ready (Table 4.36, register 328742)
            if (!ocmf::wait_for_ready(*p_modbus_transport)) {
                return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                                      "can't stop transaction: OCMF did not reach ready state");
            }

            // For Eichrecht, return the OCMF file as the signed meter value report.
            const std::string ocmf_data = read_ocmf_file();
            auto signed_meter_value = types::units_signed::SignedMeterValue{ocmf_data, "", "OCMF"};
            signed_meter_value.public_key.emplace(m_public_key_hex);

            // write 0 to the OCMF state to confirm the reading of the OCMF file
            ocmf::confirm_file_read(*p_modbus_transport);
            m_pending_closed_transaction = false;
            return types::powermeter::TransactionStopResponse{types::powermeter::TransactionRequestStatus::OK,
                                                              m_start_signed_meter_value, signed_meter_value};
        } else {
            EVLOG_error << "No open transaction or unknown transaction id: " << transaction_id;
            return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                                  "No open transaction or unknown transaction id");
        }
    } catch (const std::exception& e) {
        EVLOG_error << e.what();
        return make_transaction_stop_response(types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR,
                                              fmt::format("can't stop transaction: {}", e.what()));
    }
}

void powermeterImpl::read_powermeter_values() {
    transport::DataVector data;

    if (m_transaction_support) {
        // Read a compact range starting at 300001 containing all instantaneous values we use
        // up to 300052 (frequency). Energy totals are read separately from 301281+ (INT64, Wh).
        data = p_modbus_transport->fetch(MODBUS_REAL_TIME_VALUES_ADDRESS, MODBUS_REAL_TIME_VALUES_COUNT);
    } else {
        // older models/firmwares are nasty when reading the whole block, so we split the request manually
        data = p_modbus_transport->fetch(MODBUS_REAL_TIME_VALUES_ADDRESS, MODBUS_REAL_TIME_VALUES_COUNT - 2);
        auto data2 = p_modbus_transport->fetch(MODBUS_REAL_TIME_VALUES_ADDRESS + MODBUS_REAL_TIME_VALUES_COUNT - 2, 2);
        data.insert(data.end(), data2.begin(), data2.end());
    }

    types::powermeter::Powermeter powermeter{};
    powermeter.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
    powermeter.meter_id = m_serial_number;

    // Voltage values (INT32, weight: Volt*10)
    // 300001 (0000h): V L1-N
    // 300003 (0002h): V L2-N
    // 300005 (0004h): V L3-N
    types::units::Voltage voltage_V;
    voltage_V.L1 =
        Factors::VOLTAGE * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::V_L1_N}));
    voltage_V.L2 =
        Factors::VOLTAGE * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::V_L2_N}));
    voltage_V.L3 =
        Factors::VOLTAGE * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::V_L3_N}));
    powermeter.voltage_V = voltage_V;

    // Current values (INT32, weight: Ampere*1000)
    // Values are already signed: positive = import, negative = export
    // 300013 (000Ch): A L1
    // 300015 (000Eh): A L2
    // 300017 (0010h): A L3
    types::units::Current current_A;
    current_A.L1 =
        Factors::CURRENT * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::A_L1}));
    current_A.L2 =
        Factors::CURRENT * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::A_L2}));
    current_A.L3 =
        Factors::CURRENT * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::A_L3}));
    powermeter.current_A = current_A;

    // Power values (INT32, weight: Watt*10)
    // Values are already signed: positive = import, negative = export
    // 300019 (0012h): W L1
    // 300021 (0014h): W L2
    // 300023 (0016h): W L3
    // 300041 (0028h): W sys
    types::units::Power power_W;
    power_W.L1 =
        Factors::POWER * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::W_L1}));
    power_W.L2 =
        Factors::POWER * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::W_L2}));
    power_W.L3 =
        Factors::POWER * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::W_L3}));
    power_W.total =
        Factors::POWER * static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::W_SYS}));
    powermeter.power_W = power_W;

    // Reactive power values (INT32, weight: var*10)
    // Values are already signed: positive = import, negative = export
    // 300031 (001Eh): var L1
    // 300033 (0020h): var L2
    // 300035 (0022h): var L3
    // 300045 (002Ch): var sys
    types::units::ReactivePower VAR;
    VAR.L1 = Factors::REACTIVE_POWER *
             static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::VAR_L1}));
    VAR.L2 = Factors::REACTIVE_POWER *
             static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::VAR_L2}));
    VAR.L3 = Factors::REACTIVE_POWER *
             static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::VAR_L3}));
    VAR.total = Factors::REACTIVE_POWER *
                static_cast<float>(modbus_utils::to_int32(data, modbus_utils::ByteOffset{Offsets::VAR_SYS}));
    powermeter.VAR = VAR;

    // Frequency (INT16, weight: Hz*10) - register 300052 (0033h)
    // Note: Frequency is also available at 300273 and 301341 as INT32 with
    // different factors, but we use 300052 (INT16) to keep the bulk read compact
    // (300001-300055)
    types::units::Frequency frequency_Hz;
    frequency_Hz.L1 = Factors::FREQUENCY *
                      static_cast<float>(modbus_utils::to_int16(data, modbus_utils::ByteOffset{Offsets::FREQUENCY}));
    powermeter.frequency_Hz = frequency_Hz;

    // Phase sequence (INT16) - register 300051 (0032h)
    // Value -1 = L1-L3-L2 sequence, value 1 = L1-L2-L3 sequence
    std::int16_t phase_sequence = modbus_utils::to_int16(data, modbus_utils::ByteOffset{Offsets::PHASE_SEQUENCE});
    if (phase_sequence == -1) {
        powermeter.phase_seq_error = true; // L1-L3-L2 is considered an error (counter-clockwise)
    } else if (phase_sequence == 1) {
        powermeter.phase_seq_error = false; // L1-L2-L3 is correct (clockwise)
    }

    if (m_transaction_support) {
        transport::DataVector dataEnergy =
            p_modbus_transport->fetch(MODBUS_REAL_TIME_ENERGY_ADDRESS, MODBUS_REAL_TIME_ENERGY_COUNT);

        // Energy import: register 301281 (kWh (+) TOT) - INT64, 4 words
        // Spec (Table 4.3): value weight is Wh.
        // Note: energy_Wh_import is a required field, not optional
        powermeter.energy_Wh_import.total =
            static_cast<float>(modbus_utils::to_int64(dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_IMPORT}));

        // Energy export: register 301309 (kWh (-) TOT) - INT64, 4 words
        // Spec (Table 4.3): value weight is Wh.
        types::units::Energy energy_Wh_export;
        energy_Wh_export.total =
            static_cast<float>(modbus_utils::to_int64(dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_EXPORT}));
        powermeter.energy_Wh_export = energy_Wh_export;
    } else {
        transport::DataVector dataEnergy = p_modbus_transport->fetch(MODBUS_REAL_TIME_ENERGY_ADDRESS_EM300_SERIES,
                                                                     MODBUS_REAL_TIME_ENERGY_COUNT_EM300_SERIES);

        // Energy import: register 301025 (kWh (+) TOT) - INT32, 2 words -> INT part
        // Spec (Table 2.5.1): value weight is kWh*1.
        // Note: energy_Wh_import is a required field, not optional
        powermeter.energy_Wh_import.total =
            Factors::ENERGY_INT * static_cast<float>(modbus_utils::to_int32(
                                      dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_IMPORT_INT}));
        // Energy import: register 301027 (kWh (+) TOT) - INT32, 2 words -> DEC part
        // Spec (Table 2.5.1): value weight is kWh*1000.
        powermeter.energy_Wh_import.total +=
            Factors::ENERGY_DEC * static_cast<float>(modbus_utils::to_int32(
                                      dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_IMPORT_DEC}));

        // Energy export: register 301033 (kWh (-) TOT) - INT32, 2 words -> INT part
        // Spec (Table 2.5.1): value weight is kWh*1.
        types::units::Energy energy_Wh_export;
        energy_Wh_export.total =
            Factors::ENERGY_INT * static_cast<float>(modbus_utils::to_int32(
                                      dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_EXPORT_INT}));
        // Energy export: register 301035 (kWh (-) TOT) - INT32, 2 words -> DEC part
        // Spec (Table 2.5.1): value weight is kWh*1000.
        energy_Wh_export.total +=
            Factors::ENERGY_DEC * static_cast<float>(modbus_utils::to_int32(
                                      dataEnergy, modbus_utils::ByteOffset{Offsets::ENERGY_EXPORT_DEC}));
        powermeter.energy_Wh_export = energy_Wh_export;
    }

    // Disable for now the temperature reading, since I can't read it in the above
    // block read Read internal temperature (INT16, weight: Temperature*10) -
    // register 300776 (0307h) - 1 word transport::DataVector temperature_data =
    // p_modbus_transport->fetch(MODBUS_TEMPERATURE_ADDRESS, 1);
    // types::temperature::Temperature temperature;
    // temperature.temperature = Factors::TEMPERATURE *
    //                           static_cast<float>(modbus_utils::to_int16(temperature_data,
    //                           modbus_utils::ByteOffset{0}));
    // temperature.location = "Internal";
    // std::vector<types::temperature::Temperature> temperatures;
    // temperatures.push_back(temperature);
    // powermeter.temperatures = temperatures;

    if (m_transaction_support) {
        powermeter.signed_meter_value = read_signed_meter_value();
    }
    publish_powermeter(powermeter);
}

void powermeterImpl::dump_device_state() {
    try {
        // 1. OCMF state
        transport::DataVector state_data = p_modbus_transport->fetch(MODBUS_OCMF_STATE_ADDRESS, 1);
        std::uint16_t state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});

        // 2. Charging status (register 328742 / 7045h)
        transport::DataVector charging_status_data = p_modbus_transport->fetch(MODBUS_OCMF_CHARGING_STATUS_ADDRESS, 1);
        std::uint16_t charging_status = modbus_utils::to_uint16(charging_status_data, modbus_utils::ByteOffset{0});

        // 3. Last transaction id (register 328723 / 7059h, CHAR[])
        transport::DataVector last_tx_data = p_modbus_transport->fetch(MODBUS_OCMF_LAST_TRANSACTION_ID_ADDRESS,
                                                                       MODBUS_OCMF_LAST_TRANSACTION_ID_WORD_COUNT);
        auto null_pos = std::find(last_tx_data.begin(), last_tx_data.end(), 0);
        std::string last_tx_id(last_tx_data.begin(), null_pos);

        // 4. Time synchronization status (register 328769 / 7060h)
        transport::DataVector time_sync_status_data =
            p_modbus_transport->fetch(MODBUS_OCMF_TIME_SYNC_STATUS_ADDRESS, 1);
        std::uint16_t time_sync_status = modbus_utils::to_uint16(time_sync_status_data, modbus_utils::ByteOffset{0});

        // 5. OCMF command (last written command value)
        transport::DataVector cmd_data = p_modbus_transport->fetch(MODBUS_OCMF_COMMAND_ADDRESS, 1);
        std::uint16_t raw_cmd = modbus_utils::to_uint16(cmd_data, modbus_utils::ByteOffset{0});
        // ASCII code is stored in the low byte
        char cmd_char = static_cast<char>(raw_cmd & 0xFFU);

        // 6. Transaction ID definition (OCMF transaction ID generation)
        transport::DataVector tx_def_data = p_modbus_transport->fetch(MODBUS_OCMF_TRANSACTION_ID_GENERATION_ADDRESS, 1);
        std::uint16_t tx_def = modbus_utils::to_uint16(tx_def_data, modbus_utils::ByteOffset{0});

        EVLOG_info << "EM580 device state dump:";
        EVLOG_info << "  OCMF state: " << state;
        EVLOG_info << "  Charging status (device, raw): " << charging_status;
        // EVLOG_info << "  Last transaction id (device): " << last_tx_id;
        EVLOG_info << "  Time synchronization status (device, raw): " << time_sync_status;
        EVLOG_info << "  Last OCMF command (raw): 0x" << std::hex << raw_cmd << " ('" << cmd_char << "')";
        EVLOG_info << "  Transaction ID definition (OCMF): 0x" << std::hex << tx_def;
    } catch (const std::exception& e) {
        EVLOG_error << "Failed to dump EM580 device state: " << e.what();
    }
}

bool powermeterImpl::is_transaction_active() const {
    return m_transaction_active.load();
}

void powermeterImpl::synchronize_time() {
    // Get current UTC time as seconds since Unix epoch
    auto now_utc = date::utc_clock::now();
    // Convert to system_clock for time_t conversion
    auto sys_now = std::chrono::system_clock::now();
    auto time_since_epoch = sys_now.time_since_epoch();
    std::int64_t seconds_since_epoch = std::chrono::duration_cast<std::chrono::seconds>(time_since_epoch).count();

    // Convert to UINT64 and split into 4 words
    // According to EM580 Modbus spec: for INT64, word order is LSW->MSW
    // (little-endian word order) So we write: [LSW, LSW+1, MSW-1, MSW] = [bits
    // 0-15, bits 16-31, bits 32-47, bits 48-63]
    std::uint64_t timestamp = static_cast<std::uint64_t>(seconds_since_epoch);
    std::vector<std::uint16_t> data;
    data.push_back(static_cast<std::uint16_t>(timestamp & 0xFFFF));         // LSW: bits 0-15
    data.push_back(static_cast<std::uint16_t>((timestamp >> 16) & 0xFFFF)); // bits 16-31
    data.push_back(static_cast<std::uint16_t>((timestamp >> 32) & 0xFFFF)); // bits 32-47
    data.push_back(static_cast<std::uint16_t>((timestamp >> 48) & 0xFFFF)); // MSW: bits 48-63

    // Write UTC timestamp to register 328723 (4 words for INT64)
    p_modbus_transport->write_multiple_registers(MODBUS_UTC_TIMESTAMP_ADDRESS, data);

    EVLOG_info << "Time synchronized: " << Everest::Date::to_rfc3339(now_utc)
               << " (Unix timestamp: " << seconds_since_epoch << ")";
}

void powermeterImpl::set_timezone(int offset_minutes) {
    EVLOG_info << "Try to set the timezone ... ";

    // Convert to INT16 (signed 16-bit integer)
    // Timezone offset range: -1440 to +1440 minutes is validated by the manifest.
    std::int16_t offset_int16 = static_cast<std::int16_t>(offset_minutes);
    std::vector<std::uint16_t> data;
    data.push_back(static_cast<std::uint16_t>(offset_int16));
    p_modbus_transport->write_multiple_registers(MODBUS_TIMEZONE_OFFSET_ADDRESS, data);

    EVLOG_info << "Timezone set to: " << (offset_minutes >= 0 ? "+" : "") << offset_minutes << " minutes";
}

void powermeterImpl::time_sync_thread() {
    const auto sync_interval = std::chrono::hours(1);
    auto next_sync_time = std::chrono::steady_clock::now() + sync_interval;

    while (!stop_requested_.load()) {
        {
            std::unique_lock<std::mutex> lock(stop_mutex_);
            stop_cv_.wait_until(lock, next_sync_time, [this] { return stop_requested_.load(); });
        }
        if (stop_requested_.load()) {
            break;
        }

        if (!is_transaction_active()) {
            // No active transaction, perform time sync immediately
            try {
                synchronize_time();
                m_pending_time_sync = false;
            } catch (const std::exception& e) {
                EVLOG_error << "Time synchronization failed: " << e.what();
                // Mark as pending to retry when transaction ends
                m_pending_time_sync = true;
            }
        } else {
            // Transaction is active, mark sync as pending
            EVLOG_info << "Time synchronization deferred: charging session in progress";
            m_pending_time_sync = true;
        }

        // Schedule next sync attempt in 1 hour
        next_sync_time += sync_interval;
    }
}

void powermeterImpl::read_device_state() {
    // Read device state register (Table 4.30, Section 4.3.6)
    // Register 320499 (5012h): Device state (UINT16 bitfield)
    transport::DataVector state_data = p_modbus_transport->fetch(MODBUS_DEVICE_STATE_ADDRESS, 1);
    std::uint16_t device_state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});

    // Check for error bits and raise VendorError if any are set
    const auto error_messages = device_state_utils::decode_device_state_errors(device_state);

    // If any error bits are set, raise VendorError
    if (!error_messages.empty()) {
        std::string error_description = "Device state errors detected: ";
        for (size_t i = 0; i < error_messages.size(); ++i) {
            if (i > 0) {
                error_description += ", ";
            }
            error_description += error_messages[i];
        }
        error_description += " (device state: 0x" + fmt::format("{:04X}", device_state) + ")";

        EVLOG_error << "Device state error: " << error_description;
        auto error = error_factory->create_error("powermeter/VendorError", "DeviceStateError", error_description);
        raise_error(error);
    } else {
        EVLOG_debug << "Device state OK (0x" << fmt::format("{:04X}", device_state) << ")";
    }
}

types::units_signed::SignedMeterValue powermeterImpl::read_signed_meter_value() {
    transport::DataVector signed_data = p_modbus_transport->fetch(MODBUS_SIGNED_MAP_ADDRESS, m_signed_map_word_count);

    // Spec (Table 4.4): signed data spans 61 words (302049..302109) and signature starts at 302110.
    static constexpr std::size_t SIGNED_MAP_SIGNED_DATA_WORDS = 61;
    static constexpr std::size_t SIGNED_MAP_SIGNED_DATA_BYTES = SIGNED_MAP_SIGNED_DATA_WORDS * 2;
    const std::size_t signature_bytes =
        (m_signed_map_word_count > SIGNED_MAP_SIGNED_DATA_WORDS)
            ? (static_cast<std::size_t>(m_signed_map_word_count) - SIGNED_MAP_SIGNED_DATA_WORDS) * 2
            : 0;

    std::string signed_data_string = modbus_utils::to_hex_string(
        signed_data, modbus_utils::ByteOffset{0}, modbus_utils::ByteLength{SIGNED_MAP_SIGNED_DATA_BYTES});
    std::string signed_data_signature_string = modbus_utils::to_hex_string(
        signed_data, modbus_utils::ByteOffset{SIGNED_MAP_SIGNED_DATA_BYTES}, modbus_utils::ByteLength{signature_bytes});

    types::units_signed::SignedMeterValue smv;
    smv.signed_meter_data =
        R"({"signedData":")" + signed_data_string + R"(","signature":")" + signed_data_signature_string + R"("})";
    smv.signing_method = m_signature_method_string;
    smv.encoding_method = "plain";
    smv.public_key = m_public_key_hex;
    smv.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
    return smv;
}

} // namespace module::main
