package com.gameplatform.server.service.link;

import com.gameplatform.server.mapper.account.UserAccountMapper;
import com.gameplatform.server.mapper.agent.AgentPointsTxMapper;
import com.gameplatform.server.mapper.agent.LinkBatchMapper;
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.model.entity.account.UserAccount;
import com.gameplatform.server.model.entity.agent.AgentPointsTx;
import com.gameplatform.server.model.entity.agent.LinkBatch;
import com.gameplatform.server.model.entity.agent.LinkTask;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gameplatform.server.service.admin.SystemConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

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

    private final UserAccountMapper userAccountMapper;
    private final LinkBatchMapper linkBatchMapper;
    private final LinkTaskMapper linkTaskMapper;
    private final AgentPointsTxMapper agentPointsTxMapper;
    private final SystemConfigService systemConfigService;

    public LinkGenerationService(UserAccountMapper userAccountMapper,
                                 LinkBatchMapper linkBatchMapper,
                                 LinkTaskMapper linkTaskMapper,
                                 AgentPointsTxMapper agentPointsTxMapper,
                                 SystemConfigService systemConfigService) {
        this.userAccountMapper = userAccountMapper;
        this.linkBatchMapper = linkBatchMapper;
        this.linkTaskMapper = linkTaskMapper;
        this.agentPointsTxMapper = agentPointsTxMapper;
        this.systemConfigService = systemConfigService;
    }

    @Transactional
    public Mono<GenerateResult> generateLinks(Long operatorId, String operatorType,
                                              int times, int linkCount) {
        return Mono.fromCallable(() -> doGenerate(operatorId, operatorType, times, linkCount))
                .subscribeOn(Schedulers.boundedElastic());
    }

    private GenerateResult doGenerate(Long operatorId, String operatorType,
                                      int times, int linkCount) {
        if (times <= 0 || linkCount <= 0) {
            throw new IllegalArgumentException("times 与 linkCount 必须为正整数");
        }

        // 获取操作者账户信息
        UserAccount operator = userAccountMapper.selectById(operatorId);
        if (operator == null) {
            throw new IllegalArgumentException("操作者账户不存在");
        }
        
        boolean isAdminOperator = "ADMIN".equalsIgnoreCase(operatorType);
        if (!isAdminOperator && !"AGENT".equalsIgnoreCase(operatorType)) {
            throw new IllegalArgumentException("非法操作者类型");
        }

        // 从配置表获取每次副本的奖励点数
        int perTimeQuantity = systemConfigService.getDefaultQuantity();
        long needPoints = (long) times;  // 只扣times，不乘以perTimeQuantity
        // 移除expireHours配置，因为NEW状态不设置过期时间
        
        if (log.isDebugEnabled()) {
            log.debug("generateLinks operatorId={} operatorType={} times={} linkCount={} perTimeQuantity={} needPoints={}",
                    operatorId, operatorType, times, linkCount, perTimeQuantity, needPoints);
        }
        
        if (!isAdminOperator) {
            // 代理商自操作，需扣点判断
            long balance = operator.getPointsBalance() == null ? 0L : operator.getPointsBalance();
            if (balance < needPoints) {
                throw new IllegalStateException("点数不足");
            }
        }

        LinkBatch batch = new LinkBatch();
        batch.setAgentId(operator.getId());
        batch.setQuantity(perTimeQuantity);  // 每次副本的奖励点数
        batch.setTimes(times);               // 打副本的次数
        batch.setOperatorId(operatorId);
        linkBatchMapper.insert(batch);

        // NEW状态的链接不设置过期时间，只有激活使用时才设置过期时间
        // 批量生成唯一的编号
        List<String> uniqueCodeNos = generateUniqueCodeNos(linkCount);
        
        // 批量创建任务对象
        List<LinkTask> tasks = new ArrayList<>(linkCount);
        for (int i = 0; i < linkCount; i++) {
            LinkTask t = new LinkTask();
            t.setBatchId(batch.getId());
            t.setAgentId(operator.getId());
            t.setCodeNo(uniqueCodeNos.get(i));
            t.setTokenHash(DigestUtils.sha256Hex(generateToken()));
            t.setExpireAt(null);  // NEW状态不设置过期时间
            t.setStatus("NEW");
            tasks.add(t);
        }
        
        // 批量插入
        linkTaskMapper.batchInsert(tasks);

        if (!isAdminOperator) {
            // 扣点流水 + 账户余额
            long before = operator.getPointsBalance() == null ? 0L : operator.getPointsBalance();
            long after = before - needPoints;

            AgentPointsTx tx = new AgentPointsTx();
            tx.setAccountId(operator.getId());
            tx.setType("DEDUCT");
            tx.setBeforePoints(before);
            tx.setDeltaPoints(-needPoints);
            tx.setAfterPoints(after);
            tx.setReason("create_links");
            tx.setRefId(batch.getId());
            tx.setOperatorId(operatorId);
            agentPointsTxMapper.insert(tx);

            UserAccount patch = new UserAccount();
            patch.setId(operator.getId());
            patch.setPointsBalance(after);
            userAccountMapper.updateById(patch);
        }

        GenerateResult result = new GenerateResult();
        result.setBatchId(batch.getId());
        result.setNeedPoints(needPoints);
        result.setExpireAt(null);  // NEW状态无过期时间
        result.setCodeNos(tasks.stream().map(LinkTask::getCodeNo).toList());
        return result;
    }

    private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars
    private static final SecureRandom RANDOM = new SecureRandom();
    private static final int MAX_BATCH_RETRY = 3; // 批量生成最大重试次数
    private static final double CANDIDATE_MULTIPLIER = 1.2; // 候选数量系数，生成比需要多20%的候选

    /**
     * 批量生成唯一的链接编号
     * 优化策略：一次生成多个候选编号，批量检查唯一性，减少数据库查询次数
     */
    private List<String> generateUniqueCodeNos(int count) {
        if (count <= 0) {
            return Collections.emptyList();
        }
        
        List<String> uniqueCodeNos = new ArrayList<>(count);
        int remainingCount = count;
        
        for (int attempt = 1; attempt <= MAX_BATCH_RETRY; attempt++) {
            // 生成候选编号（比需要的数量多一些，以应对冲突）
            int candidateCount = (int) Math.ceil(remainingCount * CANDIDATE_MULTIPLIER);
            Set<String> candidates = generateRandomCodeNos(candidateCount);
            
            if (log.isDebugEnabled()) {
                log.debug("第{}次批量生成: 需要{}个，生成{}个候选编号", attempt, remainingCount, candidates.size());
            }
            
            // 批量检查哪些编号已存在
            List<String> existingCodeNos = Collections.emptyList();
            if (!candidates.isEmpty()) {
                try {
                    existingCodeNos = linkTaskMapper.findExistingCodeNos(new ArrayList<>(candidates));
                } catch (Exception e) {
                    log.warn("批量检查编号唯一性时发生异常: {}", e.getMessage());
                    existingCodeNos = new ArrayList<>(candidates); // 异常时保守处理，认为全部重复
                }
            }
            
            Set<String> existingSet = new HashSet<>(existingCodeNos);
            
            // 过滤出未使用的编号
            List<String> availableCodeNos = candidates.stream()
                    .filter(code -> !existingSet.contains(code))
                    .limit(remainingCount)
                    .collect(Collectors.toList());
            
            uniqueCodeNos.addAll(availableCodeNos);
            remainingCount = count - uniqueCodeNos.size();
            
            if (log.isDebugEnabled()) {
                log.debug("第{}次批量生成结果: 获得{}个唯一编号，还需{}个", 
                        attempt, availableCodeNos.size(), remainingCount);
            }
            
            if (remainingCount <= 0) {
                log.info("成功批量生成{}个唯一编号，共尝试{}次", count, attempt);
                return uniqueCodeNos;
            }
        }
        
        // 如果经过多次重试仍不够，使用时间戳后缀确保唯一性
        log.warn("批量生成后仍缺少{}个编号，使用后备策略生成", remainingCount);
        long timestamp = System.currentTimeMillis();
        for (int i = 0; i < remainingCount; i++) {
            String fallbackCodeNo = generateRandomCodeNo() + (timestamp + i) % 10000;
            uniqueCodeNos.add(fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length())));
        }
        
        return uniqueCodeNos;
    }
    
    /**
     * 生成一批随机编号（使用Set去重）
     */
    private Set<String> generateRandomCodeNos(int count) {
        Set<String> codeNos = new HashSet<>(count);
        // 生成稍多一些以应对Set去重
        int attempts = count * 2;
        for (int i = 0; i < attempts && codeNos.size() < count; i++) {
            codeNos.add(generateRandomCodeNo());
        }
        return codeNos;
    }
    
    /**
     * 生成8位随机编号
     */
    private String generateRandomCodeNo() {
        StringBuilder sb = new StringBuilder(8);
        for (int i = 0; i < 8; i++) {
            sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
        }
        return sb.toString();
    }

    private String generateToken() {
        byte[] bytes = new byte[32];
        RANDOM.nextBytes(bytes);
        return org.apache.commons.codec.binary.Hex.encodeHexString(bytes);
    }

    public static class GenerateResult {
        private Long batchId;
        private Long needPoints;
        private LocalDateTime expireAt;
        private List<String> codeNos;

        public Long getBatchId() { return batchId; }
        public void setBatchId(Long batchId) { this.batchId = batchId; }
        public Long getNeedPoints() { return needPoints; }
        public void setNeedPoints(Long needPoints) { this.needPoints = needPoints; }
        public LocalDateTime getExpireAt() { return expireAt; }
        public void setExpireAt(LocalDateTime expireAt) { this.expireAt = expireAt; }
        public List<String> getCodeNos() { return codeNos; }
        public void setCodeNos(List<String> codeNos) { this.codeNos = codeNos; }
    }
}


