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.ArrayList;
import java.util.List;

@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<LinkTask> tasks = new ArrayList<>();
        for (int i = 0; i < linkCount; i++) {  // 生成linkCount个链接
            LinkTask t = new LinkTask();
            t.setBatchId(batch.getId());
            t.setAgentId(operator.getId());
            t.setCodeNo(generateCodeNo());
            t.setTokenHash(DigestUtils.sha256Hex(generateToken()));
            t.setExpireAt(null);  // NEW状态不设置过期时间
            t.setStatus("NEW");
            linkTaskMapper.insert(t);
            tasks.add(t);
        }

        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_RETRY_ATTEMPTS = 5; // 最大重试次数

    /**
     * 生成唯一的链接编号
     * 利用数据库唯一约束确保编号不重复
     */
    private String generateCodeNo() {
        for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
            String codeNo = generateRandomCodeNo();
            
            // 检查是否已存在（利用数据库唯一约束）
            if (isCodeNoUnique(codeNo)) {
                log.debug("生成唯一编号成功: {} (第{}次尝试)", codeNo, attempt);
                return codeNo;
            }
            
            log.warn("编号{}已存在，第{}次尝试生成新编号", codeNo, attempt);
        }
        
        // 如果多次重试都失败，使用时间戳后缀确保唯一性
        String fallbackCodeNo = generateRandomCodeNo() + System.currentTimeMillis() % 10000;
        log.warn("使用后备编号生成策略: {}", fallbackCodeNo);
        return fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length()));
    }
    
    /**
     * 生成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 boolean isCodeNoUnique(String codeNo) {
        try {
            LinkTask existingTask = linkTaskMapper.findByCodeNo(codeNo);
            return existingTask == null;
        } catch (Exception e) {
            log.warn("检查编号{}唯一性时发生异常: {}", codeNo, e.getMessage());
            // 异常时保守处理，认为不唯一
            return false;
        }
    }

    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; }
    }
}


