package iot.lidee.iot.data.consumer;

import com.alibaba.fastjson2.JSON;
import iot.lidee.base.service.ISessionStore;
import iot.lidee.base.session.Session;
import iot.lidee.common.constant.LideeConstant;
import iot.lidee.common.core.mq.DeviceStatusBo;
import iot.lidee.common.core.mq.message.ModbusPollMsg;
import iot.lidee.common.core.protocol.Message;
import iot.lidee.common.core.redis.RedisCache;
import iot.lidee.common.core.redis.RedisKeyBuilder;
import iot.lidee.common.enums.DeviceStatus;
import iot.lidee.common.enums.ServerType;
import iot.lidee.common.enums.TopicType;
import iot.lidee.common.utils.DateUtils;
import iot.lidee.common.utils.StringUtils;
import iot.lidee.common.utils.gateway.mq.TopicsUtils;
import iot.lidee.iot.data.service.impl.MessageManager;
import iot.lidee.iot.domain.DeviceJob;
import iot.lidee.iot.domain.ModbusJob;
import iot.lidee.iot.domain.ModbusParams;
import iot.lidee.iot.enums.DeviceType;
import iot.lidee.iot.model.DeviceStatusVO;
import iot.lidee.iot.model.modbus.ModbusPollJob;
import iot.lidee.iot.service.IDeviceService;
import iot.lidee.iot.service.IModbusJobService;
import iot.lidee.iot.service.IModbusParamsService;
import iot.lidee.mq.producer.MessageProducer;
import iot.lidee.mqttclient.PubMqttClient;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 平台定时批量获取设备属性(或单个获取)
 *
 * @author bill
 */
@Slf4j
@Component
public class DevicePropFetchConsumer {


    @Autowired
    private PubMqttClient pubMqttClient;
    @Autowired
    private RedisCache redisCache;
    @Resource
    private MessageManager messageManager;
    @Resource
    private TopicsUtils topicsUtils;
    @Resource
    private IModbusJobService modbusJobService;
    @Resource
    private IDeviceService deviceService;
    @Resource
    private ISessionStore sessionStore;
    @Resource
    private IModbusParamsService modbusParamsService;

    @Value("${server.broker.enabled}")
    private Boolean enabled;
    //锁集合
    private final ConcurrentHashMap<String, Lock> taskLocks = new ConcurrentHashMap<>();

