package com.agri.quartz.task;

import cn.hutool.core.map.MapUtil;
import com.agri.common.enums.TempCommandStatus;
import com.agri.common.utils.*;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.config.MqttConfig;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysAgriLimit;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.domain.SysImeiAutoLog;
import com.agri.system.domain.vo.RollerTermVO;
import com.agri.system.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.checkerframework.checker.units.qual.A;
import org.eclipse.paho.client.mqttv3.MqttException;
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.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 卷膜自动模式定时检查
 * 每五分钟执行一次  每日时间区间内执行
 * 1、查询大棚列表开启自动模式的大棚，获取imei
 * 2、根据imei查询dtu_data最后一分钟最后一条温度数据
 * 3、查询参数设置获取  风口总长 预留风口长度 在条件内开启指定风口，第一次开 预留风口+条件内设定的风口长度 还是得存日志、、、
 * 4、下发开启或者关闭指令  起个自动关任务 需要改自动关逻辑，自动模式，监听处不转发回执
 *  todo通知用户
 */
@Component("rollerAutoTask")
public class RollerAutoTask {

    /**
     * 优化：统一使用SLF4J日志（JDK 8兼容）
     */
    private static final Logger log = LoggerFactory.getLogger(RollerAutoTask.class);

    @Autowired
    private ISysAgriInfoService agriInfoService;

    @Autowired
    private ISysDtuDataService dtuDataService;

    @Autowired
    private ISysRollerParamService rollerParamService;

    @Resource
    private MqttConfig.MqttMessageSender mqttMessageSender;

    @Autowired
    private ISysDevOperLogService devOperLogService;

    @Autowired
    private MqttAutoOffManager autoOffManager;

