package com.yupi.project.service.impl;

import com.yupi.project.config.properties.MqttProperties;
import com.yupi.project.model.entity.RobotTask;
import com.yupi.project.model.enums.CommandTypeEnum;
import com.yupi.project.model.enums.RobotTaskStatusEnum;
import com.yupi.project.service.MqttService;
import com.yupi.project.service.RobotTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Map;

/**
 * MQTT 服务实现类
 * 负责向 MQTT Broker 发送指令给机器人/设备
 */
@Service
@Slf4j
public class MqttServiceImpl implements MqttService {

    private final MqttClient mqttClient; // 由Spring从MqttConfig注入的MQTT客户端实例
    private final MqttProperties mqttProperties; // MQTT相关的配置属性
    private final RobotTaskService robotTaskService; // 机器人任务服务，用于创建和更新任务状态
    private final ObjectMapper objectMapper; // Jackson ObjectMapper for JSON manipulation

    // 构造函数注入依赖
    public MqttServiceImpl(@Qualifier("mqttClientBean") MqttClient mqttClient,
                          MqttProperties mqttProperties,
                          RobotTaskService robotTaskService,
                          ObjectMapper objectMapper) {
        this.mqttClient = mqttClient;
        this.mqttProperties = mqttProperties;
        this.robotTaskService = robotTaskService;
        this.objectMapper = objectMapper;
    }

