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.bo.AppWateringLogBo;
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.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.*;

/**
 * 设备命令下发业务实现。
 * <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);
        if (StringUtils.isBlank(command.getDeviceMac())) {
            command.setDeviceMac(requireDeviceMac(command.getDeviceNo()));
        }
        String commandId = commandPublisher.send(command);
        log.info("[命令] 命令已下发 设备编号={} 命令类型={} 命令编号={}",
            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.setDeviceMac(requireDeviceMac(deviceNo));
        command.setCommandType("switchDevice");
        String commandStartTime = normalizeCommandStartTime(startTime);
        command.getPayload().put("deviceNo", deviceNo);
        command.getPayload().put("cmd", workStatus);
        command.getPayload().put("startTime", commandStartTime);
        if (durationMin != null) {
            command.getPayload().put("durationMin", durationMin);
        }

        String commandId = commandPublisher.send(command);
        log.info("[命令] 开关命令已下发 设备编号={} 工作状态={} 持续分钟数={} 命令编号={}",
            deviceNo, workStatus, durationMin, commandId);

        Long userId = LoginHelper.getUserId();
        if ("1".equals(workStatus)) {
            AppWateringLogBo logBo = buildManualStartLog(deviceNo, userId, commandId, commandStartTime, durationMin);
            wateringLogService.insertByBo(logBo);
        } else {
            int updated = wateringLogService.finishRunningLogsByDevice(deviceNo, new Date(), "手动停止浇水");
            if (updated <= 0) {
                log.warn("[命令] 未找到进行中的浇水记录 设备编号={} 用户ID={} 停止命令编号={}",
                    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.setDeviceMac(requireDeviceMac(deviceNo));
        command.setCommandType(commandType);
        if (extra != null) {
            command.getPayload().putAll(extra);
        }
        return sendCommand(command);
    }

    @Override
    public String sendScheduleBindCommand(String deviceNo, Map<String, Object> payload) {
        AppDeviceVo device = requireCommandDevice(deviceNo);
        if (StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发排程：" + deviceNo);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setDeviceMac(device.getMacAddress());
        command.setCommandType("bindSchedule");
        command.setTopic(buildScheduleTopic(deviceNo));
        if (payload != null) {
            command.getPayload().putAll(payload);
        }
        return sendCommand(command);
    }

    @Override
    public String sendBindDeviceCommand(String deviceNo) {
        AppDeviceVo device = deviceService.queryById(deviceNo);
        if (ObjectUtil.isNull(device)) {
            throw new ServiceException("设备不存在：" + deviceNo);
        }
        if (StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发绑定命令：" + deviceNo);
        }

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

    @Override
    public String sendScheduleUnbindCommand(String deviceNo, Long scheduleId) {
        AppDeviceVo device = requireCommandDevice(deviceNo);
        if (StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发排程解绑命令：" + deviceNo);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setDeviceMac(device.getMacAddress());
        command.setCommandType("unbindSchedule");
        command.setTopic(buildScheduleTopic(deviceNo));
        command.getPayload().put("cmd", -1);
        command.getPayload().put("deviceNo", deviceNo);
        command.getPayload().put("scheduleId", scheduleId);
        command.getPayload().put("unbind", true);
        return sendCommand(command);
    }

    @Override
    public String sendScheduleCanceledCommand(String deviceNo, Long scheduleId) {
        AppDeviceVo device = requireCommandDevice(deviceNo);
        if (StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发排程取消命令：" + deviceNo);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setDeviceMac(device.getMacAddress());
        command.setCommandType("cancelSchedule");
        command.setTopic(buildScheduleCanceledTopic(deviceNo));
        command.getPayload().put("deviceNo", deviceNo);
        command.getPayload().put("scheduleId", scheduleId);
        command.getPayload().put("canceled", true);
        return sendCommand(command);
    }

    @Override
    public String sendInitDeviceCommand(String deviceNo) {
        AppDeviceVo device = deviceService.queryById(deviceNo);
        if (ObjectUtil.isNull(device)) {
            throw new ServiceException("设备不存在：" + deviceNo);
        }
        if (StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发初始化命令：" + deviceNo);
        }

        DeviceCommand command = new DeviceCommand();
        command.setDeviceNo(deviceNo);
        command.setDeviceMac(device.getMacAddress());
        command.setCommandType("initDevice");
        Map<String, Object> payload = new HashMap<>();
        payload.put("deviceNo", deviceNo);
        payload.put("initStatus", true);
        payload.put("cmd", "init");
        command.getPayload().putAll(payload);

        String commandId = commandPublisher.send(command);
        log.info("[命令] 设备初始化命令已下发 设备编号={} 命令编号={}", deviceNo, commandId);
        return commandId;
    }
// ========================= 校验 =========================

    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 String requireDeviceMac(String deviceNo) {
        AppDeviceVo device = deviceService.queryById(deviceNo);
        if (device == null || StringUtils.isBlank(device.getMacAddress())) {
            throw new ServiceException("设备未登记 MAC，无法下发命令：" + deviceNo);
        }
        return device.getMacAddress();
    }

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

    private String buildScheduleTopic(String deviceNo) {
        return "/" + deviceNo.trim().toLowerCase(Locale.ROOT) + "/subscriber/schedule";
    }

    private String buildScheduleCanceledTopic(String deviceNo) {
        return "/" + deviceNo.trim().toLowerCase(Locale.ROOT) + "/schedule/canceled";
    }

    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
    ) {
        Date starttime = parseStartTime(valueAsString(startTime));
        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.setRemark("手动开始浇水");
        logBo.setCreateTime(new Date());
        logBo.setStatus("1");
        logBo.setEndTime(resolveEndTime(starttime, durationMin));

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

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

    private String normalizeCommandStartTime(String startTime) {
        if (StringUtils.isBlank(startTime)) {
            return startTime;
        }
        if (startTime.matches("^\\d{2}:\\d{2}$")) {
            return startTime + ":00";
        }
        return startTime;
    }

    private Date resolveEndTime(Date startTime, Integer durationMin) {
        if (startTime == null || durationMin == null || durationMin <= 0) {
            return null;
        }
        return new Date(startTime.getTime() + durationMin * 60000L);
    }
    private Date parseStartTime(String startTime) {
        if (startTime.matches("^\\d{2}:\\d{2}:\\d{2}$")) {
            try {
                Date parsedTime = new SimpleDateFormat("HH:mm:ss").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, parsed.get(Calendar.SECOND));
                today.set(Calendar.MILLISECOND, 0);
                return today.getTime();
            } catch (ParseException e) {
                throw new ServiceException("开始时间格式错误，期望 HH:mm、HH:mm:ss 或 yyyy-MM-dd HH:mm:ss");
            }
        }
        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、HH:mm:ss 或 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、HH:mm:ss 或 yyyy-MM-dd HH:mm:ss");
        }
    }
}