    /**
     * Redis模板，用于存储订阅关系、设备在线状态、分布式锁
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    private ISysAgriLimitService agriLimitService;


    @Value("${spring.mqtt.dtu-ctl-lock-ttl}")
    private int dtuCtlLockTTL;

    @Autowired
    private ISysImeiAutoLogService imeiAutoLogService;

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");


    private static final Map<String, Function<SysAgriLimit, Integer>> LIMIT_MAP = new HashMap<>();
    private static final Set<String> VALID_FUNC_CODES = new HashSet<>();
    static {
        LIMIT_MAP.put("jm1g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1gLimit())));
        LIMIT_MAP.put("jm2g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2gLimit())));
        LIMIT_MAP.put("jbg1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbgLimit())));
        LIMIT_MAP.put("jm3g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3gLimit())));
        LIMIT_MAP.put("jm2k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2kLimit())));
        LIMIT_MAP.put("jm3k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3kLimit())));
        LIMIT_MAP.put("jbk1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbkLimit())));
        LIMIT_MAP.put("jm1k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1kLimit())));
    }


    // ========== 常量定义（新增） ==========
    private static final int WORK_MODE_AUTO = 1; // 自动模式
    private static final int NOT_DELETED = 0;     // 未删除
    private static final String LOCK_PREFIX = "lock:";
    private static final String AUTO_MODE = "auto_mode";
    private static final String CREATE_BY = "条件控制";
    private static final int OP_SOURCE = 3; // 操作来源：条件控制
    private static final int LOCK_ACQUIRED = 1; // 是否获取锁
    private static final int DEFAULT_RUN_TIME = 300; // 默认运行时间

    public void checkAutoTerm() {
        try {
            log.info("=============【定时任务】大棚自动模式监测开始执行，时间：{}=============", LocalDateTime.now());
            // 查询自动模式的大棚
            List<SysAgriInfo> agriInfos = agriInfoService.lambdaQuery()
                    .select(SysAgriInfo::getImei, SysAgriInfo::getAgriName)
                    .eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO)
                    .eq(SysAgriInfo::getIsDeleted, NOT_DELETED)
                    .list();
            if (CollectionUtils.isEmpty(agriInfos)) return;
            // 取imei集合
            List<String> imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(imeiList)) return;

            // 根据imei 查询dtu_data最后一条温度数据
            List<Map<String, Object>> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList);
            if (CollectionUtils.isEmpty(dtuDataList)) return;
            // 根据温湿度imei分组
            Map<String, List<Map<String, Object>>> dtuDataByImeiMap
                    = dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei")));

            // 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置
            List<RollerTermVO> rollerTermList = rollerParamService.getRollerTerms(imeiList);
            if (CollectionUtils.isEmpty(rollerTermList)) {
                // todo 无参数设置和条件列表直接返回
                log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!");
                return;
            }
            // 按imei分组 → 再按roller分组（一步到位）
            Map<String, Map<String, List<RollerTermVO>>> rollerTermMap = rollerTermList.stream()
                    .collect(Collectors.groupingBy(
                            RollerTermVO::getImei,
                            Collectors.groupingBy(RollerTermVO::getRoller)
                    ));

            // 查询每个IMEI今天的第一条日志
            Map<String, Map<String, Integer>> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList);
            ArrayList<SysImeiAutoLog> logArrayList = new ArrayList<SysImeiAutoLog>();
            // 循环所有开启自动化的大棚
            for (SysAgriInfo agriInfo : agriInfos) {
                String imei = agriInfo.getImei();
                String agriName = agriInfo.getAgriName();
                // 获取温湿度指定imei的数据
                List<Map<String, Object>> dtuDataInfo = dtuDataByImeiMap.get(imei);
                // 该大棚温湿度不存在
                if (CollectionUtils.isEmpty(dtuDataInfo)) {
                    // todo 该大棚下1分钟内无最新温湿度，怀疑离线
                    log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度，怀疑离线", imei);
                    continue;
                }

                // 最后一条对应imei对应温度 及时间
                Map<String, Object> dtuData = dtuDataInfo.get(0);
                // 求温度上报时间
                LocalDateTime dtuTime = (LocalDateTime) dtuData.get("time");
                if (dtuTime == null) {
                    // todo 当前大棚温湿度时间为空 跳过
                    log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空， 跳过", imei, LocalDateTime.now().minusMinutes(1));
                    continue;
                }

                // 获取当前imei下的所有参数设置以及卷膜自动化条件设置
                Map<String, List<RollerTermVO>> configTermByRollerMap = rollerTermMap.get(imei);
                if (MapUtil.isEmpty(configTermByRollerMap)) {
                    // todo 当前大棚下没有设置条件或者参数
                    log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数, 跳过", imei);
                    continue;
                }

                // 获取今天对应imei的日志
                Map<String, Integer> todayLogByRoller = todayLogCountByImeiMap.get(imei);

                for (Map.Entry<String, List<RollerTermVO>> configEntry : configTermByRollerMap.entrySet()) {
                    String roller = configEntry.getKey(); // 当前卷膜
                    List<RollerTermVO> terms = configEntry.getValue(); // 当前卷膜条件
                    // 每个卷膜分组只会有一个卷膜参数设置，所有取第一个即可
                    if (terms == null || terms.isEmpty()) {
                        // todo 当前卷膜  无参数设置，跳过当前roller
                        log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置，跳过当前roller", imei, roller);
                        continue;
                    }
                    LocalDateTime startTime = terms.stream()
                            .max(Comparator.comparing(RollerTermVO::getStartTime))
                            .get()
                            .getStartTime();
                    // 获取卷膜参数
                    RollerTermVO rollerConfig = terms.get(0);
                    String refTempCode = rollerConfig.getRefTempCode(); // 参考温度
                    BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长
                    BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口

                    // 防御性判断：避免dtuData.get(refTempCode)为null
                    Object tempObj = dtuData.get(refTempCode);
                    if (tempObj == null) {
                        // todo 当前卷膜参考温度设置为空
                        log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空，跳过！", imei, roller);
                        continue;
                    }
                    // 优化：明确标注为当前roller的参考温度快照（仅解析一次）
                    BigDecimal currentTemp = new BigDecimal(tempObj.toString());

                    // 遍历当前roller的所有term（改用普通for循环，可读性更高）
                    for (RollerTermVO term : terms) {
                        // 判断该温度上报时间是否在该条件设置的时间范围内
                        boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime());
                        boolean isCancelOff = startTime.equals(term.getStartTime());
                        log.info("\n【定时任务-卷膜自动化控制】正在监测大棚『{}』-卷膜『{}』：" +
                                        "条件设置详情，监控时间范围：『{}~{}』，适宜温度：{}℃，运行风口：{}cm。「{}」",
                                imei,roller,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER),
                                term.getTemp(),term.getVent(), isCancelOff?"当前为卷膜「"+roller+"」最后一条自动化规则":"");
                        if (!inRange) {
                            log.info("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」温湿度时间：「{}」不在监控时间范围内「{}~{}」，跳过！",
                                    imei, roller, dtuTime,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER));
                            continue;
                        }
                        // 在范围内
                        //判断温度是否在适宜温度内
                        TempCommandStatus tempCommandStatus = TempJudgeUtil.judgeTempCommand(currentTemp, term.getTemp());
                        SysImeiAutoLog imeiAutoLog = new SysImeiAutoLog();
                        imeiAutoLog.setImei(imei);
                        imeiAutoLog.setMonitorPeriod(term.getStartTime().format(FORMATTER)+"~"+term.getEndTime().format(FORMATTER));
                        imeiAutoLog.setCurrentTemp(currentTemp);
                        imeiAutoLog.setRefTemp(refTempCode);
                        imeiAutoLog.setSuitableTemp(term.getTemp());
                        imeiAutoLog.setFanStatus(term.getVent());
                        imeiAutoLog.setIsLast(isCancelOff?1:0);
                        // todo  开关指令需要通知用户  推送主题 && 更新数据 前端重新请求消息表
                        if (tempCommandStatus == TempCommandStatus.OPEN) {
                            log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度：『{}℃』，适宜温度为：「{}℃」，触发自动化条件，即将执行『开』指令！",
                                    imei, roller, currentTemp, term.getTemp());
                            // 判断是否首次开
                            Integer openLen = todayLogByRoller.getOrDefault(roller + "k1", 0);
                            imeiAutoLog.setRollFilm(roller);
                            imeiAutoLog.setExecCmd("开");
                            // 开指令
                            sendOpenCommand(imei, agriName, roller, openLen == 0, term.getVent(), reservedLen);
                            // 每次后，数量累计+1 不需要因为只循环到一次
                        } else if (tempCommandStatus == TempCommandStatus.CLOSE) {
                            log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度：『{}℃』，适宜温度为：「{}℃」，触发自动化条件，即将执行『关』指令！",
                                    imei, roller, currentTemp, term.getTemp());
                            // 判断是否首次开
                            Integer closeLen = todayLogByRoller.getOrDefault(roller + "g1", 0);
                            imeiAutoLog.setRollFilm(roller);
                            imeiAutoLog.setExecCmd("关");
                            // 关指令
                            sendCloseCommand(imei, agriName, roller, closeLen == 0, term.getVent(), isCancelOff);
                            // 每次后，数量累计+1
                        } else {
                            log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度：『{}℃』，适宜温度为：「{}℃」，温度适宜，无需操作！",
                                    imei, roller, currentTemp, term.getTemp());
                        }
                        logArrayList.add(imeiAutoLog);
                    }
                }
            }
            imeiAutoLogService.saveBatch(logArrayList);
            log.info("=============【定时任务】大棚自动模式监测执行完毕，时间：{}=============", LocalDateTime.now());
        } catch (Exception e) {
            WxUtil.pushText("【定时任务】大棚自动模式监测执行终端， \n发生错误.错误原因："+e+"，\n时间："+LocalDateTime.now());
            log.error("\n=============【定时任务】大棚自动模式监测执行终端，\n发生错误.错误原因：{}，\n时间：{}=============", e, LocalDateTime.now());
        }
    }


    /**
     * 发送开指令
     * @param imei 设备唯一标识
     * @param agriName 大棚名称
     * @param roller 卷膜标识
     * @param isFirstRun 是否第一次执行
     * @param vent 每次执行的风口大小
     * @param reservedLen 预留风口长度
     */
    private void sendOpenCommand(String imei, String agriName, String roller, boolean isFirstRun,
                                 BigDecimal vent, BigDecimal reservedLen) {
        // ========== 1. 前置参数校验（防御性判断） ==========
        validateBaseParams(imei, agriName, roller);
        if (vent == null) {
            log.error("【开指令】设备{}卷膜{}风口大小为空，指令发送失败", imei, roller);
            return;
        }
        if (isFirstRun && reservedLen == null) {
            log.error("【开指令】设备{}卷膜{}首次执行但预留风口为空，指令发送失败", imei, roller);
            return;
        }

        // ========== 2. 计算开指令风口长度 ==========
        BigDecimal openLen = vent;
        if (isFirstRun) {
            log.error("【自动模式】设备【{}】卷膜【{}】今日首次执行开指令，叠加预留风口{}", imei, roller, reservedLen);
            openLen = openLen.add(reservedLen);
        }
        String funcType = roller + "k1";
        String message = buildMqttMessage(funcType);

        // ========== 3. 执行核心指令逻辑 ==========
        executeCommand(imei, agriName, roller, funcType, message, openLen, true, false);
    }