    /**
     * 发送指令给指定的机器人/设备
     *
     * @param robotId 目标机器人的唯一标识符
     * @param commandType 指令类型 (枚举)
     * @param payloadJson 指令的JSON格式负载
     * @param sessionId (可选) 与此指令关联的充电会话ID
     * @param associatedTaskId (可选) 此指令关联的、已由调用方创建的RobotTask ID。
     * @return 指令是否发送成功 (注意：这仅表示MQTT消息已发布，不代表设备已执行)
     * @throws Exception 如果发送过程中发生MQTT异常或其他意外异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean sendCommand(String robotId, CommandTypeEnum commandType, String payloadJson, Long sessionId, Long associatedTaskId) throws Exception {
        log.info("尝试向机器人 {} 发送指令 {} (关联任务ID: {})，负载: {}", commandType, robotId, associatedTaskId == null ? "N/A" : associatedTaskId, payloadJson);

        if (associatedTaskId == null) {
            log.error("MqttServiceImpl.sendCommand 错误: associatedTaskId 不能为 null。任务应由调用者预先创建。机器人 {}，指令 {}", robotId, commandType);
            // 根据当前设计，associatedTaskId 是必需的，因为 MqttServiceImpl 不再负责创建任务。
            throw new IllegalArgumentException("associatedTaskId cannot be null. Task must be pre-created by caller.");
        }

        // 步骤1：检查机器人是否已有 *其他* 正在处理(PENDING)或已发送(SENT)的任务
        if (robotTaskService.hasOtherPendingOrSentTask(robotId, associatedTaskId)) {
            if (CommandTypeEnum.STOP_CHARGE.equals(commandType)) {
                log.info("机器人 {} 有其他正在处理的任务，但 STOP_CHARGE 是优先指令，继续执行任务 {}。", robotId, associatedTaskId);
            } else {
                log.warn("机器人 {} 正忙 (存在其他PENDING或SENT状态的任务)，指令 {} (任务 {}) 已中止。", robotId, commandType, associatedTaskId);
                // 注意：即使中止，预先创建的 associatedTaskId 任务仍然是 PENDING。调用者需要处理此情况。
                return false; 
            }
        }

        // 步骤2：获取预先创建的任务 (associatedTaskId)
        RobotTask taskToProcess = robotTaskService.getById(associatedTaskId);
        if (taskToProcess == null) {
            log.error("为机器人 {} 的指令 {} 查找预创建的 RobotTask (ID: {}) 失败。发送操作中止。", commandType, robotId, associatedTaskId);
            return false; // 任务未找到
        }
        // 校验任务状态是否为 PENDING，因为后续会将其标记为 SENT
        if (taskToProcess.getStatus() != RobotTaskStatusEnum.PENDING) {
            log.warn("预创建的任务 {} (机器人 {}, 指令 {}) 状态为 {}，而非预期的 PENDING。可能已被处理或状态错误。发送操作中止。", 
                    associatedTaskId, robotId, commandType, taskToProcess.getStatus());
            return false;
        }

        log.info("处理预创建的 PENDING 状态 RobotTask ID: {}，机器人 {}，指令 {}。", associatedTaskId, robotId, commandType);

        // <<<< START MODIFICATION: Add/Ensure taskId in payloadJson using associatedTaskId >>>>
        String finalPayloadJson;
        try {
            // Parse the original payloadJson
            // If payloadJson is null or empty, create a new JSON object
            // Otherwise, parse it and add/overwrite the taskId
            ObjectNode payloadNode;
            if (payloadJson == null || payloadJson.trim().isEmpty()) {
                payloadNode = objectMapper.createObjectNode();
            } else {
                // Check if it's a valid JSON, otherwise treat as non-JSON or handle error
                try {
                    payloadNode = (ObjectNode) objectMapper.readTree(payloadJson);
                } catch (Exception e) {
                    // If original payload is not a valid JSON, we might log a warning
                    // and create a new JSON object just for the taskId, or wrap the original string.
                    // For now, let's assume if it's not empty, it should be valid JSON.
                    // Or, simpler: create a Map, add taskId, then add all from original parsed map.
                    log.warn("Original payloadJson for robot {} command {} is not a valid JSON object: {}. Proceeding by creating a new JSON with taskId.", robotId, commandType, payloadJson, e);
                    // Fallback: create a new node. This might lose original non-JSON payload if that was intended.
                    // A safer approach for non-JSON or mixed payloads might be needed if that's a use case.
                    payloadNode = objectMapper.createObjectNode(); 
                    // If you expect non-JSON payloads and want to preserve them, this part needs more thought.
                    // For now, assuming command payloads are always JSON.
                }
            }
            
            payloadNode.put("taskId", String.valueOf(associatedTaskId)); // NEW: use associatedTaskId
            
            // For START_CHARGE, ensure sessionId from method argument is also in the payload if not already
            // This is based on ESP32 code expecting sessionId in payload for START_CHARGE
            if (CommandTypeEnum.START_CHARGE.equals(commandType) && sessionId != null) {
                if (!payloadNode.has("sessionId") || payloadNode.get("sessionId") == null || payloadNode.get("sessionId").isNull()) {
                     payloadNode.put("sessionId", sessionId); // Add sessionId if not present
                     log.info("Added sessionId {} to START_CHARGE command payload for task {}.", sessionId, associatedTaskId);
                }
            }
            
            finalPayloadJson = objectMapper.writeValueAsString(payloadNode);
        } catch (Exception e) {
            log.error("为机器人 {} 的指令 {} (任务ID: {}) 添加 taskId 到 payload 时发生JSON处理异常: {}", commandType, robotId, associatedTaskId, e.getMessage(), e);
            // Mark task as failed because we cannot construct a valid payload
            robotTaskService.markTaskAsFailed(associatedTaskId, "Payload JSON processing error: " + e.getMessage(), new Date());
            return false; // Payload construction failed
        }
        // <<<< END MODIFICATION >>>>

        // 3. 构建目标MQTT主题
        // 主题格式: [mqttProperties.commandTopicBase]/[robotId]
        // 例如: yupi_mqtt_power_project/robot/command/ESP32_SPOT_001
        String topic = mqttProperties.getCommandTopicBase() + "/" + robotId;

        // 4. 准备MQTT消息
        MqttMessage mqttMessage = new MqttMessage(finalPayloadJson.getBytes(StandardCharsets.UTF_8));
        mqttMessage.setQos(mqttProperties.getDefaultQos()); // 设置QoS级别，从配置中读取
        // mqttMessage.setRetained(false); // 保留消息标志，默认为false，通常指令不需要保留

        try {
            // 5. 发布MQTT消息
            if (!mqttClient.isConnected()) {
                log.error("MQTT客户端未连接。无法向机器人 {} 发送指令 {}。任务ID: {}", commandType, robotId, associatedTaskId);
                // 任务将保持PENDING状态。后续可由定时任务重试或标记为错误。
                return false; 
            }
            log.debug("正在向主题发布消息: {}, QoS: {}, 负载: {}", topic, mqttMessage.getQos(), finalPayloadJson);
            mqttClient.publish(topic, mqttMessage);
            log.info("成功向机器人 {} 在主题 {} 上发布指令 {}。任务ID: {}", commandType, robotId, topic, associatedTaskId);

            // 6. 将机器人任务的状态标记为 SENT (已发送)
            boolean markedAsSent = robotTaskService.markTaskAsSent(associatedTaskId, new Date());
            if (!markedAsSent) {
                log.warn("指令 {} 已发布给机器人 {}，但将任务 {} 标记为 SENT 失败。系统可能处于不一致状态。", 
                         commandType, robotId, associatedTaskId);
                // 这是一个严重警告。指令已发送，但数据库状态未正确反映。
                // 事务应回滚。
            }
            return true; // 指令已发送，并尝试更新了任务状态
        } catch (MqttException e) { 
            log.error("向机器人 {} 发布指令 {} (任务ID: {}) 时发生MqttException。错误: {}", 
                      commandType, robotId, associatedTaskId, e.getMessage(), e);
            // RobotTask (associatedTaskId) 将保持 PENDING 状态 (或它之前的状态)。事务应回滚。
            throw e; // 重新抛出异常，以便Spring的@Transactional进行事务回滚
        } catch (Exception e) {
            log.error("向机器人 {} 发送指令 {} (任务ID: {}) 时发生意外异常。错误: {}",
                      commandType, robotId, associatedTaskId, e.getMessage(), e);
            throw e; // 重新抛出异常，以便事务回滚
        }
    }
} 