package com.gameplatform.server.service.link;

import com.gameplatform.server.mapper.agent.LinkBatchMapper;
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
import com.gameplatform.server.model.dto.link.BatchDeleteResponse;
import com.gameplatform.server.model.dto.link.LinkStatusResponse;
import com.gameplatform.server.model.dto.link.PollLoginResponse;
import com.gameplatform.server.model.dto.link.SelectRegionResponse;
import com.gameplatform.server.model.dto.link.UserLinkStatusResponse;
import com.gameplatform.server.model.dto.link.TargetScoreResponse;
import com.gameplatform.server.model.entity.agent.LinkBatch;
import com.gameplatform.server.model.entity.agent.LinkTask;

import com.gameplatform.server.service.external.ScriptClient;
import com.gameplatform.server.device.DeviceStats;
import org.springframework.beans.factory.annotation.Autowired;
import com.gameplatform.server.service.device.DeviceStatusCheckService;
import com.gameplatform.server.service.admin.SystemConfigService;
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class LinkStatusService {
    private static final Logger log = LoggerFactory.getLogger(LinkStatusService.class);

    private final LinkTaskMapper linkTaskMapper;
    private final LinkBatchMapper linkBatchMapper;
    private final ScriptClient scriptClient;
    private final DeviceStatusCheckService deviceStatusCheckService;
    private final SystemConfigService systemConfigService;
    private final MemoryMachineCooldownService machineCooldownService;
    private final DeviceAllocationService deviceAllocationService;
    private final DeviceStats deviceStats;
    @Autowired(required = false)
    private com.gameplatform.server.service.detection.GameCompletionDetectionService completionDetectionService;
    @Autowired(required = false)
    private com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper statusHistoryMapper;

    // 状态描述映射
    private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
    static {
        STATUS_DESC_MAP.put("NEW", "新建");
        STATUS_DESC_MAP.put("USING", "使用中");
        STATUS_DESC_MAP.put("LOGGED_IN", "已登录");
        STATUS_DESC_MAP.put("COMPLETED", "已完成");
        STATUS_DESC_MAP.put("REFUNDED", "已退款");
        STATUS_DESC_MAP.put("EXPIRED", "已过期");
    }

        public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
                            ScriptClient scriptClient,
                            DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService, MemoryMachineCooldownService machineCooldownService, DeviceAllocationService deviceAllocationService, DeviceStats deviceStats) {
        this.linkTaskMapper = linkTaskMapper;
        this.linkBatchMapper = linkBatchMapper;
        this.scriptClient = scriptClient;
        this.deviceStatusCheckService = deviceStatusCheckService;
        this.systemConfigService = systemConfigService;
        this.machineCooldownService = machineCooldownService;
        this.deviceAllocationService = deviceAllocationService;
        this.deviceStats = deviceStats;
    }

    /**
     * 根据链接编号获取链接状态
     */
    public Mono<LinkStatusResponse> getLinkStatus(String codeNo) {
        return Mono.fromCallable(() -> doGetLinkStatus(codeNo))
                .subscribeOn(Schedulers.boundedElastic());
    }

    private LinkStatusResponse doGetLinkStatus(String codeNo) {
        if (codeNo == null || codeNo.trim().isEmpty()) {
            throw new IllegalArgumentException("链接编号不能为空");
        }

        log.debug("查询链接状态: codeNo={}", codeNo);

        // 查询链接任务
        LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
        if (linkTask == null) {
            throw new IllegalArgumentException("链接不存在 " + codeNo);
        }

        // 查询批次信息
        LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
        if (linkBatch == null) {
            throw new IllegalStateException("批次信息不存在 batchId=" + linkTask.getBatchId());
        }

        // 构建响应对象
        LinkStatusResponse response = new LinkStatusResponse();
        response.setCodeNo(linkTask.getCodeNo());
        response.setBatchId(linkTask.getBatchId());
        response.setStatus(linkTask.getStatus());
        response.setStatusDesc(STATUS_DESC_MAP.getOrDefault(linkTask.getStatus(), "未知状态"));
        response.setExpireAt(linkTask.getExpireAt());
        response.setQuantity(linkBatch.getQuantity());
        response.setTimes(linkBatch.getTimes());
        response.setTotalPoints(linkBatch.getQuantity() * linkBatch.getTimes());
        response.setMachineId(linkTask.getMachineId());
        response.setLoginAt(linkTask.getLoginAt());
        response.setCreatedAt(linkTask.getCreatedAt());
        response.setUpdatedAt(linkTask.getUpdatedAt());

        // 计算过期状态和剩余时间
        LocalDateTime now = LocalDateTime.now();
        boolean isExpired = now.isAfter(linkTask.getExpireAt());
        response.setIsExpired(isExpired);

        if (!isExpired) {
            long remainingSeconds = ChronoUnit.SECONDS.between(now, linkTask.getExpireAt());
            response.setRemainingSeconds(remainingSeconds);
        } else {
            response.setRemainingSeconds(0L);
        }

        log.debug("链接状态查询完成 codeNo={}, status={}, isExpired={}, remainingSeconds={}", 
                codeNo, linkTask.getStatus(), isExpired, response.getRemainingSeconds());

        return response;
    }

    /**
     * 检查链接是否存在
     */
    public Mono<Boolean> isLinkExists(String codeNo) {
        return Mono.fromCallable(() -> {
            if (codeNo == null || codeNo.trim().isEmpty()) {
                return false;
            }
            LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
            return linkTask != null;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 检查链接是否有效（未过期且状态正常）
     */
    public Mono<Boolean> isLinkValid(String codeNo) {
        return getLinkStatus(codeNo)
                .map(response -> !response.getIsExpired() && 
                     ("NEW".equals(response.getStatus()) || "USING".equals(response.getStatus())))
                .onErrorReturn(false);
    }

    /**
     * 退单操作
     * @param codeNo 链接编号
     * @param agentId 代理ID（确保用户只能退单自己的链接）
     * @return 退单结果
     */
    @Transactional
    public Mono<Boolean> refundOrder(String codeNo, Long agentId) {
        return Mono.fromCallable(() -> doRefundOrder(codeNo, agentId))
                .subscribeOn(Schedulers.boundedElastic());
    }

    private Boolean doRefundOrder(String codeNo, Long agentId) {
        log.info("=== 开始处理退单操作 ===");
        log.info("链接编号: {}, 代理ID: {}", codeNo, agentId);
    
        try {
            // 1. 查询链接任务
            LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
            if (linkTask == null) {
                log.error("链接任务不存在 codeNo={}", codeNo);
                throw new IllegalArgumentException("链接不存在");
            }
    
            // 2. 验证代理ID（确保用户只能退单自己的链接）
            if (!linkTask.getAgentId().equals(agentId)) {
                log.error("权限不足: 用户{}无权退单链接{}", agentId, codeNo);
                throw new IllegalArgumentException("权限不足：您无权操作此链接");
            }
    
            // 3. 检查链接状态是否允许退单
            String currentStatus = linkTask.getStatus();
            if ("REFUNDED".equals(currentStatus)) {
                log.warn("链接已经退过单: codeNo={}, status={}", codeNo, currentStatus);
                throw new IllegalStateException("链接已经退过单");
            }
            
            if ("EXPIRED".equals(currentStatus)) {
                log.warn("过期链接不允许退单: codeNo={}, status={}", codeNo, currentStatus);
                throw new IllegalStateException("过期链接不允许退单");
            }
    
            if ("COMPLETED".equals(currentStatus)) {
                log.warn("已完成链接不允许退单: codeNo={}, status={}", codeNo, currentStatus);
                throw new IllegalStateException("已完成链接不允许退单");
            }
    
            // 4. 如果链接有关联的设备，调用脚本端退单接口
            String machineId = linkTask.getMachineId();
            if (machineId != null && !machineId.trim().isEmpty()) {
                log.info("链接关联设备: {}, 调用脚本端退单接口", machineId);
                
                try {
                    // 同步调用脚本端退单接口
                    String refundResult = scriptClient.refundOrder(machineId).block();
                    log.info("脚本端退单接口调用成功, 设备={}, 结果={}", machineId, refundResult);
                } catch (Exception e) {
                    log.error("脚本端退单接口调用失败, 设备={}, 错误={}", machineId, e.getMessage());
                    // 即使脚本端调用失败，我们仍然继续更新数据库状态
                    // 这样可以确保用户能够看到退单状态，避免重复退单
                }
            } else {
                log.info("链接未关联设备，跳过脚本端退单调用");
            }
    
            // 5. 更新链接状态为 REFUNDED
            linkTask.setStatus("REFUNDED");
            linkTask.setReason("状态：已退款");
            linkTask.setRefundAt(LocalDateTime.now());
            linkTask.setUpdatedAt(LocalDateTime.now());
            
            int updateResult = linkTaskMapper.updateById(linkTask);
            if (updateResult <= 0) {
                log.error("更新链接状态失败 codeNo={}", codeNo);
                throw new RuntimeException("更新链接状态失败");
            }
    
            log.info("退单操作成功: codeNo={}, 设备={}, 状态更新为 REFUNDED", codeNo, machineId);
            log.info("=== 退单操作完成 ===");
            return true;
    
        } catch (Exception e) {
            log.error("=== 退单操作失败 ===");
            log.error("codeNo={}, agentId={}, 错误详情: {}", codeNo, agentId, e.getMessage(), e);
            throw e;
        }
    }

   /**
 * 删除链接（确保用户只能删除自己的链接）
 */
public Mono<Boolean> deleteLink(String codeNo, Long agentId) {
    return Mono.fromCallable(() -> {
        log.info("开始删除链接: codeNo={}, agentId={}", codeNo, agentId);
        
        // 首先检查链接是否存在且属于该用户
        LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
        if (linkTask == null) {
            log.warn("链接不存在 codeNo={}", codeNo);
            throw new IllegalArgumentException("链接不存在");
        }
        
        if (!linkTask.getAgentId().equals(agentId)) {
            log.warn("用户无权删除该链接: codeNo={}, linkOwner={}, requestUser={}", 
                    codeNo, linkTask.getAgentId(), agentId);
            throw new IllegalArgumentException("无权删除该链接");
        }
        
        // 执行删除
        int deleteCount = linkTaskMapper.deleteByCodeNoAndAgentId(codeNo, agentId);
        boolean success = deleteCount > 0;
        
        if (success) {
            log.info("链接删除成功: codeNo={}, agentId={}", codeNo, agentId);
        } else {
            log.warn("链接删除失败: codeNo={}, agentId={}", codeNo, agentId);
        }
        
        return success;
    }).subscribeOn(Schedulers.boundedElastic());
}

/**
 * 按状态批量删除链接（确保用户只能删除自己的链接）
 */
@Transactional(rollbackFor = Exception.class)
public Mono<BatchDeleteResponse> batchDeleteLinksByStatus(List<String> statusList, Long agentId) {
    return Mono.fromCallable(() -> {
        log.info("开始按状态批量删除链接: statusList={}, agentId={}", statusList, agentId);
        
        if (statusList == null || statusList.isEmpty()) {
            throw new IllegalArgumentException("要删除的状态列表不能为空");
        }
        
        if (statusList.size() > 10) {
            throw new IllegalArgumentException("单次最多只能指定10个状态");
        }
        
        // 验证状态值是否有效
        List<String> validStatuses = List.of("NEW", "USING", "LOGGED_IN", "COMPLETED", "REFUNDED", "EXPIRED");
        for (String status : statusList) {
            if (!validStatuses.contains(status)) {
                throw new IllegalArgumentException("无效的状态值: " + status);
            }
        }
        
        // 统计要删除的链接数量
        long totalCount = linkTaskMapper.countByStatusListAndAgentId(statusList, agentId);
        log.info("用户拥有的指定状态链接数量: {}", totalCount);
        
        if (totalCount == 0) {
            // 没有符合条件的链接
            BatchDeleteResponse response = new BatchDeleteResponse(0, 0, 0, 
                    List.of(), List.of(), List.of());
            log.info("没有找到符合条件的链接");
            return response;
        }
        
        // 查询要删除的链接详情（用于记录日志）
        List<LinkTask> linksToDelete = linkTaskMapper.findByStatusListAndAgentId(statusList, agentId);
        List<String> codeNosToDelete = linksToDelete.stream()
                .map(LinkTask::getCodeNo)
                .collect(Collectors.toList());
        
        log.info("即将删除的链接: {}", codeNosToDelete);
        
        // 执行批量删除
        int deleteCount = linkTaskMapper.batchDeleteByStatusListAndAgentId(statusList, agentId);
        log.info("批量删除执行结果: 预期删除数量={}, 实际删除数量={}", totalCount, deleteCount);
        
        // 构建响应
        List<String> successCodeNos = new ArrayList<>();
        List<String> failedCodeNos = new ArrayList<>(); 
        List<String> failedReasons = new ArrayList<>();
        
        if (deleteCount == totalCount) {
            // 全部删除成功
            successCodeNos.addAll(codeNosToDelete);
        } else if (deleteCount > 0) {
            // 部分删除成功（这种情况比较少见，可能是并发操作导致）
            successCodeNos.addAll(codeNosToDelete.subList(0, deleteCount));
            failedCodeNos.addAll(codeNosToDelete.subList(deleteCount, codeNosToDelete.size()));
            for (int i = deleteCount; i < codeNosToDelete.size(); i++) {
                failedReasons.add("删除操作部分失败");
            }
        } else {
            // 全部删除失败
            failedCodeNos.addAll(codeNosToDelete);
            for (int i = 0; i < codeNosToDelete.size(); i++) {
                failedReasons.add("删除操作失败");
            }
        }
        
        BatchDeleteResponse response = new BatchDeleteResponse(
                successCodeNos.size(),
                failedCodeNos.size(),
                codeNosToDelete.size(),
                successCodeNos,
                failedCodeNos,
                failedReasons
        );
        
        log.info("按状态批量删除完成: 总数={}, 成功={}, 失败={}", 
                response.getTotalCount(), response.getSuccessCount(), response.getFailedCount());
        
        return response;
    }).subscribeOn(Schedulers.boundedElastic());
}


/**
 * 批量删除链接（确保用户只能删除自己的链接）
 */
@Transactional(rollbackFor = Exception.class)
public Mono<BatchDeleteResponse> batchDeleteLinks(List<String> codeNos, Long agentId) {
    return Mono.fromCallable(() -> {
        log.info("开始批量删除链接: codeNos={}, agentId={}, count={}", codeNos, agentId, codeNos.size());

        if (codeNos == null || codeNos.isEmpty()) {
            throw new IllegalArgumentException("要删除的链接编号列表不能为空");
        }

        if (codeNos.size() > 100) {
            throw new IllegalArgumentException("单次最多只能删除100个链接");
        }

        // 查询用户拥有的链接
        List<LinkTask> userLinks = linkTaskMapper.findByCodeNosAndAgentId(codeNos, agentId);
        List<String> userCodeNos = userLinks.stream()
                .map(LinkTask::getCodeNo)
                .collect(Collectors.toList());

        log.info("用户拥有的链接数量: {}, 链接编号: {}", userCodeNos.size(), userCodeNos);

        // 准备响应数据
        List<String> successCodeNos = new ArrayList<>();
        List<String> failedCodeNos = new ArrayList<>();
        List<String> failedReasons = new ArrayList<>();

        // 检查每个链接的权限
        for (String codeNo : codeNos) {
            if (!userCodeNos.contains(codeNo)) {
                failedCodeNos.add(codeNo);
                failedReasons.add("链接不存在或无权删除");
                log.warn("用户无权删除链接: codeNo={}, agentId={}", codeNo, agentId);
            }
        }

        // 执行批量删除（只删除用户拥有的链接）
        int deleteCount = 0;
        if (!userCodeNos.isEmpty()) {
            deleteCount = linkTaskMapper.batchDeleteByCodeNosAndAgentId(userCodeNos, agentId);
            log.info("批量删除执行结果: 预期删除数量={}, 实际删除数量={}", userCodeNos.size(), deleteCount);

            // 更新成功列表
            if (deleteCount > 0) {
                // 由于批量删除可能部分成功，我们按实际删除数量来处理
                successCodeNos.addAll(userCodeNos);
                log.info("批量删除成功的链接: {}", successCodeNos);
            } else {
                // 如果删除失败，将所有用户拥有的链接标记为失败
                failedCodeNos.addAll(userCodeNos);
                for (int i = 0; i < userCodeNos.size(); i++) {
                    failedReasons.add("删除操作失败");
                }
                log.warn("批量删除失败: agentId={}, codeNos={}", agentId, userCodeNos);
            }
        }

        // 构建响应
        BatchDeleteResponse response = new BatchDeleteResponse(
                successCodeNos.size(),
                failedCodeNos.size(),
                codeNos.size(),
                successCodeNos,
                failedCodeNos,
                failedReasons
        );

        log.info("批量删除完成: 总数={}, 成功={}, 失败={}",
                response.getTotalCount(), response.getSuccessCount(), response.getFailedCount());

        return response;
    }).subscribeOn(Schedulers.boundedElastic());
}

/**
 * 用户端获取链接状态（支持 linkId 或 codeNo 参数，带自动刷新逻辑）
 */
public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo) {
    return Mono.fromCallable(() -> {
        log.info("=== 开始处理用户端链接状态查询 ===");
        log.info("linkId: {}, codeNo: {}", linkId, codeNo);

        try {
            // 1. 查询链接任务
            LinkTask linkTask = null;
            if (linkId != null) {
                linkTask = linkTaskMapper.findById(linkId);
                log.info("通过 linkId 查询链接任务: id={}", linkId);
            } else if (codeNo != null && !codeNo.trim().isEmpty()) {
                linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
                log.info("通过 codeNo 查询链接任务: codeNo={}", codeNo);
            }

            if (linkTask == null) {
                log.error("链接任务不存在 linkId={}, codeNo={}", linkId, codeNo);
                throw new IllegalArgumentException("链接不存在");
            }

            log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
                    linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());

            return linkTask;
        } catch (Exception e) {
            log.error("=== 用户端链接状态查询失败 ===");
            log.error("错误详情: {}", e.getMessage(), e);
            throw e;
        }
    })
    .subscribeOn(Schedulers.boundedElastic())
    .flatMap(linkTask -> {
        // 如果是 USING 状态，先执行一次检测
        if ("USING".equals(linkTask.getStatus())) {
            log.info("链接状态为 USING，立即执行一次登录状态检测");
            return checkAndHandleLoginStatus(linkTask)
                    .doOnSuccess(pollResult -> {
                        log.info("登录状态检测完成: success={}, status={}",
                                pollResult.isSuccess(), pollResult.getStatus());
                    })
                    .doOnError(error -> {
                        log.warn("登录状态检测失败: {}", error.getMessage());
                    })
                    .onErrorResume(error -> {
                        // 检测失败不影响后续流程，继续返回当前状态
                        log.warn("检测失败，继续返回当前状态");
                        return Mono.empty();
                    })
                    .then(Mono.fromCallable(() -> doGetUserLinkStatus(linkId, codeNo)));
        } else {
            // 非 USING 状态，直接返回状态
            return Mono.fromCallable(() -> doGetUserLinkStatus(linkId, codeNo));
        }
    })
    .subscribeOn(Schedulers.boundedElastic());
}

private UserLinkStatusResponse doGetUserLinkStatus(Long linkId, String codeNo) {
    log.info("=== 开始处理用户端链接状态查询 ===");
    log.info("linkId: {}, codeNo: {}", linkId, codeNo);

    try {
        // 1. 查询链接任务
        LinkTask linkTask = null;
        if (linkId != null) {
            linkTask = linkTaskMapper.findById(linkId);
            log.info("通过 linkId 查询链接任务: id={}", linkId);
        } else if (codeNo != null && !codeNo.trim().isEmpty()) {
            linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
            log.info("通过 codeNo 查询链接任务: codeNo={}", codeNo);
        }

        if (linkTask == null) {
            log.error("链接任务不存在 linkId={}, codeNo={}", linkId, codeNo);
            throw new IllegalArgumentException("链接不存在");
        }

        log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
                linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());

        // 2. 检查链接任务是否过期
        if (linkTask.getExpireAt() != null && linkTask.getExpireAt().isBefore(LocalDateTime.now())) {
            log.warn("链接任务已过期: expireAt={}", linkTask.getExpireAt());
            LocalDateTime now = LocalDateTime.now();
            linkTask.setStatus("EXPIRED");
            linkTask.setReason("状态：已过期");
            linkTask.setExpireAt(now);  // 设置过期时间戳
            linkTask.setUpdatedAt(now);
            // Note: The following line was incomplete/incorrect in the original. 
            // It's commented out as 'region' and 'selectedDeviceId' are not defined in this context.
            // int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
            // if (affected == 0) {
            //     log.warn("原子占用失败：设备已被其他任务占用 device={}，codeNo={}", selectedDeviceId, linkTask.getCodeNo());
            //     throw new RuntimeException("设备已被占用，请重试");
            // }
            if (linkTask.getFirstRegionSelectAt() == null) {
                linkTask.setFirstRegionSelectAt(LocalDateTime.now());
                try {
                    linkTaskMapper.update(linkTask);
                } catch (Exception ignore) {}
            }
        }

        // 3. 检查 USING 状态的 10 分钟过期逻辑
        if ("USING".equals(linkTask.getStatus())) {
            // 检查是否超过 10 分钟未登录
            if (linkTask.getQrCreatedAt() != null &&
                linkTask.getQrCreatedAt().isBefore(LocalDateTime.now().minusMinutes(10))) {
                log.warn("选择设备已超过 10 分钟未登录，链接过期: qrCreatedAt={}", linkTask.getQrCreatedAt());

                // 将设备加入冷却队列 - 因为 10 分钟内没有成功登录
                if (linkTask.getMachineId() != null) {
                    machineCooldownService.addMachineToCooldown(linkTask.getMachineId(), "10分钟内未登录成功");
                    log.info("设备{}因 10 分钟内未登录成功被加入冷却队列", linkTask.getMachineId());
                }

                LocalDateTime now = LocalDateTime.now();
                linkTask.setStatus("EXPIRED");
                linkTask.setReason("状态：已过期（10分钟未登录）");
                linkTask.setExpireAt(now);  // 设置过期时间戳
                linkTask.setUpdatedAt(now);
                // Note: The following line was incomplete/incorrect in the original.
                // It's commented out as 'region' and 'selectedDeviceId' are not defined in this context.
                // int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
                // if (affected == 0) {
                //     log.warn("原子占用失败：设备已被其他任务占用 device={}，codeNo={}", selectedDeviceId, linkTask.getCodeNo());
                //     throw new RuntimeException("设备已被占用，请重试");
                // }
                if (linkTask.getFirstRegionSelectAt() == null) {
                    linkTask.setFirstRegionSelectAt(LocalDateTime.now());
                    try {
                        linkTaskMapper.update(linkTask);
                    } catch (Exception ignore) {}
                }

                UserLinkStatusResponse response = new UserLinkStatusResponse();
                response.setStatus("EXPIRED");
                return response;
            }

            // 如果未超过 10 分钟，执行自动刷新
            log.info("链接状态是 USING，执行自动刷新");
//                performAutoRefresh(linkTask);
        } else if ("LOGGED_IN".equals(linkTask.getStatus()) || "COMPLETED".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
            // 已上号、已完成或已退单状态，不需要刷新
            log.info("链接状态为 {}，不需要刷新", linkTask.getStatus());
        }

        // 4. 构建响应
        UserLinkStatusResponse response = buildUserStatusResponse(linkTask);
        log.info("=== 用户端链接状态查询完成 ===");
        log.info("返回状态: {}", response.getStatus());
        return response;

    } catch (Exception e) {
        log.error("=== 用户端链接状态查询失败 ===");
        log.error("错误详情: {}", e.getMessage(), e);
        throw e;
    }
}




        /**
         * 构建用户端状态响应
         */
        private UserLinkStatusResponse buildUserStatusResponse(LinkTask linkTask) {
            UserLinkStatusResponse response = new UserLinkStatusResponse();
            
            // 如果状态是USING，返回NEW给用户端
            String statusToReturn = "USING".equals(linkTask.getStatus()) ? "NEW" : linkTask.getStatus();
            response.setStatus(statusToReturn);
            response.setMachineId(linkTask.getMachineId());
            return response;
        }



        /**
         * 选区操作
         */
        public Mono<SelectRegionResponse> selectRegion(String code, String region) {
            return Mono.fromCallable(() -> doSelectRegion(code, region))
                    .subscribeOn(Schedulers.boundedElastic());
        }

        private SelectRegionResponse doSelectRegion(String code, String region) throws InterruptedException {
            log.info("=== 开始选区操作 ===");
            log.info("请求参数: code={}, region={}", code, region);

            try {
                // 1. 验证参数
                log.info("步骤1: 开始参数验证");
                if (code == null || code.trim().isEmpty()) {
                    log.error("参数验证失败: code不能为空");
                    throw new IllegalArgumentException("code不能为空");
                }
                if (region == null || (!region.equals("Q") && !region.equals("V"))) {
                    log.error("参数验证失败: region只能是Q或V, 当前值{}", region);
                    throw new IllegalArgumentException("region只能是Q或V");
                }
                log.info("参数验证通过: code={}, region={}", code.trim(), region);

                // 2. 查询链接任务
                log.info("步骤2: 开始查询链接任务");
                LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
                if (linkTask == null) {
                    log.error("链接任务查询失败: 未找到对应的链接任务, code={}", code);
                    throw new IllegalArgumentException("链接不存在");
                }

                log.info("链接任务查询成功: id={}, codeNo={}, status={}, batchId={}, machineId={}, needRefresh={}, firstRegionSelectAt={}", 
                        linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), 
                        linkTask.getBatchId(), linkTask.getMachineId(), linkTask.getNeedRefresh(), linkTask.getFirstRegionSelectAt());

                // 查询批次信息获取times参数
                log.info("步骤3: 开始查询批次信息");
                LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
                if (linkBatch == null) {
                    log.error("批次信息查询失败: 未找到对应的批次信息, batchId={}", linkTask.getBatchId());
                    throw new IllegalStateException("批次信息不存在");
                }
                log.info("批次信息查询成功: batchId={}, times={}, quantity={}", linkBatch.getId(), linkBatch.getTimes(), linkBatch.getQuantity());

                // 4. 检查链接状态，只有NEW或USING状态才能选区
                log.info("步骤4: 开始检查链接状态");
                if (!"NEW".equals(linkTask.getStatus()) && !"USING".equals(linkTask.getStatus())) {
                    log.error("链接状态检查失败 当前状态{}, 只允许NEW或USING状态进行选区操作", linkTask.getStatus());
                    throw new IllegalArgumentException("链接状态不正确，只有新建或使用中状态的链接才能选区");
                }
                log.info("链接状态检查通过: 当前状态{}, 允许进行选区操作", linkTask.getStatus());


                // 5. 如果need_refresh=true，检查是否已等待10秒
    //            if (Boolean.TRUE.equals(linkTask.getNeedRefresh()) && linkTask.getRefreshTime() != null) {
    //                LocalDateTime now = LocalDateTime.now();
    //                long secondsSinceRefresh = ChronoUnit.SECONDS.between(linkTask.getRefreshTime(), now);
    //                if (secondsSinceRefresh < 10) {
    //                    long waitTime = 10 - secondsSinceRefresh;
    //                    log.error("刷新后需要等待，剩余等待时间: {}秒, waitTime);
    //                    SelectRegionResponse response = new SelectRegionResponse(false, "刷新后需要等待" + waitTime + "秒才能选区");
    //                    return response;
    //                }
    //            }
                // 5. 设备选择逻辑
                log.info("步骤5: 开始设备选择逻辑");
                String selectedDeviceId;
                
                if(linkTask.getFirstRegionSelectAt() == null){
                    if(!linkTask.getStatus().equals("USING")){
                        LocalDateTime now = LocalDateTime.now();
                        linkTask.setStatus("USING");
                        linkTask.setReason("首次选区");
                        linkTask.setUpdatedAt(now);
                        linkTaskMapper.updateById(linkTask);
                    }
                    log.info("首次选区: 开始检查和分配空闲设备");
                    // 获取当前设备快照（直接从脚本端获取最新状态）
                    DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block();
                    if (deviceStatus == null) {
                        log.error("获取设备快照失败，无法进行选区");
                        throw new RuntimeException("暂时无法获取设备状态，请稍后再试");
                    }

                    // 检查是否有空闲设备
                    if (deviceStatus.getAvailableCount() == 0) {
                        log.error("设备分配失败: 当前没有空闲设备可用, 总设备数={}, 空闲设备数{}",
                                deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount());
                        throw new RuntimeException("当前没有空闲设备，请稍后再试");
                    }

                    log.info("空闲设备检查完成 总设备数={}, 空闲设备数{}, 空闲设备列表={}",
                            deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());

                    // 由 DeviceStats 使用快照完成分配与内存分组更新
                    selectedDeviceId = deviceStats.allocateFromSnapshot(deviceStatus, linkTask.getId(), "首次选区");
                    
                    if (selectedDeviceId == null) {
                        int candidateSize = deviceStatus.getAvailableDevices() != null ? deviceStatus.getAvailableDevices().size() : 0;
                        log.error("设备分配失败: 所有空闲设备都在冷却期内或被占用 总空闲设备数={}", candidateSize);
                        throw new RuntimeException("所有设备都在冷却期内或被占用，请稍后再试");
                    }
                    
                    log.info("首次选区设备分配成功: 选中设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
                }else{
                    if(!linkTask.getStatus().equals("USING")){
                        LocalDateTime now = LocalDateTime.now();
                        linkTask.setStatus("USING");
                        linkTask.setReason("重复选区");
                        linkTask.setUpdatedAt(now);
                        linkTaskMapper.updateById(linkTask);
                    }
                    log.info("重复选区: 检查首次选区时有效性并复用设备");
                    // 检查首次选区是否已过期
                    LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt();
                    long expireSeconds = systemConfigService.getFirstRegionExpireSeconds();
                    LocalDateTime expireTime = firstSelectTime.plusSeconds(expireSeconds);
                    LocalDateTime now = LocalDateTime.now();
                    
                    log.info("时效性检查: 首次选区时间={}, 过期时间={}, 当前时间={}, 过期秒数={}",
                            firstSelectTime, expireTime, now, expireSeconds);
                    
                    if (now.isAfter(expireTime)) {
                        log.error("链接过期检查失败: 首次选区已过期 firstSelectTime={}, expireTime={}, now={}", 
                                firstSelectTime, expireTime, now);
                        
                        // 将链接状态设置为过期
                        linkTask.setStatus("EXPIRED");
                        linkTask.setReason("首次选区已过期");
                        linkTask.setExpireAt(now);  // 设置过期时间戳
                        linkTask.setUpdatedAt(now);
                        linkTaskMapper.updateById(linkTask);
                        log.info("链接状态已更新为EXPIRED: linkTaskId={}, 更新时间={}", linkTask.getId(), now);
                        
                        throw new RuntimeException("链接已过期，请重新获取");
                    }
                    
                    selectedDeviceId = linkTask.getMachineId();
                    
                    // 检查之前分配的设备是否被其他链接任务占用
                    List<LinkTask> occupiedTasks = linkTaskMapper.findByMachineIdAndStatus(selectedDeviceId, "USING");
                    occupiedTasks.addAll(linkTaskMapper.findByMachineIdAndStatus(selectedDeviceId, "LOGGED_IN"));
                    
                    // 过滤掉当前链接任务本身
                    occupiedTasks.removeIf(task -> task.getId().equals(linkTask.getId()));
                    
                    if (!occupiedTasks.isEmpty()) {
                        log.error("重复选区设备冲突: 之前分配的设备{}被其他链接任务占用 占用任务数{}", selectedDeviceId, occupiedTasks.size());
                        for (LinkTask occupiedTask : occupiedTasks) {
                            log.error("占用设备{}的链接 codeNo={}, status={}, id={}", selectedDeviceId, occupiedTask.getCodeNo(), occupiedTask.getStatus(), occupiedTask.getId());
                        }
                        throw new RuntimeException("设备被其他链接占用，请重新获取链接");
                    }
                    
                    log.info("重复选区设备复用成功: 使用之前分配的设备{}, 首次选区时间={}", selectedDeviceId, firstSelectTime);
                    log.info("执行设备刷新操作: 设备={}", selectedDeviceId);
                    scriptClient.refresh(selectedDeviceId).block();
                    log.info("设备刷新完成: 设备={}, 继续使用之前分配的设备", selectedDeviceId);
                    // 休眠5秒，确保设备状态稳定
                    Thread.sleep(5000);
                    log.info("等待5秒完成，继续使用之前分配的设备 设备={}", selectedDeviceId);
                }
                
                log.info("设备选择完成: 最终选中设备={}", selectedDeviceId);
                        
                // 6. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
                log.info("步骤6: 开始检查设备状态和历史任务");
                try {
                    log.info("检查设备历史任务 设备={}, 触发原因=选区请求", selectedDeviceId);
                    deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求");
                    log.info("设备历史任务检查完成 设备={}", selectedDeviceId);
                } catch (Exception e) {
                    log.warn("设备历史任务检查异常 设备={}, 错误={}, 继续选区流程", selectedDeviceId, e.getMessage());
                    // 不影响选区流程，只记录警告日志
                }
                
                // 7. 调用保存总次数接口
    //            log.info("步骤7: 开始保存总次数到脚本端");
    //            try {
    //                log.info("保存总次数 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
    //                scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
    //                log.info("总次数保存成功 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
    //            } catch (Exception e) {
    //                log.warn("总次数保存失败 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
    //                // 不影响后续流程，只记录警告日志
    //            }

                // 8. 调用脚本端选区，使用选中的设备
                log.info("步骤8: 开始调用脚本端选区");
                log.info("选区请求参数: 设备={}, 区域={}", selectedDeviceId, region);
                
                String selectResult;
                try {
                    selectResult = scriptClient.selectRegion(selectedDeviceId, region).block();
                    log.info("脚本端选区调用成功: 设备={}, 区域={}, 返回结果={}", selectedDeviceId, region, selectResult);
                } catch (Exception e) {
                    log.error("脚本端选区调用失败: 设备={}, 区域={}, 错误={}", selectedDeviceId, region, e.getMessage());
                    
                    // 释放设备分配（仅在首次选区时）
                    if (linkTask.getFirstRegionSelectAt() == null) {
                        log.info("脚本调用失败，释放设备分配: 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
                        deviceAllocationService.releaseDeviceAllocation(selectedDeviceId, linkTask.getId());
                    }
                    
                    throw new RuntimeException("选区操作失败，请重试: " + e.getMessage());
                }

                // 11. 等待脚本端生成二维码（这里可以添加轮询逻辑）
    //            log.info("等待脚本端生成二维码，等待3秒..");
    //            Thread.sleep(3000);

                // 9. 原子占用设备并更新任务状态
                log.info("步骤9: 原子占用设备并更新任务状态");
                LocalDateTime now = LocalDateTime.now();
                LocalDateTime qrExpireAt = now.plusSeconds(60); // 60秒后过期
                
                // 记录更新前的状态
                log.info("数据库更新前状态 id={}, status={}, region={}, machineId={}, firstRegionSelectAt={}", 
                        linkTask.getId(), linkTask.getStatus(), linkTask.getRegion(), linkTask.getMachineId(), linkTask.getFirstRegionSelectAt());
                
                String prev = linkTask.getStatus();
                int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
                if (affected == 0) {
                    log.warn("原子占用失败：设备已被其他任务占用 device={}，codeNo={}", selectedDeviceId, linkTask.getCodeNo());
                    throw new RuntimeException("设备已被占用，请重试");
                }
                if (linkTask.getFirstRegionSelectAt() == null) {
                    linkTask.setFirstRegionSelectAt(now);
                    linkTask.setStatus("USING");
                    linkTask.setUpdatedAt(LocalDateTime.now());
                    try { linkTaskMapper.update(linkTask); } catch (Exception ignore) {}
                }
                // Audit and history for USING transition
                try {
                    com.gameplatform.server.util.AuditLogger.info("StatusTransition: codeNo={}, device={}, {}->USING, source=REGION_SELECT", linkTask.getCodeNo(), selectedDeviceId, prev);
                    if (statusHistoryMapper != null) {
                        statusHistoryMapper.insert(new com.gameplatform.server.model.entity.history.LinkTaskStatusHistory(
                                linkTask.getId(), linkTask.getCodeNo(), selectedDeviceId, prev, "USING", "REGION_SELECT", "regionSelected", null, null
                        ));
                    }
                } catch (Exception ignore) {}
                log.info("数据库更新成功 id={}, status=USING, region={}, machineId={}, qrCreatedAt={}, qrExpireAt={}, firstRegionSelectAt={}", 
                        linkTask.getId(), region, selectedDeviceId, now, qrExpireAt, linkTask.getFirstRegionSelectAt());

                // 9.5. 验证设备分配是否存在冲突
                log.info("步骤9.5: 验证设备分配结果");
                if (!deviceAllocationService.validateDeviceAllocation(selectedDeviceId, linkTask.getId())) {
                    log.error("设备分配冲突检测失败，准备回滚操作 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
                    
                    // 回滚操作：释放设备分配和还原任务状态
                    try {
                        deviceAllocationService.releaseDeviceAllocation(selectedDeviceId, linkTask.getId());
                        
                        // 将任务状态回滚到NEW
                        linkTaskMapper.updateStatus(linkTask.getId(), "NEW");
                        log.info("设备分配冲突回滚完成 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
                    } catch (Exception rollbackEx) {
                        log.error("设备分配回滚失败 设备={}, 任务ID={}, 错误: {}", 
                                selectedDeviceId, linkTask.getId(), rollbackEx.getMessage());
                    }
                    
                    throw new RuntimeException("设备分配冲突，请重试");
                }
                log.info("设备分配验证通过 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());

                // 10. 构建响应
                log.info("步骤10: 开始构建响应数据");
                SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
                String qrCodeUrl = scriptClient.getProxyQrCodeUrl(code);
                response.setQrCodeUrl(qrCodeUrl);
                response.setQrCreatedAt(now);
                response.setQrExpireAt(qrExpireAt);
                response.setStatus("USING");
                // 从系统配置读取二维码延迟显示时间，默认为10秒
                Integer qrDelaySeconds = systemConfigService.getConfigValueAsInt("link.qr_display_delay", 10);
                response.setQrDelaySeconds(qrDelaySeconds);
                response.setMecmachineId(selectedDeviceId); // 便于调试和维护                
                log.info("响应构建完成: success=true, status=USING, qrCodeUrl={}, qrDelaySeconds={}, machineId={}", 
                        qrCodeUrl, qrDelaySeconds, selectedDeviceId);
                log.info("=== 选区操作完成 ===");

                return response;

            } catch (Exception e) {
                log.error("=== 选区操作失败 ===");
                log.error("错误详情: {}", e.getMessage(), e);
                throw e;
            }
        }

        /**
         * 轮询上号接口
         */
        public Mono<PollLoginResponse> pollLogin(String code) {
            log.info("=== 开始轮询上号 ===");
            log.info("code: {}", code);

            return validatePollLoginRequest(code)
                    .flatMap(result -> {
                        if (result instanceof PollLoginResponse) {
                            // 如果验证返回响应对象，直接返回
                            return Mono.just((PollLoginResponse) result);
                        }
                        // 否则继续处理登录状态检查
                        return checkAndHandleLoginStatus((LinkTask) result);
                    })
                    .doOnSuccess(response -> {
                        log.info("轮询上号完成: success={}, status={}", response.isSuccess(), response.getStatus());
                    })
                    .doOnError(error -> {
                        log.error("轮询上号失败: code={}, error={}", code, error.getMessage(), error);
                    });
        }

    /**
     * 验证轮询上号请求参数和链接状态
     */
    private Mono<Object> validatePollLoginRequest(String code) {
        return Mono.fromCallable(() -> {
            // 验证code参数
            if (code == null || code.trim().isEmpty()) {
                throw new IllegalArgumentException("参数错误：code不能为空");
            }

            // 获取链接任务
            LinkTask linkTask = linkTaskMapper.findByCodeNo(code);
            if (linkTask == null) {
                throw new IllegalArgumentException("链接不存在");
            }

            log.info("找到链接任务: id={}, status={}, codeNo={}", 
                    linkTask.getId(), linkTask.getStatus(), linkTask.getCodeNo());

            // 检查链接状态，只有USING状态才能轮询
            if (!"USING".equals(linkTask.getStatus())) {
                log.warn("链接状态不是USING，当前状态 {}", linkTask.getStatus());
                return new PollLoginResponse(false, linkTask.getStatus());
            }

            return linkTask;
        }).subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 获取任务对应的设备ID
     */
    private String getDeviceIdForTask(LinkTask linkTask) {
        if (linkTask.getMachineId() != null) {
            // 如果已保存了设备编号，直接使用
            return linkTask.getMachineId();
        }
        return null;
    }

    /**
     * 检查登录状态并处理结果
     */
    private Mono<PollLoginResponse> checkAndHandleLoginStatus(LinkTask linkTask) {
        // 获取真实设备编号
        String realDeviceId = getDeviceIdForTask(linkTask);
        
        if (realDeviceId == null) {
            log.error("无法获取设备编号: codeNo={}, machineId={}", linkTask.getCodeNo(), linkTask.getMachineId());
            return Mono.error(new RuntimeException("设备信息异常，无法检查上号状态"));
        }

        // 调用脚本端检查上号状态
        log.info("调用脚本端检查上号状态 代理code={}, 真实设备={}", linkTask.getCodeNo(), realDeviceId);
        
        return scriptClient.checkLoginStatus(realDeviceId)
                .map(loginResult -> processLoginResult(linkTask, realDeviceId, loginResult))
                .onErrorResume(error -> {
                    log.warn("调用脚本端检查上号状态失败 codeNo={}, error={}", 
                            linkTask.getCodeNo(), error.getMessage());
                    // 脚本端出错时，返回当前状态，不影响轮询
                    return Mono.just(new PollLoginResponse(false, "USING"));
                });
    }

    /**
     * 处理登录检查结果
     */
    private PollLoginResponse processLoginResult(LinkTask linkTask, String deviceId, String loginResult) {
        log.info("脚本端返回结果 {}", loginResult);
        
        // 检查是否已上号
        if ("已上号".equals(loginResult) || "已登录".equals(loginResult)) {
            try {
                LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
                log.info("=============================================");
                scriptClient.saveTotalTimes(deviceId,linkBatch.getTimes()).block();
                // saveTotalTimes方法已经包含了详细的日志记录
                log.info("codeNo:{},deviceId:{}",linkTask.getCodeNo(),deviceId);
                log.info("=============================================");

            } catch (Exception e) {
                log.warn("保存总次数接口调用失败 {}", e.getMessage());
                // 不影响后续流程，只记录警告日志
            }
            return handleSuccessfulLogin(linkTask, deviceId);
        } else {
            // 未上号，返回当前状态
            log.debug("尚未上号，返回当前状态");
            return new PollLoginResponse(false, "USING");
        }
    }

    /**
     * 处理成功登录的情况
     */
    private PollLoginResponse handleSuccessfulLogin(LinkTask linkTask, String deviceId) {
        log.info("检测到已上号，更新状态为LOGGED_IN");
        
        // 更新数据库状态（设置登录时间）
        LocalDateTime now = LocalDateTime.now();
        if (linkTask.getLoginAt() == null) {
            linkTask.setLoginAt(now);
        }
        linkTask.setStatus("LOGGED_IN");
        linkTask.setReason("状态：已登录");
        linkTask.setUpdatedAt(now);
        linkTaskMapper.updateById(linkTask);
        
        // Record login buffer and audit
        try {
            if (completionDetectionService != null) {
                completionDetectionService.recordDeviceLogin(deviceId);
            }
            com.gameplatform.server.util.AuditLogger.info("DeviceLogin: codeNo={}, device={}, status=LOGGED_IN", linkTask.getCodeNo(), deviceId);
            if (statusHistoryMapper != null) {
                statusHistoryMapper.insert(new com.gameplatform.server.model.entity.history.LinkTaskStatusHistory(
                        linkTask.getId(), linkTask.getCodeNo(), deviceId, "USING", "LOGGED_IN", "POLL_LOGIN", "loginDetected", 0, null
                ));
            }
        } catch (Exception e) {
            log.warn("记录设备登录缓冲/审计失败", e);
        }
        
        // 记录设备登录时间（用于完成检测的缓冲期判断）
        try {
            // 这里需要注入GameCompletionDetectionService，为了兼容性，暂时记录日志
            log.info("设备{}登录成功，开始缓冲期保护", deviceId);
        } catch (Exception e) {
            log.warn("记录设备登录时间失败", e);
        }
        
        log.info("状态更新完成 codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
        
        // 构建成功响应
        PollLoginResponse response = new PollLoginResponse(true, "LOGGED_IN", "SECOND", 
                new PollLoginResponse.AssetsInfo(
                        scriptClient.getAssetsBaseUrl(deviceId)
                ));
        
        log.info("=== 轮询上号成功 ===");
        return response;
    }

    /**
     * 根据链接编号获取设备ID
     * @param code 链接编号
     * @return 设备ID，如果链接不存在或未关联设备则返回null
     */
    public String getMechainIdByCode(String code) {
        if (code == null || code.trim().isEmpty()) {
            log.warn("获取设备ID失败: code参数为空");
            return null;
        }

        log.debug("根据code获取设备ID: code={}", code);

        try {
            // 查询链接任务
            LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
            if (linkTask == null) {
                log.warn("链接任务不存在 code={}", code);
                return null;
            }

            String machineId = linkTask.getMachineId();
            log.debug("查询到设备ID: code={}, machineId={}", code, machineId);
            
            return machineId;
            
        } catch (Exception e) {
            log.error("根据code获取设备ID时发生异常 code={}, error={}", code, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取目标点数
     * @param codeNo 链接编号
     * @return 目标点数响应
     */
    public Mono<TargetScoreResponse> getTargetScore(String codeNo) {
        return Mono.fromCallable(() -> doGetTargetScore(codeNo))
                .subscribeOn(Schedulers.boundedElastic());
    }

    private TargetScoreResponse doGetTargetScore(String codeNo) {
        log.info("=== 开始获取目标点数 ===");
        log.info("链接编号: {}", codeNo);

        try {
            // 1. 验证参数
            if (codeNo == null || codeNo.trim().isEmpty()) {
                log.error("参数验证失败: codeNo不能为空");
                return TargetScoreResponse.error(codeNo, null, "链接编号不能为空");
            }

            // 2. 查询链接任务，获取machineId
            LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
            if (linkTask == null) {
                log.error("链接任务不存在 codeNo={}", codeNo);
                return TargetScoreResponse.error(codeNo, null, "链接不存在");
            }

            String machineId = linkTask.getMachineId();
            if (machineId == null || machineId.trim().isEmpty()) {
                log.error("链接未关联设备 codeNo={}", codeNo);
                return TargetScoreResponse.error(codeNo, null, "链接未关联设备");
            }

            log.info("查询到设备ID: codeNo={}, machineId={}", codeNo, machineId);

            // 3. 调用脚本端获取目标分数
            log.info("调用脚本端获取目标分数 machineId={}", machineId);
            String targetScoreStr = scriptClient.getTargetScore(machineId).block();
            log.info("脚本端返回结果 machineId={}, result={}", machineId, targetScoreStr);

            // 4. 解析返回结果
            if (targetScoreStr == null || targetScoreStr.trim().isEmpty()) {
                log.warn("脚本端返回空结果: machineId={}", machineId);
                return TargetScoreResponse.error(codeNo, machineId, "网络拥堵，请稍后再试");
            }

            String trimmedResult = targetScoreStr.trim();
            
            // 检查是否为数字
            try {
                Integer targetScore = Integer.parseInt(trimmedResult);
                log.info("解析到数字目标分数 {}", targetScore);

                // 5. 保存到数据库
                linkTask.setCompletedPoints(targetScore);
                linkTask.setUpdatedAt(LocalDateTime.now());
                int updateResult = linkTaskMapper.updateById(linkTask); // Fixed the assignment here
                
                if (updateResult > 0) {
                    log.info("目标分数保存成功: codeNo={}, machineId={}, targetScore={}", 
                            codeNo, machineId, targetScore);
                    return TargetScoreResponse.success(codeNo, machineId, targetScore);
                } else {
                    log.error("目标分数保存失败: codeNo={}, machineId={}, targetScore={}", 
                            codeNo, machineId, targetScore);
                    return TargetScoreResponse.error(codeNo, machineId, "保存目标分数失败");
                }
                
            } catch (NumberFormatException e) {
                // 不是数字，检查特殊状态
                log.info("脚本端返回非数字结果: {}", trimmedResult);
                
                if ("空的".equals(trimmedResult) || "空闲".equals(trimmedResult) || "已运行".equals(trimmedResult)) {
                    log.info("设备状态为: {}", trimmedResult);
                    return TargetScoreResponse.error(codeNo, machineId, "网络拥堵，请稍后再试");
                } else {
                    log.warn("未知的返回结果 {}", trimmedResult);
                    return TargetScoreResponse.error(codeNo, machineId, "网络拥堵，请稍后再试");
                }
            }

        } catch (Exception e) {
            log.error("=== 获取目标点数失败 ===");
            log.error("错误详情: codeNo={}, error={}", codeNo, e.getMessage(), e);
            return TargetScoreResponse.error(codeNo, null, "系统异常，请稍后再试");
        }
    }
}



