package vip.xiaonuo.coldchain.core.alarm.offline;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import vip.xiaonuo.coldchain.core.alarm.bean.SensorAlarm;
import vip.xiaonuo.coldchain.core.alarm.bean.SensorAlarmUser;
import vip.xiaonuo.coldchain.core.alarm.config.ColdChainAlarmMessageProperties;
import vip.xiaonuo.coldchain.core.alarm.enums.SensorAlarmType;
import vip.xiaonuo.coldchain.core.event.SensorAlarmEvent;
import vip.xiaonuo.coldchain.core.renke.config.JfcloudColdChainServerProperties;
import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
import vip.xiaonuo.coldchain.modular.monitortarget.enums.MonitorStatusEnum;
import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
import vip.xiaonuo.common.cache.CommonCacheOperator;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author jackzhou
 * @version 1.0
 * @project jfcloud-coldchain
 * @description
 * @date 2025/1/5 10:50:04
 */

@RequiredArgsConstructor
@Service
@Slf4j
public class DeviceOfflineDetectionService {
    // 时间格式化器，用于格式化时间为指定格式
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final String CACHE_KEY_PREFIX = "Cache:DeviceOffline:";
    private final CommonCacheOperator commonCacheOperator;
    private final ApplicationEventPublisher applicationEventPublisher;
    private final MonitorTargetRegionService monitorTargetRegionService;
    private final MonitorTargetService monitorTargetService;
    private final MonitorDeviceService monitorDeviceService;
    private final ColdChainAlarmMessageProperties alarmMessageProperties;
    private final JfcloudColdChainServerProperties jfcloudColdChainServerProperties;
    private static final String KEY_SPILT = "-";


    /**
     * 异步上报设备数据
     *
     * @param deviceCode 设备标识符
     */
//    @Async("coldChainAsyncTask")
    public void reportDeviceTime(String deviceCode, Integer route) {
        // 获取当前时间并格式化为字符串
        String currentTime = LocalDateTime.now().format(DATE_TIME_FORMATTER);
        // 将设备的上报时间存储到 Redis 中，并设置过期时间（离线阈值时间+1分钟）
        cacheDeviceTime(deviceCode, route, currentTime);
        log.info("设备 {} 异步上报数据，时间：{}\n", getKey(deviceCode, route), currentTime);
    }

    /**
     * 定期检测设备状态是否离线
     * 每 3 分钟运行一次
     */
    @Scheduled(fixedRate = 180000, initialDelay = 60000) // 每 3 分钟运行一次
    public void checkDeviceStatus() {
        log.info("开始自动检测设备在线状态...");
        Map<String, Integer> deviceCodes = new LinkedHashMap<>();
        // 获取 Redis 中的设备集合
        Set<String> sensorCodes = new HashSet<>();
        Set<String> redisDeviceKeys = getAllRedisDeviceCodes();
        if (redisDeviceKeys != null && !redisDeviceKeys.isEmpty()) {
            Set<String> sensorCodes1 = redisDeviceKeys.stream()
                    .map(key -> key.split(KEY_SPILT)[0]) // 提取出 sensorCode
                    .collect(Collectors.toSet());
            sensorCodes.addAll(sensorCodes1);
            LocalDateTime now = LocalDateTime.now();
            final long OFFLINE_THRESHOLD_MINUTES = jfcloudColdChainServerProperties.getDeviceOfflineInterval() / 60 / 1000;
            // 处理每个设备
            for (String key : redisDeviceKeys) {
                processDevice(key, now, OFFLINE_THRESHOLD_MINUTES, deviceCodes);
            }
        }
        // 获取所有设备代码
        Set<String> allSensorCodes = monitorDeviceService.getAllDeviceCodes();
        // 创建新的集合，避免修改原始集合
        Set<String> offlineSensorCodes = new HashSet<>(allSensorCodes);
        offlineSensorCodes.removeAll(sensorCodes); // 剩下的是未上线的设备
        log.info("查找出从来没上线的设备，统一设置为离线...");
        monitorTargetService.updateStatusbatch(offlineSensorCodes); // 更新离线设备状态
    }



    /**
     * 处理设备状态
     * @param key
     * @param now
     * @param offlineThresholdMinutes
     * @param deviceCodes
     */
    public void processDevice(String key, LocalDateTime now, long offlineThresholdMinutes, Map<String, Integer> deviceCodes) {
        String[] split = key.split(KEY_SPILT);
        if (split.length == 0) {
            return;
        }
        String deviceCode = split[0];
        Integer route = null;
        try {
            if (split.length == 2) {
                route = Integer.valueOf(split[1]);
            }
        } catch (NumberFormatException e) {
            log.error("设备键 {} 中的 route 数据格式错误，跳过处理。", key, e);
            return;
        }
        deviceCodes.put(deviceCode, route);
        String lastReportTimeStr = getLastDeviceTime(getKey(deviceCode, route));
        MonitorStatusEnum status;
        if (lastReportTimeStr != null) {
            LocalDateTime lastReportTime = LocalDateTime.parse(lastReportTimeStr, DATE_TIME_FORMATTER);
            if (lastReportTime.plusMinutes(offlineThresholdMinutes).isBefore(now)) {
                status = MonitorStatusEnum.OFF;
                log.warn("设备 {}-{} 已离线，最后上报时间：{}。", deviceCode, route, lastReportTimeStr);
                publishAlarm(deviceCode, route, lastReportTimeStr);
            } else {
                status = MonitorStatusEnum.ONLINE;
                log.debug("设备 {}-{} 在线，最后上报时间：{}。", deviceCode, route, lastReportTimeStr);
            }
        } else {
            status = MonitorStatusEnum.OFF;
            log.info("设备 {}-{} 缺少数据，可能从未上报过。", deviceCode, route);
        }
        monitorTargetService.updateStatusByDeviceCode(deviceCode, route, status);
    }



