/*
 * 文 件 名:  Keka86BsHandler
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  <描述>
 * 修 改 人:  lvwenbin
 * 修改时间:  2025/4/15
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.ruoyi.ems.handle;

import com.huashe.common.exception.Assert;
import com.ruoyi.ems.config.EmsConfig;
import com.ruoyi.ems.core.EmsApiTemplate;
import com.ruoyi.ems.core.MqttTemplate;
import com.ruoyi.ems.domain.EmsDevice;
import com.ruoyi.ems.domain.EmsObjAttrValue;
import com.ruoyi.ems.model.AbilityPayload;
import com.ruoyi.ems.model.CallResponse;
import com.ruoyi.ems.model.ModbusCommand;
import com.ruoyi.ems.model.QueryDevice;
import com.ruoyi.ems.model.keka.DeviceStatus;
import org.apache.commons.collections4.CollectionUtils;
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.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 86开关对接处理
 * <功能详细描述>
 *
 * @author lvwenbin
 * @version [版本号, 2025/4/15]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service
public class Keka86BsHandler extends BaseDevHandler {
    private static final Logger log = LoggerFactory.getLogger(Keka86BsHandler.class);

    @Resource
    @Qualifier("mqttTemplate")
    protected MqttTemplate mqttTemplate;

    @Autowired
    private EmsConfig emsConfig;

    private static final String TOPIC_PREFIX = "/sc/dtu/ctl/";
    private static final String MODE_CODE = "M_W2_QS_KEKA_86";

    private static final byte FUNC_READ = 0x03;
    private static final byte FUNC_WRITE = 0x06;
    private static final byte DEVICE_ADDR = 0x01;

    // 单按键控制寄存器基地址 (协议: 44129=0x1021)
    private static final int LIGHT_BASE_ADDR = 0x1021;

    private static final int VALUE_ON = 0x0001;   // 闭合/开
    private static final int VALUE_OFF = 0x0000;  // 断开/关

    // 修正：读取单个寄存器只需要1个
    private static final int READ_COUNT = 0x0001;

    // ========== 新增：用于追踪待处理的读取请求 ==========
    // Key: gatewayId, Value: 最近请求的buttonId
    private final Map<String, Integer> pendingReadRequests = new ConcurrentHashMap<>();

    @Override
    public CallResponse<Void> call(AbilityPayload abilityParam) {
        CallResponse<Void> callResponse = null;

        List<EmsObjAttrValue> attrList = objAttrValueService.selectByObjCode(MODE_CODE, abilityParam.getObjCode());
        Assert.notEmpty(attrList, -1, "设备协议属性未配置!");
        Map<String, String> attrMap = attrList.stream()
            .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, EmsObjAttrValue::getAttrValue));

        String gatewayId = attrMap.get("gatewayId");
        String buttonId = attrMap.get("buttonId");

        if (StringUtils.equals("on-off", abilityParam.getAbilityKey())) {
            ModbusCommand command = buildControlCommand(Integer.parseInt(buttonId),
                Integer.parseInt(abilityParam.getAbilityParam()));
            String topic = TOPIC_PREFIX + gatewayId;
            sendMqttHex(topic, command);
            saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
            callResponse = new CallResponse<>(0, "执行成功!");
        }
        else if (StringUtils.equals("syncState", abilityParam.getAbilityKey())) {
            int btnId = Integer.parseInt(buttonId);
            ModbusCommand command = buildReadCommand(btnId);
            String topic = TOPIC_PREFIX + gatewayId;

            // 记录待处理的读取请求
            pendingReadRequests.put(gatewayId + "_" + getRegisterAddr(btnId), btnId);

            sendMqttHex(topic, command);
            saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
            callResponse = new CallResponse<>(0, "执行成功!");
        }
        else {
            callResponse = new CallResponse<>(-1, "不支持的能力键!");
        }

        return callResponse;
    }

    @Override
    public List<EmsDevice> getDeviceList() {
        QueryDevice queryDevice = new QueryDevice();
        queryDevice.setDeviceModel(MODE_CODE);
        return deviceService.selectList(queryDevice);
    }

    @Override
    public void refreshOnline() {
    }

    @Async("msgHandleExecutor")
    public void msgHandle(String gatewayId, String payload) {
        try {
            log.info("[Keka86] 网关:{}, 收到消息:{}", gatewayId, payload);

            String[] hexBytes = payload.trim().split("\\s+");
            if (hexBytes.length < 5) {
                log.warn("[Keka86] 消息长度不足,忽略: {}", payload);
                return;
            }

            int funcCode = Integer.parseInt(hexBytes[1].toUpperCase(), 16);

            if (funcCode == 0x03) {
                handleReadResponse(gatewayId, payload);
            }
            else if (funcCode == 0x06) {
                handleWriteResponse(gatewayId, payload);
            }
            else if ((funcCode & 0x80) != 0) {
                byte originalFuncCode = (byte) (funcCode & 0x7F);
                handleErrorResponse(gatewayId, payload, originalFuncCode);
            }
            else {
                log.warn("[Keka86] 未知功能码: 0x%02X, 消息: {}", funcCode, payload);
            }

        }
        catch (Exception e) {
            log.error("[Keka86] 网关:{}, 消息处理异常", gatewayId, e);
        }
    }

    private String getDeviceCode(String gatewayId, int buttonId) {
        List<EmsObjAttrValue> list = objAttrValueService
            .selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);

        if (CollectionUtils.isNotEmpty(list)) {
            for (EmsObjAttrValue gatewayAttr : list) {
                EmsObjAttrValue buttonAttr = objAttrValueService
                    .selectObjAttrValue(MODE_CODE, gatewayAttr.getObjCode(), "buttonId");

                if (buttonAttr != null
                    && StringUtils.equals(buttonAttr.getAttrValue(), String.valueOf(buttonId))) {
                    return gatewayAttr.getObjCode();
                }
            }
        }
        return null;
    }

    /**
     * 生成控制指令 (开/关)
     */
    public ModbusCommand buildControlCommand(int buttonId, int state) {
        if (buttonId < 1 || buttonId > 6) {
            throw new IllegalArgumentException("按键ID必须是1-6");
        }
        if (state != 0 && state != 1) {
            throw new IllegalArgumentException("状态必须是0(关)或1(开)");
        }

        int registerAddr = getRegisterAddr(buttonId);
        int value = (state == 1) ? VALUE_ON : VALUE_OFF;

        byte[] frame = new byte[6];
        frame[0] = DEVICE_ADDR;
        frame[1] = FUNC_WRITE;
        frame[2] = (byte) (registerAddr >> 8);
        frame[3] = (byte) (registerAddr & 0xFF);
        frame[4] = (byte) (value >> 8);
        frame[5] = (byte) (value & 0xFF);

        int crc = calculateCRC16(frame);
        byte[] fullFrame = new byte[8];
        System.arraycopy(frame, 0, fullFrame, 0, 6);
        fullFrame[6] = (byte) (crc & 0xFF);
        fullFrame[7] = (byte) (crc >> 8);

        String hexString = bytesToHexString(fullFrame);
        log.debug("[Keka86] 构建控制指令: 按键{}, 状态{}, 寄存器0x{}, 指令:{}",
            buttonId, state == 1 ? "开" : "关", String.format("%04X", registerAddr), hexString);
        return new ModbusCommand(fullFrame, hexString);
    }

    /**
     * 生成读取指令
     * 读取单个按键状态，寄存器数量=1
     */
    public ModbusCommand buildReadCommand(int buttonId) {
        if (buttonId < 1 || buttonId > 6) {
            throw new IllegalArgumentException("按键ID必须是1-6");
        }

        int registerAddr = getRegisterAddr(buttonId);

        byte[] frame = new byte[6];
        frame[0] = DEVICE_ADDR;
        frame[1] = FUNC_READ;
        frame[2] = (byte) (registerAddr >> 8);
        frame[3] = (byte) (registerAddr & 0xFF);
        frame[4] = (byte) (READ_COUNT >> 8);    // 0x00
        frame[5] = (byte) (READ_COUNT & 0xFF);  // 0x01 (读1个寄存器)

        int crc = calculateCRC16(frame);
        byte[] fullFrame = new byte[8];
        System.arraycopy(frame, 0, fullFrame, 0, 6);
        fullFrame[6] = (byte) (crc & 0xFF);
        fullFrame[7] = (byte) (crc >> 8);

        String hexString = bytesToHexString(fullFrame);
        log.debug("[Keka86] 构建读取指令: 按键{}, 寄存器0x{}, 指令:{}",
            buttonId, String.format("%04X", registerAddr), hexString);
        return new ModbusCommand(fullFrame, hexString);
    }

    /**
     * 解析读取响应
     * 响应格式: [设备地址][功能码][字节数][数据...][CRC_L][CRC_H]
     * 根据协议文档2.1节:
     * - 字节1: 设备地址
     * - 字节2: 03H (功能码)
     * - 字节3: 返回数据字节个数
     * - 字节4-5: 返回第一个寄存器数据高字节+低字节
     * - 字节6-7: 返回第二个寄存器数据高字节+低字节
     * - 字节8-9: CRC低+CRC高
     *
     * @param hexResponse 十六进制响应字符串
     * @param lightId     灯ID (用于返回完整状态信息)
     * @return DeviceStatus 设备状态对象
     */
    public DeviceStatus parseReadResponse(String hexResponse, int lightId) {
        byte[] response = hexStringToBytes(hexResponse);

        if (response.length < 7) {
            throw new IllegalArgumentException("响应数据长度不足");
        }

        // 验证CRC
        byte[] dataWithoutCrc = new byte[response.length - 2];
        System.arraycopy(response, 0, dataWithoutCrc, 0, response.length - 2);
        int calculatedCrc = calculateCRC16(dataWithoutCrc);
        int receivedCrc = (response[response.length - 2] & 0xFF) | ((response[response.length - 1] & 0xFF) << 8);

        if (calculatedCrc != receivedCrc) {
            throw new IllegalArgumentException(
                String.format("CRC校验失败: 计算值=0x%04X, 接收值=0x%04X", calculatedCrc, receivedCrc));
        }

        // 解析数据
        byte deviceAddr = response[0];
        byte funcCode = response[1];
        byte byteCount = response[2];

        if (funcCode != FUNC_READ) {
            // 检查是否是错误响应 (功能码最高位为1表示错误)
            if ((funcCode & 0x80) != 0) {
                byte errorCode = response[2];
                String errorMsg = getModbusErrorMessage(errorCode);
                throw new IllegalArgumentException("Modbus错误响应: " + errorMsg);
            }
            throw new IllegalArgumentException("功能码错误: 0x" + String.format("%02X", funcCode & 0xFF));
        }

        // 提取状态数据 (第一个寄存器的值)
        int value = ((response[3] & 0xFF) << 8) | (response[4] & 0xFF);

        // 解析状态: 0x0001=开, 0x0000=关
        int status = (value == VALUE_ON) ? 1 : 0;

        return new DeviceStatus(deviceAddr & 0xFF, lightId, status, hexResponse);
    }

    /**
     * 获取按键对应的寄存器地址
     */
    private int getRegisterAddr(int buttonId) {
        return LIGHT_BASE_ADDR + (buttonId - 1);
    }

    /**
     * 根据寄存器地址反推按键ID
     */
    private int getButtonIdFromAddr(int registerAddr) {
        return registerAddr - LIGHT_BASE_ADDR + 1;
    }

    /**
     * 获取Modbus错误信息
     */
    private static String getModbusErrorMessage(byte errorCode) {
        switch (errorCode) {
            case 0x01:
                return "01H - 功能码错误";
            case 0x02:
                return "02H - 非法数据地址";
            case 0x03:
                return "03H - 非法数据值";
            case 0x04:
                return "04H - 设备故障";
            case 0x05:
                return "05H - 查询帧正确,但设备正在处理";
            case 0x06:
                return "06H - 设备忙";
            default:
                return String.format("未知错误码: 0x%02X", errorCode & 0xFF);
        }
    }

    /**
     * 计算Modbus CRC-16
     * 多项式: 0xA001 (反向)
     */
    private static int calculateCRC16(byte[] data) {
        int crc = 0xFFFF;
        for (byte b : data) {
            crc ^= (b & 0xFF);
            for (int i = 0; i < 8; i++) {
                if ((crc & 0x0001) != 0) {
                    crc >>= 1;
                    crc ^= 0xA001;
                }
                else {
                    crc >>= 1;
                }
            }
        }
        return crc;
    }

    /**
     * 字节数组转十六进制字符串
     */
    private static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (i > 0)
                sb.append(" ");
            sb.append(String.format("%02X", bytes[i] & 0xFF));
        }
        return sb.toString();
    }

    /**
     * 十六进制字符串转字节数组
     */
    private static byte[] hexStringToBytes(String hexString) {
        String[] hexArray = hexString.trim().split("\\s+");
        byte[] bytes = new byte[hexArray.length];
        for (int i = 0; i < hexArray.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hexArray[i], 16);
        }
        return bytes;
    }

    /**
     * 发送MQTT消息 - 优化版本
     *
     * @param topic   主题
     * @param command Modbus指令对象 (包含字节数组和十六进制字符串)
     */
    public void sendMqttHex(String topic, ModbusCommand command) {
        log.info("[Send] Topic:{}, message:{}, qos:{}, retained:{}", topic, command.getCommandHex(), 2, false);
        mqttTemplate.sendHex(topic, command.getCommandBytes(), 2, false);
    }

    /**
     * 处理读取响应 (功能码 03H)
     *
     * 响应格式 (读取1个寄存器):
     * [0] 设备地址: 01
     * [1] 功能码:   03
     * [2] 字节数:   02 (1个寄存器=2字节)
     * [3] 数据高:   00
     * [4] 数据低:   01 或 00
     * [5] CRC低
     * [6] CRC高
     *
     * 示例: 01 03 02 00 01 79 84 表示状态=开
     *       01 03 02 00 00 B8 44 表示状态=关
     */
    private void handleReadResponse(String gatewayId, String hexMessage) {
        try {
            byte[] response = hexStringToBytes(hexMessage);

            // 最小长度: 地址(1) + 功能码(1) + 字节数(1) + 数据(2) + CRC(2) = 7
            if (response.length < 7) {
                log.warn("[Keka86-Read] 响应长度不足: {}", hexMessage);
                return;
            }

            // CRC校验
            byte[] dataWithoutCrc = new byte[response.length - 2];
            System.arraycopy(response, 0, dataWithoutCrc, 0, response.length - 2);
            int calculatedCrc = calculateCRC16(dataWithoutCrc);
            int receivedCrc = (response[response.length - 2] & 0xFF)
                | ((response[response.length - 1] & 0xFF) << 8);

            if (calculatedCrc != receivedCrc) {
                log.error("[Keka86-Read] CRC校验失败, 计算:0x{}, 接收:0x{}, Hex:{}",
                    String.format("%04X", calculatedCrc),
                    String.format("%04X", receivedCrc), hexMessage);
                return;
            }

            // 解析响应数据
            int deviceAddr = response[0] & 0xFF;
            int funcCode = response[1] & 0xFF;
            int byteCount = response[2] & 0xFF;

            if (funcCode != 0x03) {
                log.warn("[Keka86-Read] 功能码不匹配: 0x{}", String.format("%02X", funcCode));
                return;
            }

            // 提取寄存器值 (2字节)
            int dataValue = ((response[3] & 0xFF) << 8) | (response[4] & 0xFF);
            int status = (dataValue == VALUE_ON) ? 1 : 0;

            log.info("[Keka86-Read] 网关:{}, 设备地址:{}, 字节数:{}, 数据值:0x{}, 状态:{}",
                gatewayId, deviceAddr, byteCount,
                String.format("%04X", dataValue), status == 1 ? "开" : "关");

            // ========== 关键修复：确定是哪个按键的响应 ==========
            // 方案1: 从pendingReadRequests中查找 (需要记录发送时的按键)
            // 方案2: 遍历所有按键配置，更新匹配的设备

            // 这里采用方案2: 查找该网关下所有按键设备并更新
            // 实际生产中建议用方案1，通过请求-响应匹配

            List<EmsObjAttrValue> gatewayDevices = objAttrValueService
                .selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);

            if (CollectionUtils.isEmpty(gatewayDevices)) {
                log.warn("[Keka86-Read] 网关:{} 无注册设备", gatewayId);
                return;
            }

            // 检查是否有匹配的待处理请求
            for (EmsObjAttrValue gw : gatewayDevices) {
                String deviceCode = gw.getObjCode();
                EmsObjAttrValue btnAttr = objAttrValueService
                    .selectObjAttrValue(MODE_CODE, deviceCode, "buttonId");

                if (btnAttr != null) {
                    int buttonId = Integer.parseInt(btnAttr.getAttrValue());
                    int registerAddr = getRegisterAddr(buttonId);
                    String requestKey = gatewayId + "_" + registerAddr;

                    // 检查是否是该按键的响应
                    if (pendingReadRequests.containsKey(requestKey)) {
                        pendingReadRequests.remove(requestKey);

                        DeviceStatus deviceStatus = new DeviceStatus(
                            deviceAddr, buttonId, status, hexMessage);
                        updateDeviceStatus(deviceCode, deviceStatus);

                        log.info("[Keka86-Read] 更新设备:{}, 按键:{}, 状态:{}",
                            deviceCode, buttonId, status == 1 ? "开" : "关");
                        return; // 找到匹配的请求，处理完毕
                    }
                }
            }

            // 如果没有待处理请求匹配，可能是设备主动上报
            log.info("[Keka86-Read] 网关:{} 收到状态上报(可能是主动上报), 状态:{}",
                gatewayId, status == 1 ? "开" : "关");

        }
        catch (Exception e) {
            log.error("[Keka86-Read] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
        }
    }

    /**
     * 处理写入响应 (功能码 06H)
     *
     * 响应格式 (写入确认 - 回显请求):
     * [0] 设备地址: 01
     * [1] 功能码:   06
     * [2] 寄存器高: 10
     * [3] 寄存器低: 21 (0x1021 = 按键1)
     * [4] 数据高:   00
     * [5] 数据低:   01 或 00
     * [6] CRC低
     * [7] CRC高
     *
     * 示例: 01 06 10 21 00 01 DD 00 表示按键1设置为开
     */
    private void handleWriteResponse(String gatewayId, String hexMessage) {
        try {
            byte[] response = hexStringToBytes(hexMessage);

            if (response.length < 8) {
                log.warn("[Keka86-Write] 响应长度不足: {}", hexMessage);
                return;
            }

            // CRC校验
            byte[] dataWithoutCrc = new byte[6];
            System.arraycopy(response, 0, dataWithoutCrc, 0, 6);
            int calculatedCrc = calculateCRC16(dataWithoutCrc);
            int receivedCrc = (response[6] & 0xFF) | ((response[7] & 0xFF) << 8);

            if (calculatedCrc != receivedCrc) {
                log.error("[Keka86-Write] CRC校验失败, 网关:{}, Hex:{}", gatewayId, hexMessage);
                return;
            }

            // 解析寄存器地址和值
            int registerAddr = ((response[2] & 0xFF) << 8) | (response[3] & 0xFF);
            int dataValue = ((response[4] & 0xFF) << 8) | (response[5] & 0xFF);

            // 根据寄存器地址推断按键ID
            int buttonId = getButtonIdFromAddr(registerAddr);
            int status = (dataValue == VALUE_ON) ? 1 : 0;

            log.info("[Keka86-Write] 网关:{}, 写入确认, 寄存器:0x{}, 按键:{}, 状态:{}",
                gatewayId, String.format("%04X", registerAddr),
                buttonId, status == 1 ? "开" : "关");

            // 更新数据库状态
            String deviceCode = getDeviceCode(gatewayId, buttonId);
            if (deviceCode != null) {
                DeviceStatus deviceStatus = new DeviceStatus(
                    response[0] & 0xFF, buttonId, status, hexMessage);
                updateDeviceStatus(deviceCode, deviceStatus);
            }
            else {
                log.warn("[Keka86-Write] 网关:{}, 按键{} 未找到对应设备", gatewayId, buttonId);
            }

        }
        catch (Exception e) {
            log.error("[Keka86-Write] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
        }
    }

    /**
     * 处理错误响应
     * 响应格式: 01 83 01 CRC_L CRC_H (读取错误)
     * 01 86 01 CRC_L CRC_H (写入错误)
     */
    private void handleErrorResponse(String gatewayId, String hexMessage, byte originalFuncCode) {
        try {
            byte[] response = hexStringToBytes(hexMessage);
            if (response.length >= 3) {
                byte errorCode = response[2];
                String errorMsg = getModbusErrorMessage(errorCode);

                String funcName = (originalFuncCode == 0x03) ? "读取" : (originalFuncCode == 0x06) ? "写入" : "未知";

                log.error("[Keka86-Error] 网关:{}, {}操作失败, 错误:{}, Hex:{}", gatewayId, funcName, errorMsg,
                    hexMessage);
            }
        }
        catch (Exception e) {
            log.error("[Keka86-Error] 网关:{}, 错误响应解析失败, Hex:{}", gatewayId, hexMessage, e);
        }
    }

    /**
     * 更新设备状态到数据库
     */
    private void updateDeviceStatus(String deviceCode, DeviceStatus deviceStatus) {
        try {
            EmsObjAttrValue value = objAttrValueService.selectObjAttrValue(MODE_CODE, deviceCode, "Switch");
            String newValue = String.valueOf(deviceStatus.getStatus());

            if (null != value && !StringUtils.equals(value.getAttrValue(), newValue)) {
                objAttrValueService.updateObjAttrValue(MODE_CODE, deviceCode, "Switch", newValue);
                new EmsApiTemplate(emsConfig).notifyAttrValueChangedAsync(deviceCode, "Switch", value.getAttrValue(),
                    newValue);
                log.info("[Keka86-Update] 设备:{}, 状态更新: {} -> {}", deviceCode, value.getAttrValue(), newValue);
            }
            else if (null == value) {
                value = new EmsObjAttrValue(deviceCode, MODE_CODE, "Switch", newValue);
                objAttrValueService.mergeObjAttrValue(value);
                log.info("[Keka86-Update] 设备:{}, 状态新增: {}", deviceCode, newValue);
            }
        }
        catch (Exception e) {
            log.error("[Keka86-Update] 设备:{}, 状态更新失败", deviceCode, e);
        }
    }
}