package com.gameplatform.server.service.link;

import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.model.entity.agent.LinkTask;
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
import com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper;
import com.gameplatform.server.model.entity.history.LinkTaskStatusHistory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 设备分配服务 - 提供线程安全的设备分配机制
 * 解决并发环境下多个请求争抢同一设备的问题
 */
@Service
public class DeviceAllocationService {
    private static final Logger log = LoggerFactory.getLogger(DeviceAllocationService.class);
    
    private final MemoryMachineCooldownService machineCooldownService;
    private final LinkTaskMapper linkTaskMapper;
    private final LinkTaskStatusHistoryMapper statusHistoryMapper;
    
    public DeviceAllocationService(MemoryMachineCooldownService machineCooldownService, 
                                 LinkTaskMapper linkTaskMapper,
                                 LinkTaskStatusHistoryMapper statusHistoryMapper) {
        this.machineCooldownService = machineCooldownService;
        this.linkTaskMapper = linkTaskMapper;
        this.statusHistoryMapper = statusHistoryMapper;
    }
    
    /**
     * 原子方式分配设备，避免并发竞争
     * @param availableDevices 可用设备列表
     * @param linkTaskId 链接任务ID
     * @param reason 分配原因
     * @return 分配成功的设备ID，如果所有设备都被占用则返回null
     */
    @Transactional
    public String allocateDevice(List<String> availableDevices, Long linkTaskId, String reason) {
        if (availableDevices == null || availableDevices.isEmpty()) {
            log.warn("设备分配失败：没有可用设备");
            return null;
        }
        
        log.info("开始设备分配流程：候选设备数={}, 任务ID={}, 原因={}", 
                availableDevices.size(), linkTaskId, reason);
        
        // 1. 过滤掉被其他任务占用的设备
        List<String> filteredDevices = filterOccupiedDevices(availableDevices);
        if (filteredDevices.isEmpty()) {
            log.warn("设备分配失败：所有候选设备都被其他任务占用");
            return null;
        }
        
        log.info("设备占用检查完成：原候选设备数={}, 过滤后设备数={}, 可用设备={}",
                availableDevices.size(), filteredDevices.size(), filteredDevices);
        
        // 2. 打乱设备列表，实现负载均衡
        List<String> shuffledDevices = new ArrayList<>(filteredDevices);
        Collections.shuffle(shuffledDevices, ThreadLocalRandom.current());
        
        log.info("设备列表已随机化：{}", shuffledDevices);
        
        // 3. 尝试原子分配设备（按随机顺序）
        for (String deviceId : shuffledDevices) {
            if (machineCooldownService.tryAllocateDevice(deviceId, reason, linkTaskId)) {
                log.info("设备分配成功：设备={}, 任务ID={}, 原因={}", deviceId, linkTaskId, reason);
                return deviceId;
            } else {
                log.debug("设备{}分配失败（在冷却期或已被占用），尝试下一个设备", deviceId);
            }
        }
        
        log.warn("设备分配失败：所有候选设备都在冷却期内");
        return null;
    }
    