    /**
     * 发送关指令
     * @param imei 设备唯一标识
     * @param agriName 大棚名称
     * @param roller 卷膜标识
     * @param isFirstRun 是否第一次执行
     * @param vent 每次执行的风口大小
     */
    private void sendCloseCommand(String imei, String agriName, String roller,
                                  boolean isFirstRun, BigDecimal vent, boolean isCancelOff) {
        // ========== 1. 前置参数校验 ==========
        validateBaseParams(imei, agriName, roller);
        if (isFirstRun) {
            log.error("【关指令】设备{}卷膜{}今日首次执行，直接返回不发送关指令", imei, roller);
            return;
        }
        if (vent == null) {
            log.error("【关指令】设备{}卷膜{}风口大小为空，指令发送失败", imei, roller);
            return;
        }

        // ========== 2. 构建关指令参数 ==========
        String funcType = roller + "g1";
        String message = buildMqttMessage(funcType);

        // ========== 3. 执行核心指令逻辑 ==========
        executeCommand(imei, agriName, roller, funcType, message, vent, false, isCancelOff);
    }

// ========== 抽取公共方法：减少代码冗余 ==========

    /**
     * 基础参数校验
     * @param imei 设备ID
     * @param agriName 大棚名称
     * @param roller 卷膜标识
     */
    private void validateBaseParams(String imei, String agriName, String roller) {
        if (StringUtils.isBlank(imei)) {
            throw new IllegalArgumentException("设备IMEI不能为空");
        }
        if (StringUtils.isBlank(agriName)) {
            log.warn("【指令】设备{}卷膜{}大棚名称为空，仍继续执行指令", imei, roller);
        }
        if (StringUtils.isBlank(roller)) {
            throw new IllegalArgumentException("卷膜标识不能为空");
        }
    }

