package com.gameplatform.server.service.cooldown;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.gameplatform.server.model.dto.cooldown.MachineCooldownView;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于内存的机器冷却服务 - 高性能版本
 * 实现同一台机器在10分钟内不会重复调用的机制
 * 
 * 优化点：
 * 1. 纯内存操作，无数据库IO，性能极高
 * 2. 使用ReentrantLock确保线程安全
 * 3. 自动清理过期记录，避免内存泄漏
 * 4. 支持高并发场景
 */
@Service
public class MemoryMachineCooldownService {
    private static final Logger log = LoggerFactory.getLogger(MemoryMachineCooldownService.class);

    // 冷却时间：10分钟
    private static final int COOLDOWN_MINUTES = 10;
    
    // 内存缓存：machineId -> 冷却信息
    private final ConcurrentMap<String, CooldownInfo> cooldownMap = new ConcurrentHashMap<>();
    
    // 为每个设备提供独立的锁，避免全局锁竞争
    private final ConcurrentMap<String, ReentrantLock> deviceLocks = new ConcurrentHashMap<>();
    
    // 定期清理过期记录的阈值
    private static final int CLEANUP_THRESHOLD = 1000;
    private volatile int lastCleanupSize = 0;

    /**
     * 冷却信息内部类
     */
    private static class CooldownInfo {
        private final LocalDateTime cooldownStartTime;
        private final LocalDateTime cooldownEndTime;
        private final String reason;
        private final Long linkTaskId;
        
        public CooldownInfo(LocalDateTime cooldownStartTime, LocalDateTime cooldownEndTime, 
                          String reason, Long linkTaskId) {
            this.cooldownStartTime = cooldownStartTime;
            this.cooldownEndTime = cooldownEndTime;
            this.reason = reason;
            this.linkTaskId = linkTaskId;
        }
        
        public boolean isActive() {
            return LocalDateTime.now().isBefore(cooldownEndTime);
        }
        
        public long getRemainingMinutes() {
            if (!isActive()) {
                return 0;
            }
            return java.time.Duration.between(LocalDateTime.now(), cooldownEndTime).toMinutes() + 1;
        }
        
        public LocalDateTime getCooldownStartTime() { return cooldownStartTime; }
        public LocalDateTime getCooldownEndTime() { return cooldownEndTime; }
        public String getReason() { return reason; }
        public Long getLinkTaskId() { return linkTaskId; }
    }

    /**
     * 获取设备锁（双重检查锁定模式）
     */
    private ReentrantLock getDeviceLock(String machineId) {
        ReentrantLock lock = deviceLocks.get(machineId);
        if (lock == null) {
            synchronized (deviceLocks) {
                lock = deviceLocks.get(machineId);
                if (lock == null) {
                    lock = new ReentrantLock();
                    deviceLocks.put(machineId, lock);
                }
            }
        }
        return lock;
    }

    /**
     * 检查机器是否在冷却期内
     * @param machineId 机器ID
     * @return true表示在冷却期内，false表示可以操作
     */
    public boolean isMachineInCooldown(String machineId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            return false;
        }

        CooldownInfo cooldownInfo = cooldownMap.get(machineId);
        if (cooldownInfo == null) {
            return false;
        }

        // 检查是否已过期
        if (!cooldownInfo.isActive()) {
            // 异步清理过期记录
            cooldownMap.remove(machineId);
            return false;
        }

        long remainingMinutes = cooldownInfo.getRemainingMinutes();
        log.debug("机器{}在冷却期内，剩余冷却时间：{}分钟，原因：{}", 
                machineId, remainingMinutes, cooldownInfo.getReason());
        