    /**
     * 过滤掉被其他链接任务占用的设备
     * @param devices 设备列表
     * @return 未被占用的设备列表
     */
    private List<String> filterOccupiedDevices(List<String> devices) {
        List<String> availableDevices = new ArrayList<>();
        
        for (String deviceId : devices) {
            // 检查设备是否被其他链接任务占用（USING、LOGGED_IN状态），并自动清理超时占用
            List<LinkTask> usingTasks = linkTaskMapper.findByMachineIdAndStatus(deviceId, "USING");
            List<LinkTask> loggedInTasks = linkTaskMapper.findByMachineIdAndStatus(deviceId, "LOGGED_IN");

            LocalDateTime now = LocalDateTime.now();
            List<LinkTask> stillOccupied = new ArrayList<>();

            // USING 超过10分钟未选择区 -> EXPIRED，并写入历史
            for (LinkTask task : usingTasks) {
                try {
                    LocalDateTime firstSelectAt = task.getFirstRegionSelectAt();
                    boolean shouldExpire = firstSelectAt != null && firstSelectAt.isBefore(now.minusMinutes(10));
                    if (shouldExpire) {
                        String prev = task.getStatus();
                        task.setStatus("EXPIRED");
                        task.setReason("USING超时10分钟自动过期");
                        task.setUpdatedAt(now);
                        int updated = linkTaskMapper.update(task);
                        if (updated > 0) {
                            try {
                                Integer sinceLoginSeconds = null;
                                if (task.getLoginAt() != null) {
                                    sinceLoginSeconds = (int) Duration.between(task.getLoginAt(), now).getSeconds();
                                }
                                statusHistoryMapper.insert(new LinkTaskStatusHistory(
                                        task.getId(), task.getCodeNo(), deviceId,
                                        prev, "EXPIRED", "DEVICE_ALLOCATION", "usingTimeoutExpire10m",
                                        sinceLoginSeconds, null
                                ));
                            } catch (Exception ignore) {}
                            log.info("自动过期USING链接：codeNo={}, device={}, firstRegionSelectAt={}, 超过10分钟", task.getCodeNo(), deviceId, firstSelectAt);
                        } else {
                            stillOccupied.add(task);
                        }
                    } else {
                        stillOccupied.add(task);
                    }
                } catch (Exception e) {
                    log.warn("检查/更新USING任务超时失败，保持占用：codeNo={}, device={}, err={}", task.getCodeNo(), deviceId, e.getMessage());
                    stillOccupied.add(task);
                }
            }

            // LOGGED_IN 距离上次更新超过30分钟 -> COMPLETED，并写入历史
            for (LinkTask task : loggedInTasks) {
                try {
                    LocalDateTime updatedAt = task.getUpdatedAt();
                    boolean shouldComplete = updatedAt != null && updatedAt.isBefore(now.minusMinutes(30));
                    if (shouldComplete) {
                        String prev = task.getStatus();
                        task.setStatus("COMPLETED");
                        task.setReason("LOGGED_IN超过30分钟自动完成");
                        task.setUpdatedAt(now);
                        int updated = linkTaskMapper.update(task);
                        if (updated > 0) {
                            try {
                                Integer sinceLoginSeconds = null;
                                if (task.getLoginAt() != null) {
                                    sinceLoginSeconds = (int) Duration.between(task.getLoginAt(), now).getSeconds();
                                }
                                statusHistoryMapper.insert(new LinkTaskStatusHistory(
                                        task.getId(), task.getCodeNo(), deviceId,
                                        prev, "COMPLETED", "DEVICE_ALLOCATION", "autoCompleteAfter30m",
                                        sinceLoginSeconds, null
                                ));
                            } catch (Exception ignore) {}
                            log.info("自动完成LOGGED_IN链接：codeNo={}, device={}, updatedAt={}, 超过30分钟", task.getCodeNo(), deviceId, updatedAt);
                        } else {
                            stillOccupied.add(task);
                        }
                    } else {
                        stillOccupied.add(task);
                    }
                } catch (Exception e) {
                    log.warn("检查/更新LOGGED_IN任务超时失败，保持占用：codeNo={}, device={}, err={}", task.getCodeNo(), deviceId, e.getMessage());
                    stillOccupied.add(task);
                }
            }

            if (stillOccupied.isEmpty()) {
                availableDevices.add(deviceId);
            } else {
                log.debug("设备{}被其他链接任务占用，占用任务数={}", deviceId, stillOccupied.size());
                for (LinkTask occupiedTask : stillOccupied) {
                    log.debug("占用设备{}的链接：codeNo={}, status={}, 任务ID={}", 
                            deviceId, occupiedTask.getCodeNo(), occupiedTask.getStatus(), occupiedTask.getId());
                }
            }
        }
        
        return availableDevices;
    }
    
    /**
     * 验证设备分配结果（分配后的双重检查）
     * @param deviceId 设备ID
     * @param linkTaskId 链接任务ID
     * @return true表示分配有效，false表示存在冲突
     */
    public boolean validateDeviceAllocation(String deviceId, Long linkTaskId) {
        if (deviceId == null || linkTaskId == null) {
            return false;
        }
        
        // 检查是否有其他任务也占用了这个设备
        List<LinkTask> conflictTasks = linkTaskMapper.findByMachineIdAndStatus(deviceId, "USING");
        conflictTasks.addAll(linkTaskMapper.findByMachineIdAndStatus(deviceId, "LOGGED_IN"));
        
        // 过滤掉自己的任务
        conflictTasks.removeIf(task -> task.getId().equals(linkTaskId));
        
        if (!conflictTasks.isEmpty()) {
            log.error("设备分配冲突检测：设备{}被多个任务占用，当前任务ID={}，冲突任务数={}", 
                    deviceId, linkTaskId, conflictTasks.size());
            
            for (LinkTask conflictTask : conflictTasks) {
                log.error("冲突任务详情：任务ID={}, codeNo={}, status={}, 设备={}", 
                        conflictTask.getId(), conflictTask.getCodeNo(), 
                        conflictTask.getStatus(), conflictTask.getMachineId());
            }
            return false;
        }
        
        log.debug("设备分配验证通过：设备={}, 任务ID={}", deviceId, linkTaskId);
        return true;
    }
    
    /**
     * 释放设备分配（失败回滚时使用）
     * @param deviceId 设备ID
     * @param linkTaskId 链接任务ID
     */
    public void releaseDeviceAllocation(String deviceId, Long linkTaskId) {
        if (deviceId != null && linkTaskId != null) {
            boolean released = machineCooldownService.releaseDeviceAllocation(deviceId, linkTaskId);
            if (released) {
                log.info("设备分配已释放：设备={}, 任务ID={}", deviceId, linkTaskId);
            } else {
                log.warn("设备分配释放失败：设备={}, 任务ID={}", deviceId, linkTaskId);
            }
        }
    }
}
