package org.dromara.app.handler;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.AppSchedulingDevice;
import org.dromara.app.domain.bo.AppDeviceBo;
import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.domain.vo.AppWateringLogVo;
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
import org.dromara.app.mqtt.MqttTopicHandler;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 开始浇水上报处理器，匹配 /{deviceNo}/publish/startWater。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StartWaterHandler implements MqttTopicHandler {

    private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/start$");

    private final IAppWateringLogService appWateringLogService;
    private final IAppDeviceService appDeviceService;
    private final AppSchedulingDeviceMapper schedulingDeviceMapper;
    private final DeviceIdentityResolver deviceIdentityResolver;

    @Override
    public Pattern topicPattern() {
        return PATTERN;
    }

    @Override
    public void handle(String deviceIdentity, String payload) {
        String deviceNo = null;
        try {
            deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity);
            if (deviceNo == null) {
                log.warn("[MQTT] 开始浇水上报未找到设备 时间={} 设备标识={} 消息体={}",
                    HandlerLogTime.now(), deviceIdentity, payload);
                return;
            }
            Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
            AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
            if (hasDuplicateRunningLog(logBo)) {
                updateDeviceWorkStatusWorking(deviceNo);
                log.info("[MQTT] 开始浇水上报已存在进行中记录，跳过保存 时间={} 设备编号={} 开始时间={} 浇水方式={}",
                    HandlerLogTime.now(), deviceNo, logBo.getStartTime(), logBo.getTriggerType());
                return;
            }
            appWateringLogService.insertByBo(logBo);
            updateDeviceWorkStatusWorking(deviceNo);
            log.info("[MQTT] 开始浇水上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
        } catch (Exception e) {
            log.error("[MQTT] 开始浇水上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
                HandlerLogTime.now(), deviceIdentity, deviceNo, payload, e);
        }
    }

    private void updateDeviceWorkStatusWorking(String deviceNo) {
        AppDeviceBo deviceBo = new AppDeviceBo();
        deviceBo.setDeviceNo(deviceNo);
        deviceBo.setWorkStatus("1");
        appDeviceService.updateByBo(deviceBo);
    }

    private AppWateringLogBo buildWateringLog(String topicDeviceNo, Map<String, Object> dto) {
        String payloadDeviceNo = valueAsString(dto.get("deviceNo"));
        if (StringUtils.isNotBlank(payloadDeviceNo) && !topicDeviceNo.equals(payloadDeviceNo)) {
            log.warn("[MQTT] 开始浇水上报设备编号不一致 时间={} 主题设备编号={} 消息体设备编号={}",
                HandlerLogTime.now(), topicDeviceNo, payloadDeviceNo);
        }

        String triggerType = resolveTriggerType(dto);
        Date startTime = parseStartTime(valueAsString(dto.get("startTime")));
        Integer durationMin = valueAsInteger(dto.get("durationMin"));

        AppWateringLogBo logBo = new AppWateringLogBo();
        logBo.setDeviceNo(topicDeviceNo);
        logBo.setCommandId(valueAsString(dto.get("commandId")));
        logBo.setUserId(queryUserId(topicDeviceNo));
        logBo.setScheduleId("0".equals(triggerType) ? queryScheduleId(topicDeviceNo) : 0L);
        logBo.setStartTime(startTime);
        logBo.setEndTime(resolveEndTime(startTime, durationMin));
        logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
        logBo.setTriggerType(triggerType);
        logBo.setStatus("1");
        logBo.setCreateTime(new Date());
        logBo.setRemark("0".equals(triggerType) ? "设备上报排程开始浇水" : "设备上报手动开始浇水");
        return logBo;
    }

    private boolean hasDuplicateRunningLog(AppWateringLogBo logBo) {
        AppWateringLogBo query = new AppWateringLogBo();
        query.setDeviceNo(logBo.getDeviceNo());
        query.setScheduleId(logBo.getScheduleId());
        query.setTriggerType(logBo.getTriggerType());
        List<AppWateringLogVo> exists = appWateringLogService.queryList(query);
        if (exists == null || exists.isEmpty()) {
            return false;
        }
        return exists.stream().anyMatch(item -> {
            boolean sameCommand = StringUtils.isNotBlank(logBo.getCommandId())
                && Objects.equals(item.getCommandId(), logBo.getCommandId());
            boolean sameWateringWindow = sameMinute(item.getStartTime(), logBo.getStartTime());
            boolean running = "1".equals(item.getStatus())
                || (StringUtils.isBlank(item.getStatus()) && item.getEndTime() == null);
            return Objects.equals(item.getDeviceNo(), logBo.getDeviceNo())
                && scheduleIdMatches(item.getScheduleId(), logBo)
                && Objects.equals(item.getTriggerType(), logBo.getTriggerType())
                && running
                && (sameCommand || sameWateringWindow);
        });
    }

    private boolean scheduleIdMatches(Long existsScheduleId, AppWateringLogBo logBo) {
        if ("1".equals(logBo.getTriggerType())) {
            return existsScheduleId == null || existsScheduleId == 0L;
        }
        return Objects.equals(existsScheduleId, logBo.getScheduleId());
    }

    private boolean sameMinute(Date existsStartTime, Date reportStartTime) {
        if (existsStartTime == null || reportStartTime == null) {
            return Objects.equals(existsStartTime, reportStartTime);
        }
        Calendar existsCalendar = Calendar.getInstance();
        existsCalendar.setTime(existsStartTime);
        Calendar reportCalendar = Calendar.getInstance();
        reportCalendar.setTime(reportStartTime);
        return existsCalendar.get(Calendar.YEAR) == reportCalendar.get(Calendar.YEAR)
            && existsCalendar.get(Calendar.DAY_OF_YEAR) == reportCalendar.get(Calendar.DAY_OF_YEAR)
            && existsCalendar.get(Calendar.HOUR_OF_DAY) == reportCalendar.get(Calendar.HOUR_OF_DAY)
            && existsCalendar.get(Calendar.MINUTE) == reportCalendar.get(Calendar.MINUTE);
    }

    private Date resolveEndTime(Date startTime, Integer durationMin) {
        if (startTime == null || durationMin == null || durationMin <= 0) {
            return null;
        }
        return new Date(startTime.getTime() + durationMin * 60000L);
    }

    private String resolveTriggerType(Map<String, Object> dto) {
        String triggerType = valueAsString(dto.get("triggerType"));
        if ("0".equals(triggerType) || "1".equals(triggerType)) {
            return triggerType;
        }

        String trigger = valueAsString(dto.get("triggerON"));
        if (StringUtils.isBlank(trigger)) {
            trigger = valueAsString(dto.get("wateringType"));
        }
        if (StringUtils.isBlank(trigger)) {
            trigger = valueAsString(dto.get("type"));
        }
        if (StringUtils.isBlank(trigger)) {
            return "1";
        }

        String normalized = trigger.trim().toLowerCase(Locale.ROOT);
        if ("schedule".equals(normalized) || "0".equals(normalized)) {
            return "0";
        }
        return "1";
    }

    private Long queryUserId(String deviceNo) {
        AppDeviceVo device = appDeviceService.queryById(deviceNo);
        return device == null ? null : device.getUserId();
    }

    private Long queryScheduleId(String deviceNo) {
        List<AppSchedulingDevice> schedulingDevices = schedulingDeviceMapper.selectList(
            Wrappers.<AppSchedulingDevice>lambdaQuery()
                .eq(AppSchedulingDevice::getDeviceNo, deviceNo)
        );
        if (schedulingDevices == null || schedulingDevices.isEmpty()) {
            log.warn("[MQTT] 开始浇水上报未找到设备绑定的排程 时间={} 设备编号={}", HandlerLogTime.now(), deviceNo);
            return 0L;
        }
        if (schedulingDevices.size() > 1) {
            log.warn("[MQTT] 开始浇水上报匹配到多个排程 时间={} 设备编号={} 数量={}", HandlerLogTime.now(), deviceNo, schedulingDevices.size());
        }
        return schedulingDevices.get(0).getScheduleId();
    }

    private Date parseStartTime(String startTime) {
        if (StringUtils.isBlank(startTime)) {
            return new Date();
        }
        for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"}) {
            try {
                SimpleDateFormat format = new SimpleDateFormat(pattern);
                format.setLenient(false);
                return format.parse(startTime);
            } catch (ParseException ignored) {
                // Try the next supported format.
            }
        }
        try {
            String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd H:mm");
            format.setLenient(false);
            return format.parse(today + " " + startTime);
        } catch (ParseException ignored) {
            // Fall through and report the unsupported format.
        }
        throw new IllegalArgumentException("Unsupported startTime format: " + startTime);
    }

    private String valueAsString(Object value) {
        return value == null ? null : String.valueOf(value);
    }

    private Integer valueAsInteger(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.intValue();
        }
        String text = String.valueOf(value);
        return StringUtils.isBlank(text) ? null : Integer.valueOf(text);
    }
}