    /**
     * 构建MQTT指令消息
     * @param funcType 功能类型（如roller1k1/roller1g1）
     * @return 格式化的MQTT消息字符串
     */
    private String buildMqttMessage(String funcType) {
        return String.format("{\"%s\":1}", funcType);
    }

    /**
     * 执行指令核心逻辑（分布式锁+日志记录+MQTT发布+自动关调度）
     * @param imei 设备ID
     * @param agriName 大棚名称
     * @param roller 卷膜标识
     * @param funcType 功能类型
     * @param message MQTT消息
     * @param len 风口长度（用于计算运行时间）
     * @param isOpen 是否开指令
     */
    private void executeCommand(String imei, String agriName, String roller, String funcType,
                                String message, BigDecimal len, boolean isOpen, boolean isCancelOff) {
        String lockKey = LOCK_PREFIX + imei + ":" + funcType;
        try {
            // ========== 1. 获取分布式锁 ==========
            Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
                    lockKey, AUTO_MODE, dtuCtlLockTTL, TimeUnit.SECONDS
            );
            if (lockSuccess == null || !lockSuccess) {
                log.warn("【分布式锁】自动模式下{}设备{}的{}功能失败，可能存在并发操作",
                        isOpen ? "开启" : "关闭", imei, funcType);
                return;
            }

            // ========== 2. 记录操作日志 ==========
            log.debug("【指令处理】自动模式下触发{}设备{}的{}功能，指令：{}",
                    isOpen ? "开启" : "关闭", imei, funcType, message);

            // ========== 3. 发布MQTT指令 ==========todo
            mqttMessageSender.publish("dtu/" + imei + "/down", message);

            // ========== 4. 计算运行时间并调度自动关 ==========
            int runTime = RollerTimeCalculator.calculateRunTime(len);
            if (isCancelOff) {
                // 如果最后一条则遵循手动限位设置，如果没设置默认300秒
                SysAgriLimit agriLimit = agriLimitService.lambdaQuery()
                        .eq(SysAgriLimit::getImei, imei)
                        .one();
                if (agriLimit != null) {
                    runTime = LIMIT_MAP.getOrDefault(funcType, k -> DEFAULT_RUN_TIME).apply(agriLimit);
                }
                log.debug("【自动关调度】设备{}卷膜{}：触发最后一条自动化条件，遵循手动限位设置！时间：{}", imei, roller, LocalDateTime.now());
            }
            if (runTime > 0) {
                String autoOffKey = roller + (isOpen ? "k1" : "g1");
                autoOffManager.scheduleAutoOff(imei, autoOffKey, runTime);
                log.debug("【自动关调度】设备{}卷膜「{}:{}」调度{}秒后自动关闭", imei, roller,(isOpen?"开":"关"), runTime);
            }
            saveOperLog(imei, agriName, funcType, message, isOpen ? 1 : 0, isCancelOff, runTime);
        }
//        catch (MqttException e) {
//            // ========== 异常处理：记录详细日志，不抛运行时异常 ==========
//            log.error("【MQTT异常】{}设备{}的{}功能指令发布失败，指令：{}",
//                    isOpen ? "开启" : "关闭", imei, funcType, message, e);
//        }

