package org.dromara.app.service.impl;

import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.mqtt.DeviceCommand;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.app.service.IDeviceCommandPublisher;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.app.domain.bo.AppWateringLogBo;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 设备命令下发业务实现。
 * <p>
 * 负责命令构造、设备校验、下发生效，以及手动操作的浇水日志记录。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceCommandServiceImpl implements IDeviceCommandService {

    // 字段注入 + @Lazy 打破：DeviceMqttCommandPublisher → MqttClientManager → MqttMessageDispatcher
    //   → DeviceRegisterHandler → AppDeviceServiceImpl → DeviceCommandServiceImpl → IDeviceCommandPublisher 循环
    // commandPublisher 仅在 sendCommand/sendSwitchCommand 运行时调用，构造阶段无需立即解析
    @Lazy
    @Autowired
    private IDeviceCommandPublisher commandPublisher;

    private final IAppDeviceService deviceService;
    private final IAppWateringLogService wateringLogService;

    // ========================= 通用下发 =========================

    @Override
    public String sendCommand(DeviceCommand command) {
        validate(command);
        String commandId = commandPublisher.send(command);
        log.info("[CMD] command sent deviceNo={} commandType={} commandId={}",
            command.getDeviceNo(), command.getCommandType(), commandId);
        return commandId;
    }

    // ========================= 类型化下发 =========================

    @Override
    public String sendSwitchCommand(String deviceNo, String workStatus, String startTime, Integer durationMin) {
        assertDeviceExists(deviceNo);
        assertSwitchStatus(workStatus);
        if ("1".equals(workStatus)) {
            assertDurationPositive(durationMin);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setCommandType("switchDevice");
        command.getPayload().put("deviceNo", deviceNo);
        command.getPayload().put("cmd", workStatus);
        command.getPayload().put("startTime", startTime);
        if (durationMin != null) {
            command.getPayload().put("durationMin", durationMin);
        }

        String commandId = commandPublisher.send(command);
        log.info("[CMD] switch command sent deviceNo={} workStatus={} durationMin={} commandId={}",
            deviceNo, workStatus, durationMin, commandId);

        Long userId = LoginHelper.getUserId();
        if ("1".equals(workStatus)) {
            AppWateringLogBo logBo = buildManualStartLog(deviceNo, userId, commandId, startTime, durationMin);
            wateringLogService.insertByBo(logBo);
        } else {
            boolean updated = wateringLogService.finishManualLog(deviceNo, userId, new Date());
            if (!updated) {
                log.warn("[CMD] no active manual watering log found deviceNo={} userId={} stopCommandId={}",
                    deviceNo, userId, commandId);
            }
        }

        return commandId;
    }

    @Override
    public String sendCustomCommand(String deviceNo, String commandType, Map<String, Object> extra) {
        assertDeviceExists(deviceNo);

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setCommandType(commandType);
        if (extra != null) {
            command.getPayload().putAll(extra);
        }
        return sendCommand(command);
    }

    @Override
    public String sendBindDeviceCommand(String deviceNo) {
        AppDeviceVo device = deviceService.queryById(deviceNo);
        if (ObjectUtil.isNull(device)) {
            throw new ServiceException("设备不存在：" + deviceNo);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setCommandType("bindDevice");
        Map<String, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("bindStatus", true);
        objectObjectHashMap.put("deviceNo", "01");
        objectObjectHashMap.put("cmd", -1);
        command.getPayload().putAll(objectObjectHashMap);
        return sendCommand(command);
    }
// ========================= 校验 =========================

    private void validate(DeviceCommand command) {
        if (StringUtils.isBlank(command.getDeviceNo())) {
            throw new ServiceException("设备编号不能为空");
        }
        if (StringUtils.isBlank(command.getCommandType())) {
            throw new ServiceException("命令类型不能为空");
        }
        assertDeviceExists(command.getDeviceNo());
    }

    private void assertDeviceExists(String deviceNo) {
        AppDeviceVo device = deviceService.queryById(deviceNo);
        if (ObjectUtil.isNull(device)) {
            throw new ServiceException("设备不存在：" + deviceNo);
        }
        if (device.getUserId() == null) {
            throw new ServiceException("设备未绑定用户，无法下发命令：" + deviceNo);
        }
    }

    private void assertSwitchStatus(String workStatus) {
        if (!"0".equals(workStatus) && !"1".equals(workStatus)) {
            throw new ServiceException("设备工作状态只能为0或1");
        }
    }

    private void assertDurationPositive(Integer durationMin) {
        if (durationMin == null || durationMin <= 0) {
            throw new ServiceException("持续时间必须大于0");
        }
    }

    private AppWateringLogBo buildManualStartLog(
        String deviceNo,
        Long userId,
        String commandId,
        String startTime,
        Integer durationMin
    ) {
        AppWateringLogBo logBo = new AppWateringLogBo();
        logBo.setDeviceNo(deviceNo);
        logBo.setCommandId(commandId);
        logBo.setUserId(userId);
        logBo.setScheduleId(0L);
        logBo.setDurationMin(String.valueOf(durationMin));
        logBo.setTriggerType("1"); // 手动
        logBo.setCreateTime(new Date());

        if (StringUtils.isNotEmpty(startTime)) {
            logBo.setStartTime(parseStartTime(startTime));
        } else {
            logBo.setStartTime(new Date());
        }
        return logBo;
    }

    private Date parseStartTime(String startTime) {
        if (startTime.matches("^\\d{2}:\\d{2}$")) {
            try {
                Date parsedTime = new SimpleDateFormat("HH:mm").parse(startTime);
                Calendar parsed = Calendar.getInstance();
                parsed.setTime(parsedTime);

                Calendar today = Calendar.getInstance();
                today.set(Calendar.HOUR_OF_DAY, parsed.get(Calendar.HOUR_OF_DAY));
                today.set(Calendar.MINUTE, parsed.get(Calendar.MINUTE));
                today.set(Calendar.SECOND, 0);
                today.set(Calendar.MILLISECOND, 0);
                return today.getTime();
            } catch (ParseException e) {
                throw new ServiceException("开始时间格式错误，期望 HH:mm 或 yyyy-MM-dd HH:mm:ss");
            }
        }
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startTime);
        } catch (ParseException e) {
            throw new ServiceException("开始时间格式错误，期望 HH:mm 或 yyyy-MM-dd HH:mm:ss");
        }
    }
}
