package com.zt.plat.module.qms.iot.tcpserver.device;

import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
import com.zt.plat.module.qms.iot.tcpserver.IotUtils;
import com.zt.plat.module.qms.iot.tcpserver.core.ChannelContextConstant;
import com.zt.plat.module.qms.iot.tcpserver.core.IotClientSessionForRedisContext;
import com.zt.plat.module.qms.iot.tcpserver.core.IotDeviceSessionForRedisContext;
import com.zt.plat.module.qms.iot.tcpserver.publisher.IotDeviceRegisterPublisher;
import com.zt.plat.module.qms.resource.device.dal.dataobject.DeviceInfomationDO;
import com.zt.plat.module.qms.resource.device.service.DeviceInfomationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import tech.zzjc.tio.core.ChannelContext;
import tech.zzjc.tio.core.Tio;
import tech.zzjc.tio.core.stat.ChannelStat;
import tech.zzjc.tio.utils.SystemTimer;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class RedisSessionComponent {

    @Autowired private RedisTemplate redisTemplate;
    @Autowired private DeviceInfomationService deviceInfomationService;
    private final long timeoutSeconds = 60;

    public void update(IotDeviceSessionForRedisContext context){
        String deviceId = IotUtils.transDeviceCodeToId(context.getDeviceCode());
        context.setRegTime(new Date());
        String key = getRedisKeyByDeviceId(deviceId);
//        log.info("key={}", key);
        redisTemplate.opsForValue().set(key, context, timeoutSeconds, TimeUnit.SECONDS);
    }

    public void clearControlByDeviceCode(String deviceCode, ChannelContext channelContext){
        IotDeviceSessionForRedisContext context = getByDeviceCode(deviceCode);
        context.setControlRealName("");
        context.setControlChannelContext(null);
        channelContext.set(ChannelContextConstant.CONTROL_DEVICE_ID, "");
        redisTemplate.opsForValue().set(getRedisKeyByDeviceCode(deviceCode), context, timeoutSeconds, TimeUnit.SECONDS);
    }

    public void clearControlByDeviceId(String deviceId, ChannelContext channelContext){
        IotDeviceSessionForRedisContext context = getByDeviceId(deviceId);
        context.setControlRealName("");
        context.setControlChannelContext(null);
        channelContext.set(ChannelContextConstant.CONTROL_DEVICE_ID, "");
        redisTemplate.opsForValue().set(getRedisKeyByDeviceId(deviceId), context, timeoutSeconds, TimeUnit.SECONDS);
    }

    public void deleteByDeviceId(String deviceId){
        redisTemplate.delete(getRedisKeyByDeviceId(deviceId));
    }

    public IotDeviceSessionForRedisContext getByDeviceCode(String deviceCode){
        String redisKey = getRedisKeyByDeviceCode(deviceCode);
        return getByKey(redisKey);
    }

    public IotDeviceSessionForRedisContext getByDeviceId(String deviceId){
        String redisKey = getRedisKeyByDeviceId(deviceId);
        return getByKey(redisKey);
//        return (IotDeviceSessionForRedisContext) redisTemplate.opsForValue().get(getRedisKeyByDeviceId(deviceId));
    }
    public IotDeviceSessionForRedisContext getByKey(String key){
        return (IotDeviceSessionForRedisContext) redisTemplate.opsForValue().get(key);
    }

    public IotClientSessionForRedisContext getClientByKey(String key){
        return (IotClientSessionForRedisContext) redisTemplate.opsForValue().get(key);
    }

    public String getRedisKeyByDeviceCode(String deviceCode){
        String deviceId = IotUtils.transDeviceCodeToId(deviceCode);
        return getRedisKeyByDeviceId(deviceId);
    }
    public String getRedisKeyByDeviceId(String deviceId){
        String redisKey = IotDeviceSessionForRedisContext.DEFAULT_DEVICE_SESSION_CONTEXT_KEY + "-" + deviceId;
        return redisKey;
    }

    public void regDevice(ChannelContext channelContext, String deviceCode, String deviceType){
        IotDeviceSessionForRedisContext clientContext = getByDeviceCode(deviceCode);
        if(clientContext != null && Tio.getByBsId(channelContext.getTioConfig(), deviceCode) != null){
            return;
        }

        TenantContextHolder.setIgnore(true);
        try{
            String deviceId = IotUtils.transDeviceCodeToId(deviceCode);
            DeviceInfomationDO deviceInfo = deviceInfomationService.getDeviceInfomation(Long.valueOf(deviceId));
            IotDeviceSessionForRedisContext context = new IotDeviceSessionForRedisContext();
            context.setChannelId(channelContext.getId());
            context.setDeviceId(deviceId);
            context.setDeviceType(deviceType);
            context.setDeviceCode(deviceCode);
            if(deviceInfo != null){
                context.setDeviceName(deviceInfo.getDeviceName());
                context.setTenantId(deviceInfo.getTenantId().toString());
                deviceInfo.setIsConnect("1");
                deviceInfo.setLastConnectTime(LocalDateTime.now());
                deviceInfomationService.updateDeviceInfomation(deviceInfo);
            }
            context.setRegTime(new Date());
            context.setControlRealName( "");
            redisTemplate.opsForValue().set(getRedisKeyByDeviceId(deviceId), context, timeoutSeconds, TimeUnit.SECONDS);

            channelContext.set("regTime", new Date());
            //绑定业务id
            Tio.bindBsId(channelContext, deviceCode);
            //设备注册事件
            IotDeviceRegisterPublisher.publishEvent(deviceCode, deviceType);
        }catch (Exception e){
            e.printStackTrace();
            log.error("设备注册异常：{}", e.getMessage());
        }finally {
            TenantContextHolder.clear();
        }

    }

    public void regClient(ChannelContext channelContext){
        String channelId = channelContext.getId();
        IotClientSessionForRedisContext context = getByClientChannelId(channelId);
        if(context != null){
            return;
        }

        TenantContextHolder.setIgnore(true);
        try{
            context = new IotClientSessionForRedisContext();

            String realClientIp = channelContext.get("realClientIp") == null ? channelContext.getClientNode().getIp() : channelContext.get("realClientIp").toString();
            String realClientPort = channelContext.get("realClientPort") == null ? channelContext.getClientNode().getPort() + "" : channelContext.get("realClientPort").toString();
            String connectType = channelContext.get(ChannelContextConstant.CONNECT_TYPE) == null ? "" : channelContext.get(ChannelContextConstant.CONNECT_TYPE).toString();
            String userName = channelContext.get("user-real-name") == null ? "无" : channelContext.get("user-real-name").toString();
//            ChannelContext clientChanelContext = Tio.getByBsId(channelContext.getTioConfig(), deviceCode);
//            if(clientChanelContext != null){
//                context.setClientProtocol(clientChanelContext.getClientNode().getProtocol());
//                context.setProxyIp(clientChanelContext.getClientNode().getIp());
//                context.setProxyPort(clientChanelContext.getClientNode().getPort());
//                context.setProxyProtocol(clientChanelContext.getClientNode().getProtocol());
//            }
            context.setChannelId(channelId);
            context.setControlRealName(userName);
            context.setClientIp(realClientIp);
            context.setClientPort(Integer.parseInt(realClientPort));
            context.setConnectType(connectType);

            updateClientCommonAttrs(context, channelContext);

            String redisKey = getClientRedisKeyByChannelId(channelId);
            redisTemplate.opsForValue().set(redisKey, context, timeoutSeconds, TimeUnit.SECONDS);
            //todo 客户端注册事件
//            IotDeviceRegisterPublisher.publishEvent(deviceCode, deviceType);
        }catch (Exception e){
            e.printStackTrace();
            log.error("设备注册异常：{}", e.getMessage());
        }finally {
            TenantContextHolder.clear();
        }

    }


    public void updateClient(IotClientSessionForRedisContext context, ChannelContext channelContext){
        String channelId = channelContext.getId();
        updateClientCommonAttrs(context, channelContext);
        String redisKey = getClientRedisKeyByChannelId(channelId);
        redisTemplate.opsForValue().set(redisKey, context, timeoutSeconds, TimeUnit.SECONDS);
    }

    private void updateClientCommonAttrs(IotClientSessionForRedisContext context, ChannelContext channelContext){
        ChannelStat stat = channelContext.stat;
        context.setGmtCreate(new Date(stat.getTimeCreated()));
        context.setDecodeFailCount(stat.getDecodeFailCount());
        context.setReceivedBytes(stat.getReceivedBytes());
        context.setReceivedPackets(stat.getReceivedPackets());
        context.setReceivedTcps(stat.getReceivedTcps());
        context.setBytesPerTcpReceive(stat.getBytesPerTcpReceive());
        context.setPacketsPerTcpReceive(stat.getPacketsPerTcpReceive());
        context.setHandledBytes(stat.getHandledBytes());
        context.setHandledPackets(stat.getHandledPackets());
        context.setSentBytes(stat.getSentBytes());
        context.setSentPackets(stat.getSentPackets());
        context.setLastCommunicateTime(new Date());
    }

    public String getClientRedisKeyByChannelId(String channelId){
        String redisKey = IotClientSessionForRedisContext.DEFAULT_CLIENT_SESSION_CONTEXT_KEY + "-" + channelId;
        return redisKey;
    }


    public IotClientSessionForRedisContext getByClientChannelId(String channelId){
        String redisKey = getClientRedisKeyByChannelId(channelId);
        return getClientByKey(redisKey);
    }


    /**
     * 使用Scan方式查询key（推荐用于大数据量）
     * 避免使用KEYS命令导致Redis阻塞
     */
    public Set<String> scanKeys(String matchPattern) {
        Set<String> keys = new HashSet<>();
        try {
            redisTemplate.execute((RedisCallback<Void>) connection -> {
                try (Cursor<byte[]> cursor = connection.scan(
                        ScanOptions.scanOptions()
                                .match(matchPattern)
                                .count(1000) // 每次扫描的数量
                                .build())) {
                    while (cursor.hasNext()) {
                        keys.add(new String(cursor.next()));
                    }
                }
                return null;
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return keys;
    }


}