        catch (Exception e) {
            // 兜底异常捕获：避免未知异常导致锁无法释放（依赖TTL兜底）
            log.error("【指令执行异常】{}设备{}的{}功能执行失败",
                    isOpen ? "开启" : "关闭", imei, funcType, e);
        }
    }

    /**
     * 保存设备操作日志
     * @param imei 设备ID
     * @param agriName 大棚名称
     * @param funcCode 功能码
     * @param payload 指令内容
     * @param opType 操作类型（1-开，0-关）
     */
    private void saveOperLog(String imei, String agriName,
                             String funcCode, String payload, int opType, boolean isCancelOff, int runTime) {
        SysDevOperLog logDto = new SysDevOperLog();
        logDto.setAgriName(agriName);
        logDto.setImei(imei);
        logDto.setFuncCode(funcCode);
        logDto.setOpType(opType);
        logDto.setOpSource(OP_SOURCE);
        logDto.setPayload(payload);
        logDto.setRunTime(runTime);
        logDto.setLockAcquired(LOCK_ACQUIRED); // 已获取锁
        logDto.setLockHolder(AUTO_MODE);
        logDto.setIsTask(isCancelOff?0:1);
        if (isCancelOff) {
            logDto.setNoTaskReason("触发最后一条自动化指令，遵循手动限位设置，默认300秒");
        }
        logDto.setCreateBy(CREATE_BY);
        // 可选：增加异常捕获，避免日志保存失败影响指令执行
        try {
            devOperLogService.save(logDto);
            log.info("【日志保存】设备{}功能{}日志保存成功,日志id:{}", imei, funcCode,logDto.getId());
        } catch (Exception e) {
            log.error("【日志保存失败】设备{}功能{}日志保存失败", imei, funcCode, e);
        }
    }
}
