package com.ruoyi.cc.service.impl;

import java.util.*;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.ruoyi.cc.service.ICcParamsService;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.cc.mapper.CcExtNumMapper;
import com.ruoyi.cc.domain.CcExtNum;
import com.ruoyi.cc.service.ICcExtNumService;
import com.ruoyi.common.core.text.Convert;

/**
 * 【请填写功能名称】Service业务层处理
 * 
 * @author ruoyi
 * @date 2024-12-22
 */
@Service
@Slf4j
public class CcExtNumServiceImpl implements ICcExtNumService 
{
    @Autowired
    private CcExtNumMapper ccExtNumMapper;
    @Autowired
    private ICcParamsService ccParamsService;
    private String authTokenSecret;
    
    // 缓存最大分机号，初始值为0表示未初始化，需要从数据库查询
    // 使用volatile保证多线程可见性
    private volatile long cachedMaxExtNum = 0;
    
    // 用于同步获取和更新缓存的锁对象
    private final Object cacheLock = new Object();

    /**
     * 查询【请填写功能名称】
     * 
     * @param extId 【请填写功能名称】主键
     * @return 【请填写功能名称】
     */
    @Override
    public CcExtNum selectCcExtNumByExtId(Long extId)
    {
        return ccExtNumMapper.selectCcExtNumByExtId(extId);
    }

    /**
     * 查询【请填写功能名称】列表
     * 
     * @param ccExtNum 【请填写功能名称】
     * @return 【请填写功能名称】
     */
    @Override
    public List<CcExtNum> selectCcExtNumList(CcExtNum ccExtNum)
    {
        return ccExtNumMapper.selectCcExtNumList(ccExtNum);
    }

    /**
     * 新增【请填写功能名称】
     * 
     * @param ccExtNum 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int insertCcExtNum(CcExtNum ccExtNum)
    {
        return ccExtNumMapper.insertCcExtNum(ccExtNum);
    }

    /**
     * 修改【请填写功能名称】
     * 
     * @param ccExtNum 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int updateCcExtNum(CcExtNum ccExtNum)
    {
        return ccExtNumMapper.updateCcExtNum(ccExtNum);
    }

    /**
     * 批量删除【请填写功能名称】
     * 
     * @param extIds 需要删除的【请填写功能名称】主键
     * @return 结果
     */
    @Override
    public int deleteCcExtNumByExtIds(String extIds)
    {
        return ccExtNumMapper.deleteCcExtNumByExtIds(Convert.toStrArray(extIds));
    }

    /**
     * 删除【请填写功能名称】信息
     * 
     * @param extId 【请填写功能名称】主键
     * @return 结果
     */
    @Override
    public int deleteCcExtNumByExtId(Long extId)
    {
        return ccExtNumMapper.deleteCcExtNumByExtId(extId);
    }

    @Override
    public CcExtNum selectCcExtNumByExtNum(Long extNum) {
        List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setExtNum(extNum));
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public CcExtNum selectCcExtNumByUserCode(String userCode) {
        List<CcExtNum> list = selectCcExtNumList(new CcExtNum().setUserCode(userCode));
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }



    @Override
    public String createToken(String extnum, String opnum, String groupId, String skillLevel, String projectId)  {
        try {
            log.info("extnum:{}, opnum:{}, groupId:{}", extnum, opnum, groupId);
            authTokenSecret = ccParamsService.getParamValueByCode(
                    "ws-server-auth-token-secret", "123456");
            //登录成功后生成JWT
            //JWT的header部分,该map可以是空的,因为有默认值{"alg":HS256,"typ":"JWT"}
            Map<String, Object> map = new HashMap<>();
            Calendar instance = Calendar.getInstance();
            instance.add(Calendar.HOUR, 24);
            return JWT.create()
                    //添加头部
                    .withHeader(map)
                    //添加payload
                    .withClaim("extnum", extnum)
                    .withClaim("opnum", opnum)
                    .withClaim("groupId", groupId)
                    .withClaim("skillLevel", skillLevel)
                    .withClaim("projectId", projectId)
                    //设置过期时间
                    .withExpiresAt(instance.getTime())
                    //设置签名 密钥
                    .sign(Algorithm.HMAC256(authTokenSecret));

        } catch (Exception err) {
            log.error("error:" + err.getMessage());
        }
        return null;
    }