    @Async(LideeConstant.TASK.DEVICE_FETCH_PROP_TASK)
    public void consume(ModbusPollJob job) {
        Integer type = job.getType();
        DeviceJob deviceJob = job.getDeviceJob();
        ModbusPollMsg task = type == 1 ? job.getPollMsg() : getCommandList(deviceJob);
        if (CollectionUtils.isEmpty(task.getCommandList()))return;
        String serialNumber = task.getSerialNumber();
        //获取一个线程锁
        Lock lock = taskLocks.computeIfAbsent(serialNumber, k -> new ReentrantLock());
        //阻塞直到获取到锁
        lock.lock();
        try {
            Long productId = task.getProductId();
            List<String> commandList = task.getCommandList();
            ServerType serverType = ServerType.explain(task.getTransport());
            String topic = topicsUtils.buildTopic(productId, serialNumber, TopicType.FUNCTION_GET);
            for (String command : commandList) {
                String cacheKey = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber);
                redisCache.zSetAdd(cacheKey, command, DateUtils.getTimestampSeconds());
                switch (serverType) {
                    //通过mqtt内部客户端 下发指令
                    case MQTT:
                        publish(topic, ByteBufUtil.decodeHexDump(command));
                        log.info("=>MQTT-线程=[{}],轮询指令:[{}],主题:[{}]", Thread.currentThread().getName(), command, topic);
                        break;
                    //  下发TCP客户端
                    case TCP:
                        Message msg = new Message();
                        msg.setClientId(serialNumber);
                        msg.setPayload(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(command)));
                        messageManager.requestR(serialNumber, msg, Message.class);
                        log.info("=>TCP-线程=[{}],轮询指令:[{}]", Thread.currentThread().getName(), command);
                        break;
                }
                //指令间隔时间
                Thread.sleep(1000);
            }

        } catch (Exception e) {
            log.error("线程错误e", e);
        } finally {
            lock.unlock();
        }
    }

    private ModbusPollMsg getCommandList(DeviceJob deviceJob) {
        ModbusPollMsg modbusPollMsg = new ModbusPollMsg();
        Long jobId = deviceJob.getJobId();
        ModbusJob modbusJob = new ModbusJob();
        modbusJob.setJobId(jobId);
        modbusJob.setStatus("0");
        List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(modbusJob);
        if (CollectionUtils.isEmpty(modbusJobList))return modbusPollMsg;
        //处理子设备情况
        handleSubDeviceStatus(modbusJobList);
        List<String> commandList = modbusJobList.stream().map(ModbusJob::getCommand).collect(Collectors.toList());
        // modbus-tcp 重新处理事务标识符
        String subSerialNumber = modbusJobList.get(0).getSubSerialNumber();
        String tcpKey = RedisKeyBuilder.buildModbusTcpCacheKey(subSerialNumber);
        String tcpObject = redisCache.getStrCacheObject(tcpKey);
        List<String> tcpCommandList = new ArrayList<>();
        if (StringUtils.isNotEmpty(tcpObject)) {
            String tcpRuntimeCacheKey = RedisKeyBuilder.buildModbusTcpRuntimeCacheKey(subSerialNumber);
            for (String command : commandList) {
                String tranNo = command.substring(0, 4);
                int i = Integer.parseInt(tranNo, 16);
                Object stringHashValue = redisCache.getStringHashValue(tcpRuntimeCacheKey, String.valueOf(i));
                if (Objects.nonNull(stringHashValue)) {
                    tcpCommandList.add(command);
                    continue;
                }
                String modbusTcpId = redisCache.getCacheModbusTcpId(modbusJobList.get(0).getSubSerialNumber());
                String hexString = String.format("%04x", Integer.parseInt(modbusTcpId));
                String newTcpData = hexString + command.substring(4);
                redisCache.cacheModbusTcpData(subSerialNumber, modbusTcpId, newTcpData);
                tcpCommandList.add(newTcpData);
            }
        }
        modbusPollMsg.setSerialNumber(deviceJob.getSerialNumber());
        modbusPollMsg.setProductId(deviceJob.getProductId());
        if (CollectionUtils.isEmpty(tcpCommandList)) {
            modbusPollMsg.setCommandList(commandList);
        } else {
            modbusPollMsg.setCommandList(tcpCommandList);
        }
        DeviceStatusVO deviceStatusVO = deviceService.selectDeviceStatusAndTransportStatus(modbusPollMsg.getSerialNumber());
        modbusPollMsg.setTransport(deviceStatusVO.getTransport());
        Session session = sessionStore.getSession(modbusPollMsg.getSerialNumber());
        if (enabled && Objects.isNull(session)){
            log.info("设备：[{}],不在线",modbusPollMsg.getSerialNumber());
            return modbusPollMsg;
        }
        log.info("执行modbus轮询指令:[{}]", JSON.toJSONString(commandList));
        return modbusPollMsg;
    }


    /**
     * 处理子设备根据设备数据定时更新状态
     */
    public  void handleSubDeviceStatus(List<ModbusJob> modbusJobList){
        for (ModbusJob modbusJob : modbusJobList) {
            String subSerialNumber = modbusJob.getSubSerialNumber();
            Session session = sessionStore.getSession(subSerialNumber);
            if (Objects.isNull(session))continue;
            //如果是网关子设备，则检测子设备是否在特定时间内有数据上报
            if (modbusJob.getDeviceType() == DeviceType.SUB_GATEWAY.getCode()){
                long deterTime = 300L; //这里默认使用5分钟,如果轮询时间大于5分钟，请在配置参数设置更长时间
                ModbusParams params = modbusParamsService.getModbusParamsByDeviceId(modbusJob.getSubDeviceId());
                if (!Objects.isNull(params)){
                    deterTime = Long.parseLong(params.getDeterTimer());
                }

                long lastAccessTime = session.getLastAccessTime();
                //如果现在的时间 - 最后访问时间 > 判断时间则更新为离线
                long time = (System.currentTimeMillis() - lastAccessTime - 20) / 1000;
                if (time > deterTime){
                    //处理设备离线
                    DeviceStatusBo statusBo = DeviceStatusBo.builder().status(DeviceStatus.OFFLINE)
                            .serialNumber(subSerialNumber)
                            .timestamp(DateUtils.getNowDate()).build();
                    MessageProducer.sendStatusMsg(statusBo);
                    sessionStore.cleanSession(subSerialNumber);
                    log.info("设备[{}],超过：[{}],未上报数据，更新为离线",subSerialNumber,deterTime);
                }

            }
        }
    }

    public void publish(String topic, byte[] pushMessage) {
        pubMqttClient.publish(pushMessage, topic, false, 0);
    }
}
