package com.xzzn.common.core.modbus;

import com.alibaba.fastjson2.JSON;
import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
import com.serotonin.modbus4j.msg.WriteCoilsResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersResponse;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.TagConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.ModBusType;
import com.xzzn.common.enums.RegisterType;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import static com.xzzn.common.enums.RegisterType.COIL;
import static com.xzzn.common.enums.RegisterType.DISCRETE_INPUT;

@Service
public class ModbusProcessor {

    private static final Logger logger = LoggerFactory.getLogger(ModbusProcessor.class);
    @Value("${modbus.read-timeout:8000}")
    private int readTimeout;
    @Value("${modbus.write-timeout:5000}")
    private int writeTimeout;
    @Value("${modbus.read-retries:1}")
    private int readRetries;
    @Value("${modbus.write-retries:1}")
    private int writeRetries;

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private Modbus4jConnectionManager connectionManager;

    public boolean writeDataToDevice(DeviceConfig config) {
        logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
        ModbusMaster master = null;
        boolean result = false;
        boolean hasError = false;
        try {
            master = connectionManager.borrowMaster(config);
            master.setTimeout(writeTimeout);
            master.setRetries(0);
            result = writeTagValue(master, config, config.getWriteTags());
        } catch (Exception e) {
            logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
            hasError = true;
        }
        finally {
            if (master != null) {
                if (hasError) {
                    // 发生异常时废弃连接，下次重新创建
                    connectionManager.invalidateMaster(config, master);
                } else {
                    // 正常时归还连接
                    connectionManager.returnMaster(config, master);
                }
            }
        }
        return result;
    }

    public boolean writeDataToDeviceWithRetry(DeviceConfig config) {
        logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
        ModbusMaster master = null;
        boolean result;

        try {
            master = connectionManager.borrowMaster(config);
            master.setTimeout(writeTimeout); // 设置超时时间
            master.setRetries(0);

            // 使用重试装饰器
            ModbusMaster finalMaster = master;
            result = RetryableModbusOperation.executeWithRetry(() -> {
                return writeTagValue(finalMaster, config, config.getWriteTags());
            }, writeRetries); // 最大重试次数由配置控制

        } catch (Exception e) {
            logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
            result = false;
        } finally {
            if (master != null) {
                connectionManager.returnMaster(config, master);
            }
        }
        return result;
    }

    public boolean writeTagValue(ModbusMaster master, DeviceConfig config, List<WriteTagConfig> tags) {
        tags.forEach(tag -> {
            Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
            int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
            int address = convertAddress(tag.getAddress());
            RegisterType registerType = type.get(firstDigit);
            logger.info("Register type: {}, address: {}, firstDigit: {}", registerType, tag.getAddress(), firstDigit);
            switch (registerType) {
                case COIL: {
                    boolean result = writeCoilRequest(master, config.getSlaveId(), address, Boolean.parseBoolean(String.valueOf(tag.getValue())));
                    tag.setWrite(result);
                    break;
                }
                case HOLDING_REGISTER: {
                    Number value = Double.parseDouble(String.valueOf(tag.getValue()));
                    double doubleValue = value.doubleValue();
                    // 检查是否在16位有符号整数范围内
                    if (doubleValue < -32768 || doubleValue > 32767) {
                        logger.warn("Value {} out of range for 16-bit signed register at address {}", doubleValue, address);
                    }
                    boolean result = writeRegisterRequest(master, config.getSlaveId(), address, (int) doubleValue);
                    tag.setWrite(result);
                    break;
                }
                default:
                    logger.error("Unsupported register type: {}", registerType);
                    break;
            }
        });

        List<WriteTagConfig> collect = tags.stream().filter(tag -> !Objects.equals(tag.isWrite(), true)).collect(Collectors.toList());
        if (!collect.isEmpty()) {
            return false;
        }
        return true;
    }