        return true;
    }

    /**
     * 将机器加入冷却队列（线程安全）
     * @param machineId 机器ID
     * @param reason 加入冷却的原因
     * @param linkTaskId 关联的链接任务ID（可选）
     * @return true表示成功加入冷却，false表示设备已在冷却中
     */
    public boolean addMachineToCooldown(String machineId, String reason, Long linkTaskId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            log.warn("尝试添加空的机器ID到冷却队列");
            return false;
        }

        ReentrantLock lock = getDeviceLock(machineId);
        lock.lock();
        try {
            // 双重检查：在锁内再次检查是否已在冷却中
            CooldownInfo existingCooldown = cooldownMap.get(machineId);
            if (existingCooldown != null && existingCooldown.isActive()) {
                long remainingMinutes = existingCooldown.getRemainingMinutes();
                log.info("机器{}已在冷却期内，剩余时间：{}分钟，原因：{}，跳过重复添加", 
                        machineId, remainingMinutes, existingCooldown.getReason());
                return false;
            }

            // 创建新的冷却记录
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime cooldownEndTime = now.plusMinutes(COOLDOWN_MINUTES);
            CooldownInfo newCooldown = new CooldownInfo(now, cooldownEndTime, reason, linkTaskId);
            
            cooldownMap.put(machineId, newCooldown);

            log.info("机器{}已加入冷却队列，原因：{}，冷却时间：{}分钟，冷却结束时间：{}，关联任务：{}",
                    machineId, reason, COOLDOWN_MINUTES, cooldownEndTime, linkTaskId);

            // 定期清理过期记录
            cleanupExpiredCooldownsIfNeeded();
            
            return true;

        } finally {
            lock.unlock();
        }
    }

    /**
     * 重载方法：不指定linkTaskId
     */
    public boolean addMachineToCooldown(String machineId, String reason) {
        return addMachineToCooldown(machineId, reason, null);
    }

    /**
     * 获取机器剩余冷却时间（分钟）
     * @param machineId 机器ID
     * @return 剩余冷却时间，如果不在冷却期则返回0
     */
    public long getRemainingCooldownMinutes(String machineId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            return 0;
        }

        CooldownInfo cooldownInfo = cooldownMap.get(machineId);
        if (cooldownInfo == null) {
            return 0;
        }

        if (!cooldownInfo.isActive()) {
            // 清理过期记录
            cooldownMap.remove(machineId);
            return 0;
        }

        return cooldownInfo.getRemainingMinutes();
    }

    /**
     * 手动移除机器的冷却状态（用于测试或管理员操作）
     * @param machineId 机器ID
     * @return true表示成功移除，false表示设备不在冷却中
     */
    public boolean removeMachineFromCooldown(String machineId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            return false;
        }

        ReentrantLock lock = getDeviceLock(machineId);
        lock.lock();
        try {
            CooldownInfo removed = cooldownMap.remove(machineId);
            if (removed != null) {
                log.info("手动移除机器{}的冷却状态，原冷却原因：{}", machineId, removed.getReason());
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取当前冷却队列大小
     * @return 冷却队列中的设备数量
     */
    public int getCooldownQueueSize() {
        // 先清理过期记录再返回大小
        cleanupExpiredCooldownsIfNeeded();
        return cooldownMap.size();
    }

    /**
     * 列出内存中的所有冷却记录
     * @param activeOnly 是否仅返回仍在冷却中的记录
     */
    public List<MachineCooldownView> listAllCooldowns(boolean activeOnly) {
        // 主动清理一次，避免返回过期项
        cleanupExpiredCooldowns();

        List<MachineCooldownView> result = new ArrayList<>();
        for (Map.Entry<String, CooldownInfo> entry : cooldownMap.entrySet()) {
            String machineId = entry.getKey();
            CooldownInfo info = entry.getValue();
            boolean active = info.isActive();
            if (activeOnly && !active) {
                continue;
            }

            long remaining = active ? info.getRemainingMinutes() : 0L;
            result.add(new MachineCooldownView(
                    machineId,
                    info.getCooldownStartTime(),
                    info.getCooldownEndTime(),
                    info.getReason(),
                    info.getLinkTaskId(),
                    remaining,
                    active
            ));
        }

        // 便于排查：按结束时间升序排列
        result.sort(Comparator.comparing(MachineCooldownView::getCooldownEndTime,
                Comparator.nullsLast(Comparator.naturalOrder())));
        return result;
    }

    /**
     * 清理过期的冷却记录
     */
    public void cleanupExpiredCooldowns() {
        int sizeBefore = cooldownMap.size();
        
        // 使用迭代器安全地移除过期记录
        cooldownMap.entrySet().removeIf(entry -> {
            CooldownInfo cooldownInfo = entry.getValue();
            if (!cooldownInfo.isActive()) {
                log.debug("清理过期冷却记录：机器{}，原因：{}", entry.getKey(), cooldownInfo.getReason());
                return true;
            }
            return false;
        });
        
        int removedCount = sizeBefore - cooldownMap.size();
        if (removedCount > 0) {
            log.info("清理了{}个过期的冷却记录，当前冷却队列大小：{}", removedCount, cooldownMap.size());
        }
    }

    /**
     * 在需要时清理过期记录（避免频繁清理影响性能）
     */
    private void cleanupExpiredCooldownsIfNeeded() {
        int currentSize = cooldownMap.size();
        if (currentSize > CLEANUP_THRESHOLD && currentSize > lastCleanupSize * 1.5) {
            cleanupExpiredCooldowns();
            lastCleanupSize = currentSize;
        }
    }

    /**
     * 原子方式尝试分配设备（检查+分配一体化，防止并发竞争）
     * @param machineId 要分配的机器ID
     * @param reason 分配原因
     * @param linkTaskId 关联的链接任务ID
     * @return true表示分配成功，false表示设备已被占用或在冷却中
     */
    public boolean tryAllocateDevice(String machineId, String reason, Long linkTaskId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            log.warn("尝试分配空的机器ID");
            return false;
        }

        ReentrantLock lock = getDeviceLock(machineId);
        lock.lock();
        try {
            // 原子检查：确保设备不在冷却期内
            CooldownInfo existingCooldown = cooldownMap.get(machineId);
            if (existingCooldown != null && existingCooldown.isActive()) {
                long remainingMinutes = existingCooldown.getRemainingMinutes();
                log.debug("设备{}在冷却期内，无法分配，剩余时间：{}分钟，原因：{}", 
                        machineId, remainingMinutes, existingCooldown.getReason());
                return false;
            }

            // 原子分配：立即将设备加入冷却队列
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime cooldownEndTime = now.plusMinutes(COOLDOWN_MINUTES);
            CooldownInfo newCooldown = new CooldownInfo(now, cooldownEndTime, reason, linkTaskId);
            
            cooldownMap.put(machineId, newCooldown);

            log.info("设备{}原子分配成功，原因：{}，冷却时间：{}分钟，冷却结束时间：{}，关联任务：{}",
                    machineId, reason, COOLDOWN_MINUTES, cooldownEndTime, linkTaskId);

            return true;

        } finally {
            lock.unlock();
        }
    }

    /**
     * 释放设备分配（如果后续操作失败时回滚）
     * @param machineId 要释放的机器ID
     * @param linkTaskId 关联的链接任务ID（用于验证）
     * @return true表示释放成功
     */
    public boolean releaseDeviceAllocation(String machineId, Long linkTaskId) {
        if (machineId == null || machineId.trim().isEmpty()) {
            return false;
        }

        ReentrantLock lock = getDeviceLock(machineId);
        lock.lock();
        try {
            CooldownInfo cooldownInfo = cooldownMap.get(machineId);
            if (cooldownInfo == null) {
                return false;
            }

            // 验证是否是同一个任务的分配
            if (linkTaskId != null && !linkTaskId.equals(cooldownInfo.getLinkTaskId())) {
                log.warn("尝试释放设备{}失败：任务ID不匹配，当前任务：{}，请求任务：{}", 
                        machineId, cooldownInfo.getLinkTaskId(), linkTaskId);
                return false;
            }

            cooldownMap.remove(machineId);
            log.info("设备{}分配已释放，原关联任务：{}", machineId, linkTaskId);
            return true;

        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取冷却统计信息
     */
    public CooldownStats getCooldownStats() {
        int totalDevices = cooldownMap.size();
        int activeDevices = 0;
        long totalRemainingMinutes = 0;
        
        for (CooldownInfo cooldownInfo : cooldownMap.values()) {
            if (cooldownInfo.isActive()) {
                activeDevices++;
                totalRemainingMinutes += cooldownInfo.getRemainingMinutes();
            }
        }
        
        return new CooldownStats(totalDevices, activeDevices, totalRemainingMinutes);
    }

    /**
     * 冷却统计信息
     */
    public static class CooldownStats {
        private final int totalDevices;
        private final int activeDevices;
        private final long totalRemainingMinutes;
        
        public CooldownStats(int totalDevices, int activeDevices, long totalRemainingMinutes) {
            this.totalDevices = totalDevices;
            this.activeDevices = activeDevices;
            this.totalRemainingMinutes = totalRemainingMinutes;
        }
        
        public int getTotalDevices() { return totalDevices; }
        public int getActiveDevices() { return activeDevices; }
        public long getTotalRemainingMinutes() { return totalRemainingMinutes; }
        
        @Override
        public String toString() {
            return String.format("CooldownStats{total=%d, active=%d, totalRemainingMinutes=%d}", 
                    totalDevices, activeDevices, totalRemainingMinutes);
        }
    }
}