    private void publishAlarm(String deviceCode, Integer route, String time) {
        MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(deviceCode, route);
        if (Objects.isNull(monitorTargetRegion)) {
            return;
        }
        /**
         * jackzhou add 设备下线预警标识
         */
        if (Objects.isNull(monitorTargetRegion.getDeviceOfflineAlarm()) || !monitorTargetRegion.getDeviceOfflineAlarm()) {
            log.error("***********************设备未启动设备下线预警设置");
            return;
        }
        String monitorTargetId = monitorTargetRegion.getMonitorTargetId();
        Integer sensorRoute = monitorTargetRegion.getSensorRoute();
        String sensorCode = monitorTargetRegion.getSensorCode();
        String deviceName = monitorTargetRegion.getName();
        String deviceId = monitorTargetRegion.getId();
        final String alarmType = "设备离线";
        String alarmMessage = getAlarmMessage(deviceName, time);
        SensorAlarm sensorAlarm = new SensorAlarm();
        sensorAlarm.setAlarmType(alarmType);
        sensorAlarm.setValue(0f);
        sensorAlarm.setAlarmTime(time);
        sensorAlarm.setSource(deviceName);  // 设置设备名称
        sensorAlarm.setDeviceName(deviceName);  // 设置设备名称
        sensorAlarm.setDeviceId(deviceId);  // 设置设备名称
        sensorAlarm.setPriority("高");  // 设置设备名称
        sensorAlarm.setStatus("未处理");  // 设置设备名称
        sensorAlarm.setAlarmTime(time);  // 设置报警时间
        sensorAlarm.setMessage(alarmMessage);  // 设置报警消息
        sensorAlarm.setThreshold(null);  // 设置预警值
        sensorAlarm.setMonitorTargetId(monitorTargetId);
        sensorAlarm.setSensorRoute(sensorRoute);
        sensorAlarm.setSensorCode(sensorCode);
        sensorAlarm.setCreateUser(monitorTargetRegion.getCreateUser());
        List<SensorAlarmUser> alarmUsers = monitorTargetRegion.getAlarmUsers();
        sensorAlarm.setAlarmUsers(alarmUsers);
        sensorAlarm.setThreshold(0f);
        // 设置报警人机构 所属用户和机构
        sensorAlarm.setCreateUser(monitorTargetRegion.getCreateUser());
        sensorAlarm.setType(SensorAlarmType.SENSOR_OFFLINE.getDeviceCode());
        sensorAlarm.setCreateOrg(monitorTargetRegion.getCreateOrg());
        sensorAlarm.setRoomName(monitorTargetRegion.getRoomName());
        log.warn("设备断电报警: 类型: {},详细报警内容 : {}", alarmType, alarmMessage);
        // 发布报警事件
        applicationEventPublisher.publishEvent(new SensorAlarmEvent(this, sensorAlarm));
    }

    private String getAlarmMessage(String deviceName, String time) {
        String messageTemplate = alarmMessageProperties.getDeviceOfflineLimit();
        return messageTemplate.replace("{deviceName}", deviceName).replace("{time}", time);
    }

    /**
     * 缓存设备时间
     *
     * @param deviceCode
     * @param route
     * @param time
     */
    private void cacheDeviceTime(String deviceCode, Integer route, String time) {
//        final int minte_sufix = 2;
//        final long OFFLINE_THRESHOLD_MINUTES = jfcloudColdChainServerProperties.getDeviceOfflineInterval() / 60 / 1000;
//        commonCacheOperator.put(getKey(deviceCode, route), time, OFFLINE_THRESHOLD_MINUTES + minte_sufix, TimeUnit.MINUTES);
        commonCacheOperator.put(getKey(deviceCode, route), time);
    }

    @NotNull
    private static String getKey(String deviceCode, Integer route) {
        if (Objects.nonNull(route)) {
            return CACHE_KEY_PREFIX + deviceCode + KEY_SPILT + route;
        }
        return CACHE_KEY_PREFIX + deviceCode;
    }

    private String getLastDeviceTime(String key) {
        if (exists(key)) {
            return (String) commonCacheOperator.get(key);
        } else {
            return null;
        }
    }

    private boolean exists(String key) {
        return commonCacheOperator.get(key) != null;
    }

    private Set<String> getAllRedisDeviceCodes() {
        return commonCacheOperator.getAllKeys().stream().filter(key -> key.startsWith(CACHE_KEY_PREFIX)) // 过滤指定前缀的键
                .map(key -> key.replace(CACHE_KEY_PREFIX, "")) // 去掉前缀
                .collect(Collectors.toSet());
    }
}