package com.yupi.project.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.project.common.ErrorCode;
import com.yupi.project.exception.BusinessException;
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.ChargingSessionService;
import com.yupi.project.service.RobotTaskService;
import com.yupi.project.mapper.RobotTaskMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
* @author Yupi
* @description 针对表【robot_task(机器人指令任务表)】的数据库操作Service实现
* @createDate 2023-12-02 22:00:00
*/
@Service
@Slf4j
public class RobotTaskServiceImpl extends ServiceImpl<RobotTaskMapper, RobotTask>
    implements RobotTaskService {

    private final ChargingSessionService chargingSessionService;

    @Autowired
    public RobotTaskServiceImpl(@Lazy ChargingSessionService chargingSessionService) {
        this.chargingSessionService = chargingSessionService;
    }

    @Override
    public boolean hasPendingOrSentTask(String robotId) {
        if (robotId == null) {
            log.warn("Cannot check for pending/sent tasks: robotId is null.");
            return false; // Or throw an IllegalArgumentException
        }
        QueryWrapper<RobotTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("robot_id", robotId)
                    .in("status", RobotTaskStatusEnum.PENDING, RobotTaskStatusEnum.SENT);
        // .eq("is_delete", 0); // Add if @TableLogic is strictly used and you want to exclude logically deleted

        long count = this.count(queryWrapper);
        if (count > 0) {
            log.info("Robot {} has {} PENDING or SENT tasks.", robotId, count);
            return true;
        }
        return false;
    }

    @Override
    public boolean hasOtherPendingOrSentTask(String robotId, Long taskIdToExclude) {
        if (robotId == null) {
            log.warn("Cannot check for other pending/sent tasks: robotId is null.");
            return false;
        }
        QueryWrapper<RobotTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("robot_id", robotId)
                    .in("status", RobotTaskStatusEnum.PENDING, RobotTaskStatusEnum.SENT);
        if (taskIdToExclude != null) {
            queryWrapper.ne("id", taskIdToExclude); // Exclude the current task ID
        }
        long count = this.count(queryWrapper);
        if (count > 0) {
            log.info("Robot {} has {} other PENDING or SENT tasks (excluding task ID: {}).", robotId, count, taskIdToExclude == null ? "N/A" : taskIdToExclude);
            return true;
        }
        return false;
    }

    @Override
    @Transactional
    public RobotTask createTask(String robotId, CommandTypeEnum commandType, String payloadJson, Long sessionId) {
        if (robotId == null || commandType == null) {
            log.error("Cannot create task: robotId or commandType is null.");
            // Or throw an IllegalArgumentException
            return null;
        }
        RobotTask task = new RobotTask();
        task.setRobotId(robotId);
        task.setCommandType(commandType);
        task.setCommandPayload(payloadJson);
        task.setRelatedSessionId(sessionId);
        task.setStatus(RobotTaskStatusEnum.PENDING); // Default status
        // createTime and updateTime will be auto-filled by MyBatis-Plus
        // isDelete will be handled by @TableLogic if enabled (default 0)
        
        boolean saved = this.save(task);
        if (saved) {
            log.info("Created new RobotTask with ID: {}, Robot ID: {}, Command: {}, Status: PENDING", 
                     task.getId(), robotId, commandType);
            return task;
        } else {
            log.error("Failed to save new RobotTask for Robot ID: {}, Command: {}", robotId, commandType);
            return null;
        }
    }

    @Override
    @Transactional
    public boolean markTaskAsSent(Long taskId, Date sentTime) {
        if (taskId == null || sentTime == null) {
            log.error("Cannot mark task as sent: taskId or sentTime is null.");
            // 抛出异常而不是返回false
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "Task ID or sentTime cannot be null when marking task as SENT.");
        }
        RobotTask task = this.getById(taskId);
        if (task == null) {
            log.warn("Cannot mark task as sent: Task with ID {} not found.", taskId);
            // 抛出异常
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "Task with ID " + taskId + " not found.");
        }

        if (task.getStatus() != RobotTaskStatusEnum.PENDING) {
            log.warn("Cannot mark task {} as SENT. Current status is {}, expected PENDING.", taskId, task.getStatus());
            // 抛出异常
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "Task " + taskId + " is in status " + task.getStatus() + ", expected PENDING to mark as SENT.");
        }

        RobotTask updateTask = new RobotTask();
        updateTask.setId(taskId);
        updateTask.setStatus(RobotTaskStatusEnum.SENT);
        updateTask.setSentTime(sentTime);
        // updateTime will be auto-filled by MyBatis-Plus
        boolean updated = this.updateById(updateTask);
        if (updated) {
            log.info("Marked RobotTask with ID: {} as SENT at {}.", taskId, sentTime);
        } else {
            log.error("Failed to mark RobotTask with ID: {} as SENT. Update to database failed.", taskId);
            // 抛出异常
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "Failed to update task " + taskId + " to SENT in database.");
        }
        return true; // 如果没有抛出异常，则表示成功
    }

    @Override
    public RobotTask findLatestSentTaskByRobotId(String robotId) {
        if (robotId == null) {
            log.warn("Cannot find latest sent task: robotId is null.");
            return null;
        }
        QueryWrapper<RobotTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("robot_id", robotId)
                    .eq("status", RobotTaskStatusEnum.SENT)
                    // .eq("is_delete", 0) // If using soft delete
                    .orderByDesc("create_time") // Or sent_time, depending on desired logic for "latest"
                    .last("LIMIT 1"); // Get only the most recent one

        RobotTask task = this.getOne(queryWrapper);
        if (task != null) {
            log.info("Found latest SENT task with ID {} for Robot ID: {}.", task.getId(), robotId);
        } else {
            log.debug("No SENT task found for Robot ID: {}.", robotId);
        }
        return task;
    }

    @Override
    @Transactional
    public boolean markTaskAsAcknowledged(Long taskId, boolean success, String errorMessage, Date ackTime) {
        if (taskId == null || ackTime == null) {
            log.error("Cannot mark task as acknowledged: taskId or ackTime is null.");
            return false;
        }
        RobotTask task = this.getById(taskId);
        if (task == null) {
            log.warn("Cannot mark task as acknowledged: Task with ID {} not found.", taskId);
            return false;
        }

        // Optionally, check if current status allows this transition (e.g., from SENT)
        if (task.getStatus() != RobotTaskStatusEnum.SENT) {
            log.warn("Cannot mark task {} as acknowledged. Current status is {}, expected SENT.", taskId, task.getStatus());
            // Depending on logic, might still allow ack if it's a late response to a PENDING task that never got marked SENT by our system
            // For now, strict check from SENT.
            return false; 
        }

        RobotTask updateTask = new RobotTask();
        updateTask.setId(taskId);
        updateTask.setStatus(success ? RobotTaskStatusEnum.COMPLETED : RobotTaskStatusEnum.FAILED);
        updateTask.setAckTime(ackTime);
        if (!success && errorMessage != null) {
            updateTask.setErrorMessage(errorMessage);
        } else if (success) {
            updateTask.setErrorMessage(null); // Clear any previous error message on success
        }
        // updateTime will be auto-filled

        boolean updated = this.updateById(updateTask);
        if (updated) {
            log.info("Marked RobotTask with ID: {} as {}{}.", taskId, 
                     (success ? RobotTaskStatusEnum.COMPLETED.getValue() : RobotTaskStatusEnum.FAILED.getValue()), 
                     (success ? "" : " with error: " + errorMessage));
        } else {
            log.error("Failed to mark RobotTask with ID: {} as acknowledged.", taskId);
        }
        return updated;
    }

    @Override
    @Transactional
    public List<RobotTask> findAndMarkTimedOutTasks(int timeoutSeconds) {
        if (timeoutSeconds <= 0) {
            log.warn("Invalid timeoutSeconds: {}. Must be positive.", timeoutSeconds);
            return Collections.emptyList();
        }

        // Calculate the time before which tasks are considered timed out
        // Use Calendar to subtract seconds, more robust for date arithmetic
        java.util.Calendar cal = java.util.Calendar.getInstance();
        cal.add(java.util.Calendar.SECOND, -timeoutSeconds);
        Date timeoutThreshold = cal.getTime();

        QueryWrapper<RobotTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("status", RobotTaskStatusEnum.SENT)
                    .lt("sent_time", timeoutThreshold);
                    // .eq("is_delete", 0); // If using soft delete
        
        List<RobotTask> timedOutTasks = this.list(queryWrapper);

        if (timedOutTasks.isEmpty()) {
            log.debug("No tasks found that timed out (threshold: {}s, before {}).", timeoutSeconds, timeoutThreshold);
            return Collections.emptyList();
        }

        log.info("Found {} tasks that timed out (threshold: {}s, sent before {}). Attempting to mark them.", 
                 timedOutTasks.size(), timeoutSeconds, timeoutThreshold);

        for (RobotTask task : timedOutTasks) {
            RobotTask updateTask = new RobotTask();
            updateTask.setId(task.getId());
            updateTask.setStatus(RobotTaskStatusEnum.TIMED_OUT);
            updateTask.setErrorMessage("Task timed out after " + timeoutSeconds + " seconds.");
            // ackTime might not be relevant for a system-initiated timeout, or could be set to now
            updateTask.setAckTime(new Date()); // Or null, depending on definition
            
            // updateTime will be auto-filled
            boolean updated = this.updateById(updateTask);
            if (updated) {
                log.info("Marked task {} (Robot ID: {}) as TIMED_OUT.", task.getId(), task.getRobotId());
                // TODO: Potentially trigger further actions for a timed-out task (e.g., notify admin, update related session)
            } else {
                log.error("Failed to mark task {} (Robot ID: {}) as TIMED_OUT.", task.getId(), task.getRobotId());
                // Add to a list of tasks that failed to update, if needed for retry or reporting
            }
        }
        return timedOutTasks; // Return the list of tasks that were identified as timed out
    }

    @Override
    @Transactional
    public boolean markTaskAsProcessing(Long taskId, Date ackTime, String message) {
        if (taskId == null) {
            log.error("Cannot mark task as processing: taskId is null.");
            return false;
        }
        RobotTask task = this.getById(taskId);
        if (task == null) {
            log.warn("Cannot mark task as processing: Task with ID {} not found.", taskId);
            return false;
        }

        // Allow transition from PENDING or SENT to PROCESSING
        if (task.getStatus() != RobotTaskStatusEnum.PENDING && task.getStatus() != RobotTaskStatusEnum.SENT) {
            log.warn("Cannot mark task {} as PROCESSING. Current status is {}, expected PENDING or SENT.", taskId, task.getStatus());
            return false;
        }

        RobotTask updateTask = new RobotTask();
        updateTask.setId(taskId);
        updateTask.setStatus(RobotTaskStatusEnum.PROCESSING);
        if (ackTime != null) { // ackTime might be the time processing was confirmed
            updateTask.setAckTime(ackTime);
        }
        if (message != null) {
            updateTask.setErrorMessage(message); // Using errorMessage field for general status messages during processing
        }
        
        boolean updated = this.updateById(updateTask);
        if (updated) {
            log.info("Marked RobotTask with ID: {} as PROCESSING{}.", taskId, (message != null ? " with message: " + message : ""));
        } else {
            log.error("Failed to mark RobotTask with ID: {} as PROCESSING.", taskId);
        }
        return updated;
    }

    @Override
    @Transactional
    public boolean markTaskAsCompleted(Long taskId, Date ackTime, String message) {
        RobotTask task = this.getById(taskId);
        if (task == null) {
            log.warn("尝试将任务标记为完成失败，任务不存在: {}", taskId);
            return false;
        }

        task.setStatus(RobotTaskStatusEnum.COMPLETED);
        task.setAckTime(ackTime);
        boolean updated = this.updateById(task);
        if (updated) {
            log.info("任务 {} 已成功标记为 COMPLETED", taskId);

            // 关键修复：检查任务类型，并触发相应的会话状态推进
            CommandTypeEnum commandType = task.getCommandType();
            if (commandType == CommandTypeEnum.STOP_CHARGE && task.getRelatedSessionId() != null) {
                log.info("检测到 STOP_CHARGE 任务完成，开始处理会话 {} 的结束流程...", task.getRelatedSessionId());
                // 此处可以从 message 中解析出耗电量和时长，但为简化，我们让 handleChargingEnd 内部自己计算
                chargingSessionService.handleChargingEnd(task.getRelatedSessionId(), taskId, null, 0);
            }
            // 可以为其他任务类型（如 MOVE_TO_SPOT）添加类似逻辑，但当前只处理STOP_CHARGE
        }
        return updated;
    }

    @Override
    @Transactional
    public boolean markTaskAsFailed(Long taskId, String errorMessage, Date failedTime) {
        RobotTask task = this.getById(taskId);
        if (task == null) {
            log.warn("尝试标记任务 {} 为失败失败：任务不存在", taskId);
            return false;
        }
        // 只能标记非终态的任务
        if (RobotTaskStatusEnum.isFinalStatus(task.getStatus())) {
            log.warn("任务 {} 当前状态为 {} (终态)，无法标记为失败", taskId, task.getStatus());
            return false;
        }
        task.setStatus(RobotTaskStatusEnum.FAILED);
        task.setErrorMessage(errorMessage);
        task.setAckTime(failedTime); // Using ackTime field to store the failure time
        task.setUpdateTime(new Date());
        boolean updated = this.updateById(task);
        if (updated) {
            log.info("机器人任务 {} 已标记为失败. 原因: {}", taskId, errorMessage);
        }
        return updated;
    }

    @Override
    public RobotTask findLatestTaskByRobotIdAndSessionId(String robotId, Long sessionId) {
        if (robotId == null || sessionId == null) {
            log.warn("Cannot find latest task: robotId or sessionId is null.");
            return null;
        }
        QueryWrapper<RobotTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("robot_id", robotId)
                .eq("related_session_id", sessionId)
                .orderByDesc("create_time")
                .last("LIMIT 1");

        RobotTask task = this.getOne(queryWrapper);
        if (task != null) {
            log.info("Found latest task with ID {} for Robot ID: {} and Session ID: {}.", task.getId(), robotId, sessionId);
        } else {
            log.debug("No task found for Robot ID: {} and Session ID: {}.", robotId, sessionId);
        }
        return task;
    }
} 