package org.dromara.app.handler;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.dromara.app.domain.AppSchedulingDevice;
import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.app.domain.vo.AppDeviceVo;
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.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 排程浇水完成处理器，匹配 /{deviceNo}/publish/finish/schedule。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ScheduleFinishHandler implements MqttTopicHandler {

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

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

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

    @Override
    public void handle(String deviceNo, String payload) {
        try {
            Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
            AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
            appWateringLogService.insertByBo(logBo);
            log.info("[MQTT] 浇水排程上报 saved deviceNo={} payload={}", deviceNo, payload);
        } catch (Exception e) {
            log.error("[MQTT] 浇水排程上报 failed deviceNo={} payload={}", deviceNo, payload, e);
        }
    }

    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] 排程完成上报设备编号不一致 topicDeviceNo={} payloadDeviceNo={}", topicDeviceNo, payloadDeviceNo);
        }


        Date receivedAt = new Date();
        Date startTime = parseStartTime(valueAsString(dto.get("startTime")));
        Date plannedEndTime = resolvePlannedEndTime(
            startTime,
            parseOptionalTime(valueAsString(dto.get("endTime"))),
            valueAsInteger(dto.get("durationMin"))
        );
        Date endTime = resolveActualEndTime(receivedAt, plannedEndTime);
        Integer durationMin = calculateDurationMin(startTime, endTime);

        AppWateringLogBo logBo = new AppWateringLogBo();
        logBo.setDeviceNo(topicDeviceNo);
        logBo.setCommandId(valueAsString(dto.get("commandId")));
        logBo.setUserId(queryUserId(topicDeviceNo));
        logBo.setScheduleId(queryScheduleId(topicDeviceNo));
        logBo.setStartTime(startTime);
        logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
        logBo.setTriggerType("0");
        if ("mqtt on".equals(valueAsString(dto.get("triggerON")))){
            logBo.setTriggerType("1");
        }
        logBo.setCreateTime(receivedAt);
        logBo.setEndTime(endTime);
        return logBo;
    }

    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] 排程完成上报未找到设备绑定的排程 deviceNo={}", deviceNo);
            return 0L;
        }
        if (schedulingDevices.size() > 1) {
            log.warn("[MQTT] 排程完成上报匹配到多个排程 deviceNo={} count={}", 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.
            }
        }
        throw new IllegalArgumentException("Unsupported startTime format: " + startTime);
    }

    private Date parseOptionalTime(String time) {
        if (StringUtils.isBlank(time)) {
            return null;
        }
        return parseStartTime(time);
    }

    private Date resolvePlannedEndTime(Date startTime, Date payloadEndTime, Integer durationMin) {
        if (startTime != null && durationMin != null && durationMin > 0) {
            return new Date(startTime.getTime() + durationMin * 60000L);
        }
        return payloadEndTime;
    }

    private Date resolveActualEndTime(Date receivedAt, Date plannedEndTime) {
        if (plannedEndTime == null) {
            return receivedAt;
        }
        return receivedAt.before(plannedEndTime) ? receivedAt : plannedEndTime;
    }

    private Integer calculateDurationMin(Date startTime, Date endTime) {
        if (startTime == null || endTime == null) {
            return null;
        }
        long diffMillis = endTime.getTime() - startTime.getTime();
        if (diffMillis < 0) {
            log.warn("[MQTT] 排程完成上报结束时间早于开始时间 startTime={} endTime={}", startTime, endTime);
            return 0;
        }
        return (int) (diffMillis / 60000L);
    }

    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);
    }

}