    @Override
    public List<CcExtNum> selectUnBindCcExtNumList() {
        return ccExtNumMapper.selectUnBindCcExtNumList();
    }
    @Override
    public int cleanCcExtNumByUserCode(String loginName) {
        return ccExtNumMapper.cleanCcExtNumByUserCode(loginName);
    }
    @Override
    public int updateCcExtNumByUserCode(CcExtNum extNum) {
        return ccExtNumMapper.updateCcExtNumByUserCode(extNum);
    }

    @Override
    public List<CcExtNum> selectUnBindCcExtNumListPage(CcExtNum ccExtNum) {
        return ccExtNumMapper.selectUnBindCcExtNumListPage(ccExtNum);
    }

    @Override
    public int updateCompanyBindExtNum(CcExtNum extNum) {
        return ccExtNumMapper.updateCompanyBindExtNum(extNum);
    }

    @Override
    public int batchInsertCcExtNum(int count, String extPass, String userCode, Long startExtNum) {
        // ==================== 1. 参数校验（安全检查）====================
        if (count <= 0 || count > 10000) {
            throw new RuntimeException("生成数量必须在1-10000之间");
        }
        
        // 密码可以为空字符串，但不能为null
        if (extPass == null) {
            extPass = "";
        }
        
        // 密码长度校验（最多50个字符）
        if (extPass.length() > 50) {
            throw new RuntimeException("分机密码长度不能超过50个字符");
        }
        
        // 工号长度校验（最多32个字符）
        if (StringUtils.isNotEmpty(userCode) && userCode.length() > 32) {
            throw new RuntimeException("工号长度不能超过32个字符");
        }
        
        // ==================== 2. 工号唯一性校验 ====================
        if (StringUtils.isNotEmpty(userCode)) {
            CcExtNum checkUserCode = selectCcExtNumByUserCode(userCode);
            if (checkUserCode != null) {
                throw new RuntimeException("该工号已经绑定分机" + checkUserCode.getExtNum() + "，不允许重复绑定！");
            }
        }
        
        // ==================== 3. 确定起始分机号（使用缓存+数据库双重保障）====================
        long actualStartExtNum;
        long currentMaxExtNum;
        
        synchronized (cacheLock) {
            // 如果缓存为0，说明是第一次使用或系统刚重启，需要从数据库查询
            if (cachedMaxExtNum == 0) {
                Long dbMaxExtNum = ccExtNumMapper.selectMaxExtNum();
                currentMaxExtNum = (dbMaxExtNum == null) ? 0L : dbMaxExtNum;
                cachedMaxExtNum = currentMaxExtNum; // 更新缓存
                log.info("首次初始化分机号缓存，数据库最大分机号: {}", currentMaxExtNum);
            } else {
                currentMaxExtNum = cachedMaxExtNum;
            }
            
            // 确定实际起始分机号
            if (startExtNum == null || startExtNum <= 0) {
                actualStartExtNum = currentMaxExtNum + 1;
                if (actualStartExtNum <= 0) {
                    actualStartExtNum = 1L; // 确保起始值至少为1
                }
            } else {
                // 校验用户指定的起始分机号不能小于当前最大值
                if (startExtNum <= currentMaxExtNum) {
                    throw new RuntimeException("起始分机号(" + startExtNum + ")不能小于等于当前最大分机号(" + currentMaxExtNum + "),可选择不填起始分机号");
                }
                actualStartExtNum = startExtNum;
            }
        }
        
        // ==================== 4. 查询已存在的分机号（用于去重）====================
        List<Long> existingExtNums = ccExtNumMapper.selectExtNumsGreaterThan(actualStartExtNum - 1);
        Set<Long> existingExtNumSet = new HashSet<>(existingExtNums);
        
        // ==================== 5. 分批生成分机号并插入（防止内存溢出）====================
        int totalInserted = 0;
        long currentExtNum = actualStartExtNum;
        int remainingCount = count;
        
        // 分机号最大值限制（防止Long溢出）
        final long MAX_EXT_NUM = Long.MAX_VALUE - 10000; // 预留安全空间
        
        // 外层批次大小：每次生成1000个分机号
        final int GENERATE_BATCH_SIZE = 1000;
        // 内层插入批次大小：每次插入500条到数据库
        final int INSERT_BATCH_SIZE = 500;
        
        try {
            while (remainingCount > 0) {
                // 计算当前批次要生成的数量
                int currentBatchSize = Math.min(GENERATE_BATCH_SIZE, remainingCount);
                List<CcExtNum> currentBatchList = new ArrayList<>(currentBatchSize);
                
                // 生成当前批次的分机号
                int generatedInBatch = 0;
                long attempts = 0;
                // 防死循环：每个批次最多尝试 currentBatchSize * 10 次
                long maxAttemptsPerBatch = (long) currentBatchSize * 10L;
                
                while (generatedInBatch < currentBatchSize) {
                    attempts++;
                    
                    // 【关键】安全检查1：防止死循环
                    if (attempts > maxAttemptsPerBatch) {
                        String errorMsg = String.format(
                            "生成分机号失败：在区间[%d, %d]内可用分机号不足（已尝试%d次）。建议：1)删除部分已有分机号 2)指定更大的起始位置",
                            actualStartExtNum, currentExtNum, attempts
                        );
                        log.error(errorMsg);
                        throw new RuntimeException(errorMsg);
                    }
                    
                    // 【关键】安全检查2：防止Long溢出
                    if (currentExtNum <= 0 || currentExtNum >= MAX_EXT_NUM) {
                        String errorMsg = String.format(
                            "分机号已达到系统上限(currentExtNum=%d, MAX_EXT_NUM=%d)，无法继续生成",
                            currentExtNum, MAX_EXT_NUM
                        );
                        log.error(errorMsg);
                        throw new RuntimeException(errorMsg);
                    }
                    
                    // 如果当前分机号不存在，则添加到当前批次列表
                    if (!existingExtNumSet.contains(currentExtNum)) {
                        CcExtNum newExt = new CcExtNum();
                        newExt.setExtNum(currentExtNum);
                        newExt.setExtPass(extPass);
                        newExt.setUserCode(userCode != null ? userCode : "");
                        currentBatchList.add(newExt);
                        generatedInBatch++;
                        
                        // 将新生成的分机号加入已存在集合，避免同一批次内重复
                        existingExtNumSet.add(currentExtNum);
                    }
                    currentExtNum++;
                }
                
                // ==================== 6. 分批插入数据库 ====================
                if (!currentBatchList.isEmpty()) {
                    for (int i = 0; i < currentBatchList.size(); i += INSERT_BATCH_SIZE) {
                        int endIndex = Math.min(i + INSERT_BATCH_SIZE, currentBatchList.size());
                        List<CcExtNum> insertBatchList = currentBatchList.subList(i, endIndex);
                        int inserted = ccExtNumMapper.batchInsertCcExtNum(insertBatchList);
                        totalInserted += inserted;
                    }
                    
                    // 更新剩余计数
                    remainingCount -= generatedInBatch;
                    
                    // 【关键】更新缓存的最大分机号（在同步块中保证线程安全）
                    synchronized (cacheLock) {
                        long newMaxExtNum = currentBatchList.get(currentBatchList.size() - 1).getExtNum();
                        if (newMaxExtNum > cachedMaxExtNum) {
                            cachedMaxExtNum = newMaxExtNum;
                            log.debug("更新分机号缓存为: {}", cachedMaxExtNum);
                        }
                    }
                    
                    // 记录进度日志
                    log.info("批量生成分机号进度: {}/{}", totalInserted, count);
                }
            }
            
            log.info("批量生成分机号完成，共生成 {} 条记录", totalInserted);
            return totalInserted;
            
        } catch (RuntimeException e) {
            log.error("批量生成分机号失败: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("批量生成分机号发生未知错误", e);
            throw new RuntimeException("批量生成分机号失败: " + e.getMessage(), e);
        }
    }

    @Override
    public int companyBatchUnbindSipExt(Set<Long> extNums) {
        return ccExtNumMapper.companyBatchUnbindSipExt(extNums);
    }

    @Override
    public int updateUserCodeByLoginName(String companyName, String loginName) {
        return ccExtNumMapper.updateUserCodeByLoginName(companyName, loginName);
    }

    @Override
    public int batchUpdateUserCodeByExtNum(List<CcExtNum> list) {
        return ccExtNumMapper.batchUpdateUserCodeByExtNum(list);
    }

    @Override
    public CcExtNum selectCcExtNumByUserCodeTwo(String username) {
        return ccExtNumMapper.selectCcExtNumByUserCodeTwo(username);
    }
}