    public static boolean writeCoilRequest(ModbusMaster master, int slaveId, int address, boolean value)  {
        try {
            WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value);
            WriteCoilResponse response = (WriteCoilResponse)master.send(request);
            if (response.isException()) {
                logger.info("Write coil failed: " + response.getExceptionMessage());
            } else {
                logger.info("Write coil successful");
                return true;
            }
        } catch (Exception e) {
            logger.error("Failed to write coil value '{}' to address '{}'", value, address, e);
        }
        return false;
    }

    public static void writeCoilsRequest(ModbusMaster master, int slaveId, int address, boolean[] values) {
        try {
            WriteCoilsRequest request = new WriteCoilsRequest(slaveId, address, values);
            WriteCoilsResponse response = (WriteCoilsResponse)master.send(request);
            if (response.isException()) {
                logger.info("Write coils failed: " + response.getExceptionMessage());
            } else {
                logger.info("Write coils successful");
            }
        } catch (Exception e) {
            logger.error("Failed to write coils value '{}' to address '{}'", values, address, e);
        }
    }

    public static boolean writeRegisterRequest(ModbusMaster master, int slaveId, int address, int value) {
        try {
            WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value);
            WriteRegisterResponse response = (WriteRegisterResponse)master.send(request);
            if (response.isException()) {
                logger.info("Write register failed: " + response.getExceptionMessage());
            } else {
                logger.info("Write register successful");
                return true;
            }
        } catch (Exception e) {
            logger.error("Failed to write register value '{}' to address '{}'", value, address, e);
        }
        return false;
    }

    public static void writeRegistersRequest(ModbusMaster master, int slaveId, int address, short[] values) {
        try {
            WriteRegistersRequest request = new WriteRegistersRequest(slaveId, address, values);
            WriteRegistersResponse response = (WriteRegistersResponse)master.send(request);
            if (response.isException()) {
                logger.info("Write registers failed: " + response.getExceptionMessage());
            } else {
                logger.info("Write registers successful");
            }
        } catch (Exception e) {
            logger.error("Failed to write registers value '{}' to address '{}'", values, address, e);
        }
    }

    public static void setCoilValue(ModbusMaster master, int slaveId, int address, int value) {
        // 写入单个线圈(从站ID, 地址, 布尔值)
        BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, address);
        try {
            // 写入布尔值到线圈
            master.setValue(coilLocator, value);
            logger.info("set coil successful");
        } catch (ModbusTransportException | ErrorResponseException e) {
            logger.error("Failed to set coil value '{}' to address '{}'", value, address, e);
        }
    }

    public static void setRegisterValue(ModbusMaster master, int slaveId, int address, int value) {
        // 写入单个保持寄存器(从站, 地址, 16位整数)
        BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, address, DataType.TWO_BYTE_INT_SIGNED);
        try {
            // 写入整数值到保持寄存器
            master.setValue(holdingLocator, value);
            logger.info("set register successful");
        } catch (ModbusTransportException | ErrorResponseException e) {
            logger.error("Failed to set register value '{}' to address '{}'", value, address, e);
        }
    }

    public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
        ModbusMaster master = connectionManager.borrowMaster(config);
        // 设置了Modbus通信的超时时间为5000毫秒（5秒）。当主设备与从设备通信时，若在5秒内未收到响应，则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
        master.setTimeout(readTimeout);
        master.setRetries(readRetries);
        return master;
    }

    public Map<String, Object> readDataFromDevice(DeviceConfig config, ModbusMaster master) {
        Map<String, Object> deviceData = new HashMap<>();
        boolean hasError = false;
        try {
            BatchResults<String> results = readTagValues(master, config.getSlaveId(), config.getTags());
            for (TagConfig tag : config.getTags()) {
                if (Objects.equals(tag.getDataType(), "FOUR_BYTE_FLOAT_DBCA")){
                    Object value = results.getValue(tag.getKey());
                    value = convertValueToFloat(value);
                    deviceData.put(tag.getKey(), value);
                }else {
                    deviceData.put(tag.getKey(), results.getValue(tag.getKey()));
                }
            }
        } catch (Exception e) {
            logger.error("Failed read from devices '{}'", config.getDeviceName(), e);
            hasError = true;
        }
        finally {
            if (master != null) {
                if (hasError) {
                    // 发生异常时废弃连接，下次重新创建
                    connectionManager.invalidateMaster(config, master);
                } else {
                    // 正常时归还连接
                    connectionManager.returnMaster(config, master);
                }
            }
        }
        return deviceData;
    }

    private Object readTagValue(ModbusMaster master, int slaveId, TagConfig tag) throws Exception {
        Object value;
        Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
        Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
        int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
        int address = 0;
        int addressLength = tag.getAddress().length();
        int exp = (int) Math.pow(10, addressLength-1);
        if (firstDigit != 0){
            int digit =  Integer.parseInt(tag.getAddress());
            address = digit % (exp);
        }else {
            address = Integer.parseInt(tag.getAddress());
        }
        RegisterType registerType = type.get(firstDigit);
        int dataLength = DATA_LENGTH.get(tag.getDataType());
        // 增加配置校验和警告
        if ((Objects.equals(registerType, COIL) || Objects.equals(registerType, DISCRETE_INPUT))) {
            logger.warn("Configuration warning for tag '{}': ModBusType is {} but DataType is {}. DataType should be BOOLEAN. Proceeding with BOOLEAN read.",
                    tag.getKey(), registerType, tag.getDataType());
        }
        try {
            switch (registerType) {
                case COIL: {

                    BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
                    value = master.getValue(loc);
                    logger.info("读取线圈{}成功: {}",tag.getAddress(), value);
                    break;
                }
                case DISCRETE_INPUT: {
                    BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
                    value = master.getValue(loc);
                    logger.info("读取输入{}成功: {}",tag.getAddress(), value);
                    break;
                }
                case HOLDING_REGISTER: {
                    if (dataLength == 28){
                        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
                        value = master.getValue(locator);
                        //将 value 转换为二进制数据
                        value = convertValueToFloat(value);
                    }else{
                        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, dataLength);
                        value = master.getValue(locator);
                    }
                    logger.info("读取保持寄存器{}成功: {}",tag.getAddress(), value);
                    break;
                }
                case INPUT_REGISTER: {
                    BaseLocator<Number> locator = BaseLocator.inputRegister(slaveId, address, dataLength);
                    value= master.getValue(locator);
                    logger.info("读取输入寄存器{}成功: {}",tag.getAddress(), value);


                    break;
                }
                default:
                    logger.error("Unsupported register type: {}", registerType);
                    value = null;
                    break;
            }
        }catch (Exception e){
            logger.error("Failed to read tag '{}'", tag.getKey(), e);
            value = null;
        }
        return value;
    }


    private BatchResults<String> readTagValues(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
        try {
            BatchResults<String> results = sendBatchRead(master, slaveId, tags);
            logBatchResults(tags, results);
            return results;
        } catch (Exception e){
            if (isTimeoutException(e)) {
                logger.warn("批量读取超时，尝试降级为单点读取，slaveId: {}", slaveId);
                BatchResults<String> fallback = new BatchResults<>();
                for (TagConfig tag : tags) {
                    Object value = readTagValue(master, slaveId, tag);
                    fallback.addResult(tag.getKey(), value);
                }
                logBatchResults(tags, fallback);
                return fallback;
            }
            logger.error("Failed to read master '{}'", slaveId, e);
            throw new Exception(e);
        }
    }

    private BatchResults<String> sendBatchRead(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
        BatchRead<String> batch = new BatchRead<>();
        tags.forEach(tag -> {
            Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
            Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
            int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
            int address = 0;
            int addressLength = tag.getAddress().length();
            int exp = (int) Math.pow(10, addressLength - 1);
            if (firstDigit != 0) {
                int digit = Integer.parseInt(tag.getAddress());
                address = digit % (exp);
            } else {
                address = Integer.parseInt(tag.getAddress());
            }
            RegisterType registerType = type.get(firstDigit);
            int dataLength = DATA_LENGTH.get(tag.getDataType());
            switch (registerType) {
                case COIL: {
                    BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
                    batch.addLocator(tag.getKey(), loc);
                    break;
                }
                case DISCRETE_INPUT: {
                    BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
                    batch.addLocator(tag.getKey(), loc);
                    break;
                }
                case HOLDING_REGISTER: {
                    if (dataLength == 28) {
                        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
                        batch.addLocator(tag.getKey(), locator);
                    } else {
                        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, address, dataLength);
                        batch.addLocator(tag.getKey(), loc);
                    }
                    break;
                }
                case INPUT_REGISTER: {
                    BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, address, dataLength);
                    batch.addLocator(tag.getKey(), loc);
                    break;
                }
            }
        });

        return master.send(batch);
    }

    private void logBatchResults(List<TagConfig> tags, BatchResults<String> results) {
        List<String> logInfoList = new ArrayList<>();
        for (TagConfig tag : tags) {
            StringBuilder logInfo = new StringBuilder();
            logInfo.append(tag.getAddress());
            if (tag.getBit() != null) {
                logInfo.append("(").append(tag.getBit()).append("):");
            } else {
                logInfo.append(":");
            }
            logInfo.append(results.getValue(tag.getKey()));
            logInfoList.add(logInfo.toString());
        }
        logger.info("批处理读取寄存器成功: {}", JSON.toJSONString(logInfoList));
    }

    private boolean isTimeoutException(Throwable e) {
        Throwable current = e;
        while (current != null) {
            if (current instanceof com.serotonin.modbus4j.sero.messaging.TimeoutException) {
                return true;
            }
            current = current.getCause();
        }
        return false;
    }

    public static float convertValueToFloat(Object value) {
        if (!(value instanceof Number)) {
            throw new IllegalArgumentException("Input must be a Number");
        }

        int intValue = ((Number) value).intValue();
        String binaryData = Integer.toBinaryString(intValue);

        // 补足32位二进制字符串
        if (binaryData.length() < 32) {
            binaryData = String.format("%32s", binaryData).replace(' ', '0');
        }

        // 分割为4个字节并重组为 dcba 格式
        byte[] reorderedBytes = new byte[4];
        for (int i = 0; i < 4; i++) {
            int start = i * 8;
            String byteStr = binaryData.substring(start, start + 8);
            reorderedBytes[3 - i] = (byte) Integer.parseInt(byteStr, 2);
        }

        // 使用 ByteBuffer 提高可读性和安全性
        return Float.intBitsToFloat(
                java.nio.ByteBuffer.wrap(reorderedBytes).getInt()
        );
    }

    /** 转换寄存器地址 */
    public static int convertAddress(String address) {
        if (StringUtils.isBlank(address)) {
            return 0;
        }
        int firstDigit = Integer.parseInt(address.substring(0, 1));
        int addressLength = address.length();
        int exp = (int) Math.pow(10, addressLength-1);
        if (firstDigit != 0){
            int digit =  Integer.parseInt(address);
            return digit % (exp);
        } else {
            return Integer.parseInt(address);
        }
    }
}
