package com.ssyf.module.robot.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssyf.common.security.domain.ClientInfo;
import com.ssyf.common.security.helper.ClientContextHelper;
import com.ssyf.common.web.apicode.ApiCodeUser;
import com.ssyf.common.web.error.ServiceRuntimeException;
import com.ssyf.module.robot.domain.entity.RRobotAccount;
import com.ssyf.module.robot.domain.entity.RRobotFee;
import com.ssyf.module.robot.domain.entity.RRobotGroup;
import com.ssyf.module.robot.domain.query.RRobotFeeQuery;
import com.ssyf.module.robot.domain.vo.FeeStatistics;
import com.ssyf.module.robot.service.RRobotFeeService;
import com.ssyf.module.robot.mapper.RRobotFeeMapper;
import com.ssyf.module.robot.service.RRobotGroupService;
import com.ssyf.plugs.mysql.helper.MybatisPlusHelper;
import com.ssyf.third.bmiss.services.FundPoolService;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* @author ELSA
* @description 针对表【r_robot_fee(机器人费用记录表)】的数据库操作Service实现
* @createDate 2025-12-12 10:19:24
*/
@Slf4j
@Service
public class RRobotFeeServiceImpl extends ServiceImpl<RRobotFeeMapper, RRobotFee>
    implements RRobotFeeService{

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Lazy
    @Autowired
    private RRobotGroupService rRobotGroupService;

    @Lazy
    @Autowired
    private FundPoolService fundPoolService;
    
    // Redis缓存键前缀
    private static final String ROBOT_FEE_CACHE_KEY_PREFIX = "robot:fee:";
    private static final long CACHE_EXPIRE_HOURS = 1;

    // 新增：更新锁前缀
    private static final String ROBOT_FEE_UPDATE_LOCK = "robot:fee:lock:update:";
    // 新增：更新补偿队列Key
    private static final String ROBOT_FEE_UPDATE_QUEUE = "robot:fee:queue:update";

    @Override
    public RRobotFee addOne(RRobotFeeQuery query) {
        // 验证新增金额是否超过最大可用金额
        validateAddAmount(query.getTotalAmount());
        
        if(StrUtil.isNotBlank(query.getGroupUuid())){
            RRobotGroup robotGroup = this.rRobotGroupService.getOneByUid(query.getGroupUuid());
            if(robotGroup == null){
                ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode() , "机器人分组不存在");
            }
            query.setGroupName(robotGroup.getGroupName());
        }

        if(StrUtil.isBlank(query.getRobotUuid()) && StrUtil.isBlank(query.getGroupUuid())){
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode() , "机器人和分组不能同时为空");
        }


        ClientInfo clientInfo = ClientContextHelper.getClientInfo();
        RRobotFee RRobotFee = BeanUtil.toBean(query ,RRobotFee.class);
        RRobotFee.setUuid(IdUtil.simpleUUID());
        RRobotFee.setCreatorUuid(clientInfo.getId().toString()).setCreator(clientInfo.getNickname())
                .setCreateTime(LocalDateTime.now());
        this.save(RRobotFee);
        return RRobotFee;
    }

    @Override
    public RRobotFee getOneByUid(String uid) {
        return this.getOne(createBaseWrapper(null).eq(RRobotFee::getUuid, uid));
    }

    @Override
    public PageInfo<RRobotFee> getPage(RRobotFeeQuery query) {
        PageHelper.startPage(query.getPageNum(), query.getPageSize());
        return PageInfo.of(this.list(createBaseWrapper(query)));
    }

    @Override
    public List<RRobotFee> getList(RRobotFeeQuery query) {
        return this.list(createBaseWrapper(query));
    }

    @Override
    public RRobotFee checkedExistAndGet(String uid) {
        RRobotFee old = this.getOne(createBaseWrapper(null).eq(RRobotFee::getUuid, uid));
        if (old == null) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), "数据不存在");
        }
        return old;
    }

    @Override
    public Integer countBy(RRobotFeeQuery query) {
        return this.count(createBaseWrapper(query));
    }

    @Override
    public RRobotFee getOneAvailable(RRobotAccount robotAccount) {
        if (robotAccount == null) {
            return null;
        }

        // 先从缓存中获取
        String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + robotAccount.getUuid();
        RRobotFee cachedFee = (RRobotFee) redisTemplate.opsForValue().get(cacheKey);
        if (cachedFee != null) {
            return cachedFee;
        }

        // 缓存中没有，使用自定义SQL查询
        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // LocalDateTime转为字符串
        String validStartTimeStr = LocalDateTime.now().format(formatter);
        
        // 使用自定义SQL查询，包含已使用费用小于总费用的条件
        RRobotFee robotFee = baseMapper.selectOneAvailable(
            robotAccount.getUuid(), 
            robotAccount.getGroupId(), 
            validStartTimeStr
        );
        
        if (robotFee == null) {
            return null;
        }

        // 有可用余额，放入缓存
        redisTemplate.opsForValue().set(cacheKey, robotFee, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        return robotFee;
    }

//    @Override
//    public boolean updateUsedAmount(String robotUuid, Double usedAmount) {
//        if (StrUtil.isBlank(robotUuid) || usedAmount == null || usedAmount < 0) {
//            return false;
//        }
//
//        RRobotFee robotFee = getOneAvailable(robotUuid);
//        if (robotFee == null){
//            return false;
//        }
//
//        // 更新已使用金额
//        Double currentUsedAmount = robotFee.getUsedAmount() != null ? robotFee.getUsedAmount() : 0.0;
//        Double newUsedAmount = currentUsedAmount + usedAmount;
//
//        // 确保不会超过总金额
//        if (robotFee.getTotalAmount() != null && newUsedAmount > robotFee.getTotalAmount()) {
//            newUsedAmount = robotFee.getTotalAmount();
//        }
//
//        robotFee.setUsedAmount(newUsedAmount);
//
//        boolean updateResult = this.updateById(robotFee);
//
//        if (updateResult) {
//            // 更新成功，清理缓存
//            String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + robotUuid;
//            redisTemplate.delete(cacheKey);
//            return true;
//        }
//
//        return false;
//    }

    // 在更新和删除操作中添加缓存清理逻辑
    @Override
    public void updateByUid(String uid, RRobotFeeQuery query) {
        RRobotFee oldFee = this.checkedExistAndGet(uid);

        // 验证编辑金额是否合法
        if (query.getTotalAmount() != null) {
            validateUpdateAmount(oldFee, query.getTotalAmount());
        }

        if(StrUtil.isNotBlank(query.getGroupUuid())){
            RRobotGroup robotGroup = this.rRobotGroupService.getOneByUid(query.getGroupUuid());
            if(robotGroup == null){
                ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode() , "机器人分组不存在");
            }
            query.setGroupName(robotGroup.getGroupName());
        }
        if (StrUtil.isBlank(query.getRobotUuid())){
            if(StrUtil.isBlank(query.getGroupUuid())){
                ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode() , "机器人和分组不能同时为空");
            }
            query.setRobotAccount("");
        }

        //不能更新uid，和id
        query.setUuid(null);
        RRobotFee update = BeanUtil.toBean(query ,RRobotFee.class);
        LambdaQueryWrapper<RRobotFee> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RRobotFee::getUuid, uid);
        this.update(update , wrapper);
        // 清理缓存
        String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + uid;
        redisTemplate.delete(cacheKey);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByUid(String uid) {
        RRobotFee fee = checkedExistAndGet(uid);

        // 验证删除操作是否允许
        validateDelete(fee);

        //todo 关联数据查询

        this.remove(createBaseWrapper(null).eq(RRobotFee::getUuid, uid));
        // 清理缓存
        String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + uid;
        redisTemplate.delete(cacheKey);
    }

    @Override
    public void removeBatchByUids(List<String> uids) {
        // 验证每个费用记录是否允许删除
        for (String uid : uids) {
            RRobotFee fee = getOneByUid(uid);
            if (fee != null) {
                validateDelete(fee);
            }
        }

        //todo 关联数据查询
        this.remove(createBaseWrapper(null).in(RRobotFee::getUuid, uids));
        // 清理缓存
        for (String uid : uids) {
            String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + uid;
            redisTemplate.delete(cacheKey);
        }
    }

    private LambdaQueryWrapper<RRobotFee> createBaseWrapper(RRobotFeeQuery query) {
        LambdaQueryWrapper<RRobotFee> wrapper = new LambdaQueryWrapper<>();
        if (query == null) {
            return wrapper;
        }
        wrapper.like(StrUtil.isNotBlank(query.getGroupName()), RRobotFee::getGroupName, query.getGroupName());
        wrapper.like(StrUtil.isNotBlank(query.getGroupUuid()), RRobotFee::getGroupUuid, query.getGroupUuid());
        wrapper.like(StrUtil.isNotBlank(query.getRobotAccount()), RRobotFee::getRobotAccount, query.getRobotAccount());
        wrapper.eq(query.getStatus() != null ,RRobotFee::getStatus, query.getStatus());
        wrapper.like(StrUtil.isNotBlank(query.getUuid()) , RRobotFee::getUuid, query.getUuid());
        // 根据实际业务需求补充更多查询条件
        wrapper.eq(StrUtil.isNotBlank(query.getRobotUuid()), RRobotFee::getRobotUuid, query.getRobotUuid());
        wrapper.like(StrUtil.isNotBlank(query.getNotes()), RRobotFee::getNotes, query.getNotes());
        wrapper.ge(query.getValidStartTime() != null, RRobotFee::getValidStartTime, query.getValidStartTime());
        wrapper.le(query.getValidEndTime() != null, RRobotFee::getValidEndTime, query.getValidEndTime());
        // 示例排序逻辑
        if (CollectionUtil.isNotEmpty(query.getSorts())) {
            MybatisPlusHelper.orderAndIgnoreError(RRobotFee.class, wrapper, query.getSorts());
        } else {
            wrapper.orderByDesc(RRobotFee::getCreateTime);
        }
        return wrapper;
    }
    
    @Override
    public FeeStatistics getFeeStatistics() {
        Double total = this.fundPoolService.getTotalFund();
        if(total == null || total <= 0){
            return new FeeStatistics().setTotalPool(0.0);
        }
        FeeStatistics statistics = this.baseMapper.selectFeeStatistics();

        statistics.setTotalPool(total);
        statistics.setAllocatedAmount(statistics.getTotalAllocatedAmount() - statistics.getUsedAmount());
        statistics.setAvailableAmount(statistics.getTotalPool() - statistics.getAllocatedAmount());

        return statistics;
    }

    /**
     * 获取资金池最大可用金额
     * @return 最大可用金额
     */
    private Double getMaxAvailableAmount() {
        FeeStatistics statistics = getFeeStatistics();
        return statistics.getAvailableAmount();
    }

    /**
     * 验证新增金额是否超过最大可用金额
     * @param totalAmount 新增总金额
     * @return 验证结果
     */
    private void validateAddAmount(Double totalAmount) {
        if (totalAmount == null || totalAmount <= 0) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), "总金额必须大于0");
        }

        Double maxAvailable = getMaxAvailableAmount();
        if (maxAvailable == null || maxAvailable <= 0) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), "资金池无可用金额");
        }

        if (totalAmount > maxAvailable) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), 
                    String.format("新增金额%.2f超过资金池最大可用金额%.2f", totalAmount, maxAvailable));
        }
    }

    /**
     * 验证编辑金额是否合法
     * @param oldFee 原费用记录
     * @param newTotalAmount 新总金额
     */
    private void validateUpdateAmount(RRobotFee oldFee, Double newTotalAmount) {
        if (newTotalAmount == null || newTotalAmount <= 0) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), "总金额必须大于0");
        }

        // 计算已使用和申请金额总和
        Double usedAmount = oldFee.getUsedAmount() != null ? oldFee.getUsedAmount() : 0.0;
        Double applyAmount = oldFee.getApplyAmount() != null ? oldFee.getApplyAmount() : 0.0;
        Double usedAndApplyTotal = usedAmount + applyAmount;

        // 新总金额不能小于已使用和申请金额总和
        if (newTotalAmount < usedAndApplyTotal) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), 
                    String.format("新总金额%.2f不能小于已使用金额%.2f和申请金额%.2f的总和%.2f", 
                            newTotalAmount, usedAmount, applyAmount, usedAndApplyTotal));
        }

        // 计算新增分配金额
        Double oldTotalAmount = oldFee.getTotalAmount() != null ? oldFee.getTotalAmount() : 0.0;
        Double additionalAmount = newTotalAmount - oldTotalAmount;

        // 如果新增分配金额大于0，需要检查资金池可用金额
        if (additionalAmount > 0) {
            Double maxAvailable = getMaxAvailableAmount();
            if (maxAvailable == null || maxAvailable <= 0) {
                ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), "资金池无可用金额");
            }

            if (additionalAmount > maxAvailable) {
                ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), 
                        String.format("新增分配金额%.2f超过资金池最大可用金额%.2f", additionalAmount, maxAvailable));
            }
        }
    }

    /**
     * 验证删除操作是否允许
     * @param fee 费用记录
     */
    private void validateDelete(RRobotFee fee) {
        Double usedAmount = fee.getUsedAmount() != null ? fee.getUsedAmount() : 0.0;
        Double applyAmount = fee.getApplyAmount() != null ? fee.getApplyAmount() : 0.0;

        if (usedAmount > 0) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), 
                    String.format("费用记录存在已使用金额%.2f，不能删除", usedAmount));
        }

        if (applyAmount > 0) {
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode(), 
                    String.format("费用记录存在申请未用金额%.2f，不能删除", applyAmount));
        }
    }

    /**
     * 内部类：用于队列传输的费用变更消息体
     */
    @Data
    @Accessors(chain = true)
    public static class FeeUpdateMessage implements Serializable {
        private String feeUid;
        private String robotUuid;
        private double changeInApply;
        private double changeInUsed;
    }

    @Override
    // 移除 synchronized，改用分布式锁
    // 注意：Transactional 建议放在私有的 executeUpdate 方法上，或者确保锁的生命周期覆盖事务
    public synchronized boolean updateFeeAfterGift(String feeUid, String robotUuid, double changeInApply, double changeInUsed) {
        if (StrUtil.isBlank(feeUid)) {
            return false;
        }

        String lockKey = ROBOT_FEE_UPDATE_LOCK + feeUid;
        String clientId = IdUtil.fastSimpleUUID(); // 锁的标识，防止误删

        // 1. 尝试获取分布式锁 (设置5秒过期，防止死锁)
        Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.SECONDS);

        if (Boolean.TRUE.equals(isLocked)) {
            try {
                // 获取锁成功，直接执行数据库更新
                return executeFeeUpdate(feeUid, robotUuid, changeInApply, changeInUsed);
            } catch (Exception e) {
                log.error("更新费用异常，尝试降级到队列: feeUid={}", feeUid, e);
                // 异常时也推入队列，确保数据不丢失
                pushToUpdateQueue(feeUid, robotUuid, changeInApply, changeInUsed);
                return true; // 视为受理成功
            } finally {
                // 释放锁：先判断是否是自己的锁（简单实现，严谨可使用Lua脚本）
                if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                    redisTemplate.delete(lockKey);
                }
            }
        } else {
            // 2. 获取锁失败（并发冲突），将请求推入Redis队列
            log.info("获取费用更新锁失败，推入补偿队列: feeUid={}", feeUid);
            pushToUpdateQueue(feeUid, robotUuid, changeInApply, changeInUsed);
            return true; // 返回true表示请求已接收（最终一致性）
        }
    }

    /**
     * 执行实际的数据库更新逻辑 (开启事务)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean executeFeeUpdate(String feeUid, String robotUuid, double changeInApply, double changeInUsed) {
        RRobotFee fee = getOneByUid(feeUid);
        if (fee == null) {
            log.error("费用记录不存在: {}", feeUid);
            return false;
        }

        double newApply = (fee.getApplyAmount() == null ? 0.0 : fee.getApplyAmount()) + changeInApply;
        // 逻辑修正：防止金额变为负数
        if (newApply < 0) newApply = 0.0;

        double newUsed = (fee.getUsedAmount() == null ? 0.0 : fee.getUsedAmount()) + changeInUsed;

        fee.setApplyAmount(newApply);
        fee.setUsedAmount(newUsed);

        // 记录日志
        log.debug("Updating fee DB: uuid={}, changeApply={}, changeUsed={}", feeUid, changeInApply, changeInUsed);

        boolean result = this.updateById(fee);

        if (result) {
            // 更新成功，清理详情缓存
            String cacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + feeUid;
            redisTemplate.delete(cacheKey);
            // 清理机器人费用缓存
            if (StrUtil.isNotBlank(robotUuid)) {
                String robotCacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + robotUuid;
                redisTemplate.delete(robotCacheKey);
            }
            if (StrUtil.isNotBlank(fee.getRobotUuid())) {
                String robotCacheKey = ROBOT_FEE_CACHE_KEY_PREFIX + fee.getRobotUuid();
                redisTemplate.delete(robotCacheKey);
            }
        }
        return result;
    }

    /**
     * 将更新请求推入 Redis List 队列
     */
    private void pushToUpdateQueue(String feeUid, String robotUuid, double changeInApply, double changeInUsed) {
        FeeUpdateMessage message = new FeeUpdateMessage()
                .setFeeUid(feeUid)
                .setRobotUuid(robotUuid)
                .setChangeInApply(changeInApply)
                .setChangeInUsed(changeInUsed);
        // 将对象转为JSON字符串存入队列
        redisTemplate.opsForList().rightPush(ROBOT_FEE_UPDATE_QUEUE, JSONUtil.toJsonStr(message));
    }

    /**
     * 定时任务：检测队列并同步数据
     * 建议配置：每隔 5 秒或 10 秒执行一次，或者使用 @StreamListener 监听（如果引入了 MQ）
     * 这里使用简单的 Schedule 轮询
     */
    @Scheduled(fixedDelay = 5000)
    public void syncFeeFromQueue() {
        // 循环处理，直到队列为空或达到单次处理上限
        int maxProcessPerLoop = 100;
        int processed = 0;

        while (processed < maxProcessPerLoop) {
            // 1. 从队列左侧弹出一个消息
            Object msgObj = redisTemplate.opsForList().leftPop(ROBOT_FEE_UPDATE_QUEUE);
            if (msgObj == null) {
                break; // 队列为空
            }

            String msgStr = (String) msgObj;
            FeeUpdateMessage message = null;
            try {
                message = JSONUtil.toBean(msgStr, FeeUpdateMessage.class);
            } catch (Exception e) {
                log.error("解析费用更新消息失败: {}", msgStr, e);
                continue;
            }

            if (message != null && StrUtil.isNotBlank(message.getFeeUid())) {
                String lockKey = ROBOT_FEE_UPDATE_LOCK + message.getFeeUid();
                String clientId = "scheduler-" + IdUtil.fastSimpleUUID();

                // 2. 消费者尝试获取锁
                // 消费者必须拿到锁才能更新，否则可能覆盖实时请求的数据。
                // 如果拿不到锁，说明有实时请求正在操作，或者其他消费者在操作。
                // 策略：拿不到锁则重新扔回队列（或者等待重试）
                Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.SECONDS);

                if (Boolean.TRUE.equals(isLocked)) {
                    try {
                        executeFeeUpdate(message.getFeeUid() , message.getRobotUuid(), message.getChangeInApply(), message.getChangeInUsed());
                        log.info("队列补偿更新成功: {}", message);
                    } catch (Exception e) {
                        log.error("队列补偿更新执行异常，重新入队: {}", message, e);
                        // 异常回滚：重新塞回队列右侧
                        redisTemplate.opsForList().rightPush(ROBOT_FEE_UPDATE_QUEUE, msgStr);
                    } finally {
                        if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                            redisTemplate.delete(lockKey);
                        }
                    }
                } else {
                    // 获取锁失败，说明该记录正在被操作，稍后重试
                    // 将消息塞回队列（右侧），延迟处理
                    log.debug("队列消费获取锁失败，消息回队: {}", message.getFeeUid());
                    redisTemplate.opsForList().rightPush(ROBOT_FEE_UPDATE_QUEUE, msgStr);

                    // 为避免死循环（如果队列里全是锁住的），这里可以稍微 sleep 一下或者跳出当前循环
                    try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                }
            }
            processed++;
        }
    }
}




