package com.ruoyi.aicall.controller;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.aicall.domain.*;
import com.ruoyi.aicall.model.*;
import com.ruoyi.aicall.service.*;
import com.ruoyi.aicall.utils.DESUtil;
import com.ruoyi.cc.domain.*;
import com.ruoyi.cc.service.*;
import com.ruoyi.cc.utils.DateValidatorUtils;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.CommonUtils;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.uuid.UuidGenerator;
import com.ruoyi.framework.shiro.service.SysPasswordService;
import com.ruoyi.framework.shiro.util.AuthorizationUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 对外开放 API。
 * <p>IP 白名单统一由 AOP 校验，勿在本类方法内重复校验：
 * {@link com.ruoyi.aicall.aspect.ApiClientIpAspect}
 * （参数码：api-client-white-ips）</p>
 */
@Controller
@Slf4j
@RequestMapping("/aicall/api")
public class ApiController extends BaseController {

    @Autowired
    private ICcCallTaskService callTaskService;
    @Autowired
    private ICcCallPhoneService ccCallPhoneService;
    @Autowired
    private ICcInboundCdrService inboundCdrService;
    @Autowired
    private ICcOutboundCdrService outboundCdrService;
    @Autowired
    private ICcParamsService paramsService;
    @Autowired
    private ICcGatewaysService ccGatewaysService;
    @Autowired
    private ICcLlmAgentAccountService ccLlmAgentAccountService;
    @Autowired
    private ICcBizGroupService ccBizGroupService;
    @Autowired
    private ICcCallTaskService ccCallTaskService;
    @Autowired
    private ICcTtsAliyunService ccTtsAliyunService;
    @Autowired
    private ICcExtNumService ccExtNumService;
    @Autowired
    private ICcParamsService ccParamsService;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private SysPasswordService passwordService;
    @Autowired
    private ICcCustCallRecordService ccCustCallRecordService;
    @Autowired
    private ICcCustInfoService ccCustInfoService;
    @Autowired
    private ISysDivisionDataService sysDivisionDataService;
    @Autowired
    private ICcOutboundCdrService ccOutboundCdrService;
    @Autowired
    private ISysConfigService sysConfigService;
    @Autowired
    private ICcAsrLanguagesService asrLanguagesService;

    /**
     * 获取外呼网关列表接口
     * @param req
     * @return
     */
    @GetMapping("/gateway/list")
    @ResponseBody
    public AjaxResult getGatewayList(HttpServletRequest req, @RequestParam(value = "purposes", required = false) String purposes){
        // 获取外呼网关列表
        Map<String, Object> params = new HashMap<>();
        if (StringUtils.isBlank(purposes)) {
            params.put("purposes", Arrays.asList(1,2,3));
        } else {
            params.put("purposes", Arrays.asList(purposes.split(",")));
        }
        List<CcGateways> list = ccGatewaysService.selectCcGatewaysList(new CcGateways().setParams(params));
        List<ApiGatewaysModel> result = new ArrayList<>();
        for (CcGateways data: list) {
            ApiGatewaysModel model = new ApiGatewaysModel();
            BeanUtils.copyProperties(data, model);
            result.add(model);
        }
        return AjaxResult.success(result);
    }

    /**
     * 获取大模型列表接口
     * @param req
     * @return
     */
    @GetMapping("/llmacount/list")
    @ResponseBody
    public AjaxResult getLlmAcountList(HttpServletRequest req){
        // 获取大模型列表
        List<CcLlmAgentAccount> list = ccLlmAgentAccountService.selectCcLlmAgentAccountList(new CcLlmAgentAccount());
        return AjaxResult.success(list);
    }

    /**
     * 获取音色列表
     * @return
     */
    @GetMapping("/voicecode/list")
    @ResponseBody
    public AjaxResult getVoiceCodeList(HttpServletRequest req)
    {
        // 获取音色列表
        List<CcTtsAliyun> list = ccTtsAliyunService.selectCcTtsAliyunList(new CcTtsAliyun());
        JSONArray result = new JSONArray();
        for (CcTtsAliyun ttsAliyun: list) {
            JSONObject obj = new JSONObject();
            obj.put("voiceName", ttsAliyun.getVoiceName());
            obj.put("voiceCode", ttsAliyun.getVoiceCode());
            obj.put("voiceSource", ttsAliyun.getVoiceSource());
            obj.put("ttsModels", ttsAliyun.getTtsModels());
            result.add(obj);
        }
        return AjaxResult.success(result);
    }


    /**
     * 获取技能组列表
     * @param req
     * @return
     */
    @GetMapping("/busigroup/list")
    @ResponseBody
    public AjaxResult getBusigroupList(HttpServletRequest req){
        // 获取技能组列表
        List<CcBizGroup> list = ccBizGroupService.selectCcBizGroupList(new CcBizGroup());
        return AjaxResult.success(list);
    }

    /**
     * Login page gateway list: only purposes 1(phonebar) and 3(unlimited), exclude 2(outbound tasks)
     */
    @GetMapping("/gateway/phonebarList")
    @ResponseBody
    public AjaxResult getPhonebarGatewayList() {
        Map<String, Object> params = new HashMap<>();
        params.put("purposes", Arrays.asList(1, 3));
        List<CcGateways> list = ccGatewaysService.selectCcGatewaysList(new CcGateways().setParams(params));
        // defense: never return purpose=2 (outbound tasks)
        list = list.stream()
                .filter(g -> g.getPurpose() != null && (g.getPurpose() == 1 || g.getPurpose() == 3))
                .collect(Collectors.toList());
        return AjaxResult.success(list);
    }

    /**
     * 通话记录查询接口（支持按时间、坐席、号码、呼入/呼出类型筛选）
     * @param req
     * @param queryParams
     * @return
     */
    @PostMapping("/calltask/list")
    @ResponseBody
    public TableDataInfo getCallTaskList(HttpServletRequest req, @RequestBody ApiCallTaskQueryParams queryParams)
    {
        TableDataInfo tableDataInfo;
        // 处理分页
        if (null == queryParams.getPageNum()) {
            queryParams.setPageNum(1);
        }
        if (null == queryParams.getPageSize()) {
            queryParams.setPageSize(20);
        }
        // 校验参数
        if (StringUtils.isNotEmpty(queryParams.getCreateTimeStart())
                && !DateValidatorUtils.isYmdHms(queryParams.getCreateTimeStart())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("createTimeStart格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }
        if (StringUtils.isNotEmpty(queryParams.getCreateTimeEnd())
                && !DateValidatorUtils.isYmdHms(queryParams.getCreateTimeEnd())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.ERROR.value());
            tableDataInfo.setMsg("createTimeEnd格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("createTimeStart", queryParams.getCreateTimeStart());
        params.put("createTimeEnd", queryParams.getCreateTimeEnd());

        // 分页请求数据
        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        CcCallTask ccCallTask = new CcCallTask();
        ccCallTask.setBatchId(queryParams.getBatchId());
        ccCallTask.setBatchName(queryParams.getBatchName());
        ccCallTask.setParams(params);
        List<CcCallTask> list = ccCallTaskService.selectCcCallTaskList(ccCallTask);
        tableDataInfo = getDataTable(list);
        List<CcCallTask> records = (List<CcCallTask>) tableDataInfo.getRows();
        if (records != null && !records.isEmpty()) {
            List<Long> batchIds = new ArrayList<>(records.size());
            for (CcCallTask data : records) {
                batchIds.add(data.getBatchId());
            }
            Map<Long, CallTaskStatModel> statMap = ccCallPhoneService.statByBatchIds(batchIds);
            for (CcCallTask data: records){
                CallTaskStatModel statModel = statMap.get(data.getBatchId());
                if (statModel == null) {
                    statModel = new CallTaskStatModel();
                }
                data.setPhoneCount(statModel.getPhoneCount());
                data.setCallCount(statModel.getCallCount());
                data.setNoCallCount(statModel.getPhoneCount() - statModel.getCallCount());
                data.setConnectCount(statModel.getConnectCount());
                data.setNoConnectCount(statModel.getCallCount() - statModel.getConnectCount());
                if (data.getCallCount() > 0) {
                    data.setRealConnectRate(data.getConnectCount()*1.0/data.getCallCount());
                } else {
                    data.setRealConnectRate(0.0);
                }
            }
        }
        tableDataInfo.setRows(records);
        return tableDataInfo;
    }

    /**
     * 根据uuid查询
     * @param req
     * @param uuid
     * @param callType
     * @return
     */
    @GetMapping("/record/uuid")
    @ResponseBody
    public AjaxResult getRecordByUuid(HttpServletRequest req, @RequestParam String uuid, @RequestParam String callType)
    {
        if (StringUtils.isBlank(callType)) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "callType不能为空！", "");

        }
        // 01:呼入， 02:AI外呼， 03:人工外呼
        ApiCallRecordQueryParams queryParams = new ApiCallRecordQueryParams().setUuid(uuid).setCallType(callType);
        TableDataInfo tableDataInfo = new TableDataInfo();
        if ("01".equals(callType)) {
            tableDataInfo = getInboundRecords(queryParams);
        } else if ("02".equals(callType)) {
            tableDataInfo = getAiCallRecords(queryParams);
        } else if ("03".equals(callType)) {
            tableDataInfo = getOutboundRecords(queryParams);
        }
        if (null != tableDataInfo.getRows() && tableDataInfo.getRows().size() > 0) {
            return AjaxResult.success(tableDataInfo.getRows().get(0));
        } else {
            return AjaxResult.success();
        }
    }

    /**
     * 通话记录查询接口（支持按时间、坐席、号码、呼入/呼出类型筛选）
     * @param req
     * @param queryParams
     * @return
     */
    @PostMapping("/records/list")
    @ResponseBody
    public TableDataInfo getRecordsList(HttpServletRequest req, @RequestBody ApiCallRecordQueryParams queryParams)
    {
        TableDataInfo tableDataInfo;
        // 分页参数处理
        if (null == queryParams.getPageNum()
                && null == queryParams.getPageSize()) {
            queryParams.setPageNum(1);
            queryParams.setPageSize(200000);
        }
        if (null == queryParams.getPageNum()) {
            queryParams.setPageNum(1);
        }
        if (null == queryParams.getPageSize()) {
            queryParams.setPageSize(20);
        }
        // 类型（01:呼入， 02:AI外呼， 03:人工外呼）
        String callType = queryParams.getCallType();
        if (StringUtils.isBlank(callType)) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("callType不能为空!");
            return tableDataInfo;
        }
        // 校验参数
        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeStart())
                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeStart())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("calloutTimeStart格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }
        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeEnd())
                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeEnd())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("calloutTimeStart格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }

        // 01:呼入， 02:AI外呼， 03:人工外呼
        if ("01".equals(callType)) {
            return getInboundRecords(queryParams);
        } else if ("02".equals(callType)) {
            return getAiCallRecords(queryParams);
        } else if ("03".equals(callType)) {
            return getOutboundRecords(queryParams);
        } else {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("callType参数不合法，呼入请输入01，AI外呼请输入02，手工外呼请输入03!");
            return tableDataInfo;
        }
    }

    /**
     * 创建外呼任务接口（含任务名称、优先级、并发数、TTS/音频模板等参数）接口
     * @param req
     * @param apiCallTaskModel
     * @return
     */
    @PostMapping("/ai/createTask")
    @ResponseBody
    public AjaxResult createCallTask(HttpServletRequest req, @RequestBody ApiCallTaskModel apiCallTaskModel) {
        CcCallTask ccCallTask = new CcCallTask();
        // 校验参数
        // 任务名称不能为空
        if (StringUtils.isBlank(apiCallTaskModel.getBatchName())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "batchName不能为空！", "");
        }
        // threadNum不能为空
        if (null == apiCallTaskModel.getThreadNum()) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "threadNum不能为空！", "");
        }
        // 校验groupId是否存在
        if (!checkGroupId(apiCallTaskModel.getGroupId())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "groupId参数不合法，请输入正确的groupId！", "");
        }
        // 校验taskType是否合法
        if (null == apiCallTaskModel.getTaskType()) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "taskType不能为空！", "");
        } else {
            if (apiCallTaskModel.getTaskType() != 1
                    && apiCallTaskModel.getTaskType() != 2) {
                return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "taskType参数不合法，AI外呼请输入1，通知提醒请输入2！", "");
            }
        }
        // 校验gatewayId是否合法
        if (!checkGatewayId(apiCallTaskModel.getGatewayId())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "gatewayId参数不合法，请输入正确的gatewayId！", "");
        }
        // AI外呼需要校验llmAccountId是否合法
        if (apiCallTaskModel.getTaskType() == 2) {
            if (!checkLlmAccountId(apiCallTaskModel.getLlmAccountId())) {
                return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "llmAccountId参数不合法，请输入正确的llmAccountId！", "");
            }
        }
        // playTimes 如果为空，默认值给1
        if (apiCallTaskModel.getTaskType() == 3) {
            if (null == apiCallTaskModel.getPlayTimes()) {
                apiCallTaskModel.setPlayTimes(1);
            }
        }
        // 校验voiceCode、voiceSource是否合法
        if (!checkVoiceCode(apiCallTaskModel.getVoiceCode(), apiCallTaskModel.getVoiceSource())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "voiceCode或voiceSource参数不合法，请输入正确的voiceCode和voiceSource！", "");
        }
        // 创建任务
        BeanUtils.copyProperties(apiCallTaskModel, ccCallTask, "batchId");
        if ("acd".equals(ccCallTask.getAiTransferType())) {
            ccCallTask.setAiTransferData(ccCallTask.getAiTransferGroupId());
        } else if ("extension".equals(ccCallTask.getAiTransferType())) {
            ccCallTask.setAiTransferData(ccCallTask.getAiTransferExtNumber());
        } else if ("gateway".equals(ccCallTask.getAiTransferType())) {
            JSONObject aiTransferData = new JSONObject();
            aiTransferData.put("gatewayId", ccCallTask.getAiTransferGatewayId());
            aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
            ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
        }
        if (null != ccCallTask.getConntectRate() && ccCallTask.getConntectRate() > 0) {
            ccCallTask.setRate(ccCallTask.getConntectRate()/100.0);
        }
        ccCallTask.setCreatetime(System.currentTimeMillis());
        if (StringUtils.isBlank(ccCallTask.getAsrLanguageCode())) {
            ccCallTask.setAsrLanguageCode("zh-CN");
        }
        if (StringUtils.isBlank(ccCallTask.getTtsLanguageCode())) {
            ccCallTask.setTtsLanguageCode("zh-CN");
        }
        if (StringUtils.isBlank(ccCallTask.getAsrModels())) {
            ccCallTask.setAsrProvider("ali");
            ccCallTask.setAsrModels("fun-asr-flash-8k-realtime");
        }
        if (StringUtils.isBlank(ccCallTask.getTtsModels())) {
            ccCallTask.setTtsModels("");
        }
        ccCallTaskService.insertCcCallTask(ccCallTask);
        apiCallTaskModel.setBatchId(ccCallTask.getBatchId());
        return AjaxResult.success("success", apiCallTaskModel);
    }

    /**
     * 启动任务接口
     * @param req
     * @param batchId
     * @return
     */
    @GetMapping("/ai/startTask")
    @ResponseBody
    public AjaxResult startTask(HttpServletRequest req, @RequestParam("batchId") Long batchId) {
        // 启动任务
        CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
        if (null == ccCallTask) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入正确的batchId", "");
        }
        ccCallTask.setIfcall(1);
        ccCallTask.setExecuting(0L);
        ccCallTask.setStopTime(0L);
        ccCallTaskService.updateCcCallTask(ccCallTask);
        return AjaxResult.success();
    }

    /**
     * 停止任务接口
     * @param req
     * @param batchId
     * @return
     */
    @GetMapping( "/ai/stopTask")
    @ResponseBody
    public AjaxResult stopTask(HttpServletRequest req, @RequestParam("batchId") Long batchId)
    {
        // 停止任务
        CcCallTask ccCallTask = ccCallTaskService.selectCcCallTaskByBatchId(batchId);
        if (null == ccCallTask) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入正确的batchId", "");
        }
        ccCallTask.setIfcall(0);
        ccCallTask.setExecuting(0L);
        ccCallTask.setStopTime(System.currentTimeMillis());
        ccCallTaskService.updateCcCallTask(ccCallTask);
        return AjaxResult.success();
    }

    /**
     * 追加名单（不自动启动任务）
     * @param req
     * @param aiCallListModel
     * @return
     */
    @PostMapping("/ai/addCallList")
    @ResponseBody
    public AjaxResult addAiCallList(HttpServletRequest req, @RequestBody AiCallListModel aiCallListModel) {
        Long batchId = aiCallListModel.getBatchId();
        if (null == batchId) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不能为空", "");
        }
        if (CollectionUtils.isEmpty(aiCallListModel.getPhoneList())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数phoneList不能为空", "");
        }
        // 获取任务
        CcCallTask ccCallTask = callTaskService.selectCcCallTaskByBatchId(batchId);
        if (null == ccCallTask) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入正确的batchId", "");
        }
        if (ccCallTask.getTaskType() != 1) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入AI外呼任务（taskType为1）的batchId", "");
        }
        // 追加名单
        Integer successCount = 0;
        List<CcCallPhone> callPhoneList = new ArrayList<>();
        for (String phoneNum: aiCallListModel.getPhoneList()) {
            if (StringUtils.isBlank(phoneNum)) {
                continue;
            }
            JSONObject bizJson = new JSONObject();
            CcCallPhone callPhone = buildCcCallPhone(ccCallTask, phoneNum, bizJson);
            callPhoneList.add(callPhone);
            successCount ++;
            if (callPhoneList.size() >= 200) {
                ccCallPhoneService.batchInsertCcCallPhone(callPhoneList);
                callPhoneList = new ArrayList<>();
            }
        }
        if (callPhoneList.size() > 0) {
            ccCallPhoneService.batchInsertCcCallPhone(callPhoneList);
        }

        log.info("成功追加" + successCount + "个名单");
        return AjaxResult.success("成功追加" + successCount + "个名单");
    }


    /**
     * 追加外呼名单（不自动启动任务）
     * @param req
     * @param commonCallListModel
     * @return
     */
    @PostMapping("/common/addCallList")
    @ResponseBody
    public AjaxResult addCommonCallList(HttpServletRequest req, @RequestBody CommonCallListModel commonCallListModel) {
        Long batchId = commonCallListModel.getBatchId();
        if (null == batchId) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不能为空", "");
        }
        if (CollectionUtils.isEmpty(commonCallListModel.getPhoneList())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数phoneList不能为空", "");
        }
        // 获取任务
        CcCallTask ccCallTask = callTaskService.selectCcCallTaskByBatchId(batchId);
        if (null == ccCallTask) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入正确的batchId", "");
        }
        // 通知类的通知内容必填
        if (ccCallTask.getTaskType() == 2) {
            for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
                if (StringUtils.isBlank(commonPhoneModel.getNoticeContent())) {
                    return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "号码" + commonPhoneModel.getPhoneNum() + "的noticeContent不能为空！", "");
                }
            }
        }

        // 追加名单
        Integer successCount = 0;
        List<CcCallPhone> callPhoneList = new ArrayList<>();
        for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
            String phoneNum = commonPhoneModel.getPhoneNum();
            if (StringUtils.isBlank(phoneNum)) {
                continue;
            }
            CcCallPhone callPhone = buildCcCallPhone(ccCallTask, phoneNum, commonPhoneModel.getBizJson());
            callPhone.setTtsText(commonPhoneModel.getNoticeContent());
            callPhoneList.add(callPhone);
            successCount ++;
            if (callPhoneList.size() >= 200) {
                ccCallPhoneService.batchInsertCcCallPhone(callPhoneList);
                callPhoneList = new ArrayList<>();
            }
        }
        if (callPhoneList.size() > 0) {
            ccCallPhoneService.batchInsertCcCallPhone(callPhoneList);
        }
        log.info("成功追加" + successCount + "个名单");
        return AjaxResult.success("成功追加" + successCount + "个名单",callPhoneList);
    }


    /**
     * yangqiang定制的追加通知的名单
     * @param req
     * @param noticeCallModel
     * @return
     */
    @PostMapping("/notice/call")
    @ResponseBody
    public AjaxResult callNotice(HttpServletRequest req, @RequestBody NoticeCallModel noticeCallModel) {
        // 获取任务
        String batchName = paramsService.getParamValueByCode("testNoticeCallTaskName", "test");
        String phoneNum = paramsService.getParamValueByCode("testNoticeCallPhoneNum", "13908113506");
        CcCallTask ccCallTask = callTaskService.selectCcCallTaskByBatchName(batchName, 2);

        // 追加名单
        JSONObject bizJson = new JSONObject();
        CcCallPhone callPhone = buildCcCallPhone(ccCallTask, phoneNum, bizJson);
        callPhone.setTtsText(noticeCallModel.getNoticeContent());
        ccCallPhoneService.insertCcCallPhone(callPhone);

        // 启动任务
        ccCallTask.setIfcall(1);
        ccCallTask.setExecuting(0L);
        ccCallTask.setStopTime(0L);
        callTaskService.updateCcCallTask(ccCallTask);

        return AjaxResult.success();
    }

    private CcCallPhone buildCcCallPhone(CcCallTask ccCallTask, String phoneNum, JSONObject bizJson) {
        CcCallPhone callPhone = new CcCallPhone();
        callPhone.setId(UuidGenerator.GetOneUuid());
        callPhone.setGroupId("1");
        callPhone.setBatchId(ccCallTask.getBatchId());
        callPhone.setCreatetime(new Date().getTime());
        callPhone.setCallstatus(0);
        callPhone.setCalloutTime(0L);
        callPhone.setCallcount(0);
        callPhone.setCallEndTime(0L);
        callPhone.setTimeLen(0L);
        callPhone.setValidTimeLen(0L);
        callPhone.setUuid("");
        callPhone.setConnectedTime(0L);
        callPhone.setHangupCause("");
        callPhone.setAnsweredTime(0L);
        callPhone.setDialogue("");
        callPhone.setWavfile("");
        callPhone.setRecordServerUrl("");
        callPhone.setDialogueCount(0L);
        callPhone.setAcdOpnum("");
        callPhone.setAcdQueueTime(0L);
        callPhone.setAcdWaitTime(0);
        callPhone.setTelephone(phoneNum);
        if (phoneNum.length() > 4) {
            bizJson.put("tailNum", phoneNum.substring(phoneNum.length()-4));
        } else {
            bizJson.put("tailNum", phoneNum);
        }
        bizJson.put("phoneNum", phoneNum);
        callPhone.setCustName(bizJson.getString("custName"));
        if (null == callPhone.getCustName()) {
            callPhone.setCustName("");
        }
        callPhone.setBizJson(JSONObject.toJSONString(bizJson));
        if (ccCallTask.getTaskType() == 1) {
            callPhone.setIntent("");
        } else {
            callPhone.setIntent("-");
        }
        return callPhone;
    }

    private void insertCallPhoneBatch(List<CcCallPhone> batchList, List<CcCallPhone> successList) {
        if (batchList.isEmpty()) {
            return;
        }
        try {
            ccCallPhoneService.batchInsertCcCallPhone(batchList);
            successList.addAll(batchList);
        } catch (Exception e) {
            log.error("批次插入名单失败，本批{}条，继续处理下一批", batchList.size(), e);
        } finally {
            batchList.clear();
        }
    }



    private TableDataInfo getOutboundRecords(ApiCallRecordQueryParams queryParams) {

        Map<String, Object> params = new HashMap<>();
        params.put("inboundTimeStart", queryParams.getCalloutTimeStart());
        params.put("inboundTimeEnd", queryParams.getCalloutTimeEnd());
        if (null != queryParams.getTimeLenStart()) {
            params.put("timeLenSecondStart", queryParams.getTimeLenStart().toString());
        }
        if (null != queryParams.getTimeLenEnd()) {
            params.put("timeLenSecondEnd", queryParams.getTimeLenEnd().toString());
        }

        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        CcOutboundCdr outboundCdr = new CcOutboundCdr();
        outboundCdr.setUuid(queryParams.getUuid());
        outboundCdr.setCaller(queryParams.getTelephone());
        outboundCdr.setOpnum(queryParams.getExtnum());
        outboundCdr.setParams(params);
        List<CcOutboundCdr> list = outboundCdrService.selectCcOutboundCdrList(outboundCdr);
        TableDataInfo tableData = getDataTable(list);
        List<CcOutboundCdr> records = (List<CcOutboundCdr>) tableData.getRows();
        List<ApiCallRecordQueryResult> apiRecords = new ArrayList<>();
        for (CcOutboundCdr data: records) {
            ApiCallRecordQueryResult apiData = new ApiCallRecordQueryResult();
            if (data.getRecordFilename().startsWith("/")) {
                data.setRecordFilename(data.getRecordFilename().substring(1));
            }
            data.setWavFileUrl("/recordings/files?filename=" + data.getRecordFilename());
            apiData.setUuid(data.getUuid());
            apiData.setTelephone(CommonUtils.maskPhoneNumber(data.getCallee()));
            apiData.setCalloutTime(DateUtils.format(new Date(data.getStartTime()), "yyyy-MM-dd HH:mm:ss"));
            if (data.getAnsweredTime() > 0) {
                apiData.setAnsweredTime(DateUtils.format(new Date(data.getAnsweredTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setAnsweredTime("");
            }
            if (data.getEndTime() > 0) {
                apiData.setCallEndTime(DateUtils.format(new Date(data.getEndTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setCallEndTime("");
            }
            apiData.setHangupCause(data.getHangupCause());
            if (data.getTimeLen() > 0) {
                apiData.setWavFileUrl(data.getWavFileUrl());
            } else {
                apiData.setWavFileUrl("");
            }
            apiData.setDialogue(new JSONArray());
            apiData.setTimeLen(Long.valueOf(data.getTimeLen()/1000).intValue());
            apiRecords.add(apiData);
        }
        tableData.setRows(records);
        TableDataInfo tableDataInfo = new TableDataInfo();
        tableDataInfo.setCode(tableData.getCode());
        tableDataInfo.setRows(apiRecords);
        tableDataInfo.setTotal(tableData.getTotal());
        tableDataInfo.setMsg(tableData.getMsg());
        return tableDataInfo;

    }

    private TableDataInfo getInboundRecords(ApiCallRecordQueryParams queryParams) {

        Map<String, Object> params = new HashMap<>();
        params.put("inboundTimeStart", queryParams.getCalloutTimeStart());
        params.put("inboundTimeEnd", queryParams.getCalloutTimeEnd());
        if (null != queryParams.getTimeLenStart()) {
            params.put("timeLenSecondStart", queryParams.getTimeLenStart().toString());
        }
        if (null != queryParams.getTimeLenEnd()) {
            params.put("timeLenSecondEnd", queryParams.getTimeLenEnd().toString());
        }

        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        CcInboundCdr inboundCdr = new CcInboundCdr();
        inboundCdr.setUuid(queryParams.getUuid());
        inboundCdr.setCaller(queryParams.getTelephone());
        inboundCdr.setOpnum(queryParams.getExtnum());
        inboundCdr.setParams(params);
        List<CcInboundCdr> list = inboundCdrService.selectCcInboundCdrList(inboundCdr);
        TableDataInfo tableData = getDataTable(list);
        List<CcInboundCdr> records = (List<CcInboundCdr>) tableData.getRows();
        List<ApiCallRecordQueryResult> apiRecords = new ArrayList<>();
        for (CcInboundCdr data: records) {
            ApiCallRecordQueryResult apiData = new ApiCallRecordQueryResult();
            if (data.getWavFile().startsWith("/")) {
                data.setWavFile(data.getWavFile().substring(1));
            }
            data.setWavFileUrl("/recordings/files?filename=" + data.getWavFile());
            apiData.setUuid(data.getUuid());
            apiData.setTelephone(CommonUtils.maskPhoneNumber(data.getCaller()));
            apiData.setCalloutTime(DateUtils.format(new Date(data.getInboundTime()), "yyyy-MM-dd HH:mm:ss"));
            if (data.getAnsweredTime() > 0) {
                apiData.setAnsweredTime(DateUtils.format(new Date(data.getAnsweredTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setAnsweredTime("");
            }
            if (data.getHangupTime() > 0) {
                apiData.setCallEndTime(DateUtils.format(new Date(data.getHangupTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setCallEndTime("");
            }
            apiData.setHangupCause("");
            if (data.getTimeLen() > 0) {
                apiData.setWavFileUrl(data.getWavFileUrl());
            } else {
                apiData.setWavFileUrl("");
            }
            if (StringUtils.isNotEmpty(data.getChatContent())) {
                JSONArray dialogue = new JSONArray();
                JSONArray.parseArray(data.getChatContent()).forEach(obj -> {
                    JSONObject json = (JSONObject) obj;
                    if ("assistant".equals(json.getString("role"))
                            || "user".equals(json.getString("role"))) {
                        JSONObject content = new JSONObject();
                        content.put("role", json.getString("role"));
                        content.put("content", json.getString("content"));
                        dialogue.add(content);
                    }
                });
                apiData.setDialogue(dialogue);
            } else {
                apiData.setDialogue(new JSONArray());
            }
            apiData.setTimeLen(Long.valueOf(data.getTimeLen()/1000).intValue());
            apiData.setManualAnsweredTime(data.getManualAnsweredTime());
            apiData.setManualAnsweredTimeLen(data.getManualAnsweredTimeLen());
            apiRecords.add(apiData);
        }
        tableData.setRows(records);
        TableDataInfo tableDataInfo = new TableDataInfo();
        tableDataInfo.setCode(tableData.getCode());
        tableDataInfo.setRows(apiRecords);
        tableDataInfo.setTotal(tableData.getTotal());
        tableDataInfo.setMsg(tableData.getMsg());
        return tableDataInfo;

    }

    private TableDataInfo getAiCallRecords(ApiCallRecordQueryParams queryParams) {

        Map<String, Object> params = new HashMap<>();
        params.put("calloutTimeStart", queryParams.getCalloutTimeStart());
        params.put("calloutTimeEnd", queryParams.getCalloutTimeEnd());
        if (null != queryParams.getTimeLenStart()) {
            params.put("timeLenSecondStart", queryParams.getTimeLenStart().toString());
        }
        if (null != queryParams.getTimeLenEnd()) {
            params.put("timeLenSecondEnd", queryParams.getTimeLenEnd().toString());
        }

        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        CcCallPhone ccCallPhone = new CcCallPhone();
        if (null != queryParams.getBatchId() && queryParams.getBatchId() > 0) {
            ccCallPhone.setBatchId(queryParams.getBatchId());
        }
        ccCallPhone.setUuid(queryParams.getUuid());
        ccCallPhone.setTelephone(queryParams.getTelephone());
        ccCallPhone.setAcdOpnum(queryParams.getExtnum());
        ccCallPhone.setCallstatus(queryParams.getCallstatus());
        ccCallPhone.setCallerNumber(queryParams.getCallerNumber());
        ccCallPhone.setParams(params);
        List<CcCallPhone> list = ccCallPhoneService.selectCcCallPhoneList(ccCallPhone);
        TableDataInfo tableData = getDataTable(list);
        List<CcCallPhone> records = (List<CcCallPhone>) tableData.getRows();
        List<ApiCallRecordQueryResult> apiRecords = new ArrayList<>();
        for (CcCallPhone data: records) {
            ApiCallRecordQueryResult apiData = new ApiCallRecordQueryResult();
            if (data.getWavfile().startsWith("/")) {
                data.setWavfile(data.getWavfile().substring(1));
            }
            data.setWavfile("/recordings/files?filename=" + data.getWavfile());
            apiData.setUuid(data.getUuid());
            apiData.setTelephone(CommonUtils.maskPhoneNumber(data.getTelephone()));
            apiData.setCalloutTime(DateUtils.format(new Date(data.getCalloutTime()), "yyyy-MM-dd HH:mm:ss"));
            if (data.getAnsweredTime() > 0) {
                apiData.setAnsweredTime(DateUtils.format(new Date(data.getAnsweredTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setAnsweredTime("");
            }
            if (data.getCallEndTime() > 0) {
                apiData.setCallEndTime(DateUtils.format(new Date(data.getCallEndTime()), "yyyy-MM-dd HH:mm:ss"));
            } else {
                apiData.setCallEndTime("");
            }
            apiData.setHangupCause(data.getHangupCause());
            if (data.getTimeLen() > 0) {
                apiData.setWavFileUrl(data.getWavfile());
            } else {
                apiData.setWavFileUrl("");
            }
            if (StringUtils.isNotEmpty(data.getDialogue())) {
                JSONArray dialogue = new JSONArray();
                JSONArray.parseArray(data.getDialogue()).forEach(obj -> {
                    JSONObject json = (JSONObject) obj;
                    if ("assistant".equals(json.getString("role"))
                            || "user".equals(json.getString("role"))) {
                        JSONObject content = new JSONObject();
                        content.put("role", json.getString("role"));
                        content.put("content", json.getString("content"));
                        dialogue.add(content);
                    }
                });
                apiData.setDialogue(dialogue);
            } else {
                apiData.setDialogue(new JSONArray());
            }
            apiData.setTimeLen(Long.valueOf(data.getTimeLen()/1000).intValue());
            apiData.setSessionId(data.getId());
            apiData.setCallstatus(data.getCallstatus());
            apiData.setCallerNumber(CommonUtils.maskPhoneNumber(data.getCallerNumber()));
            apiData.setManualAnsweredTime(data.getManualAnsweredTime());
            apiData.setManualAnsweredTimeLen(data.getManualAnsweredTimeLen());
            apiRecords.add(apiData);
        }
        tableData.setRows(records);
        TableDataInfo tableDataInfo = new TableDataInfo();
        tableDataInfo.setCode(tableData.getCode());
        tableDataInfo.setRows(apiRecords);
        tableDataInfo.setTotal(tableData.getTotal());
        tableDataInfo.setMsg(tableData.getMsg());
        return tableDataInfo;
    }


    private boolean checkGroupId(String groupId) {
        // 可以为空
        if (StringUtils.isBlank(groupId)) {
            return true;
        }
        // 如果传值了，必须是存在的值
        CcBizGroup ccBizGroup = ccBizGroupService.selectCcBizGroupByGroupId(groupId);
        if (null == ccBizGroup) {
            return false;
        }
        return true;
    }

    private boolean checkGatewayId(Long gatewayId) {
        // 不可以为空
        if (null == gatewayId) {
            return false;
        }
        // 必须是存在的网关，且用途是外呼或者不限制
        CcGateways ccGateways = ccGatewaysService.selectCcGatewaysById(gatewayId);
        if (null == ccGateways) {
            return false;
        }
        // 网关用途 0 dropped; 1 phonebar; 2 outbound tasks; 3. Unlimited
        if (ccGateways.getPurpose() != 2
                && ccGateways.getPurpose() != 3) {
            return false;
        }
        return true;
    }

    private boolean checkLlmAccountId(Integer llmAccountId) {
        // 不能为空
        if (null == llmAccountId) {
            return false;
        }
        // 必须存在
        CcLlmAgentAccount ccLlmAgentAccount = ccLlmAgentAccountService.selectCcLlmAgentAccountById(llmAccountId);
        if (null == ccLlmAgentAccount) {
            return false;
        }
        return true;
    }

    private boolean checkVoiceCode(String voiceCode, String voiceSource) {
        // 不能为空
        if (StringUtils.isBlank(voiceCode) || StringUtils.isBlank(voiceSource)) {
            return false;
        }
        // voiceSource仅支持aliyun_tts、aliyun_tts_flow、doubao_vcl_tts、xf_tts 和 tx_tts1
        if (!"aliyun_tts".equals(voiceSource)
                && !"aliyun_tts_flow".equals(voiceSource)
                && !"doubao_vcl_tts".equals(voiceSource)
                && !"xf_tts".equals(voiceSource)
                && !"tx_tts1".equals(voiceSource)
                && !"vt_tts".equals(voiceSource)) {
            return false;
        }
        // voiceCode必须是存在的
        CcTtsAliyun ccTtsAliyun = ccTtsAliyunService.selectCcTtsAliyunByVoiceCode(voiceCode);
        if (null == ccTtsAliyun) {
            return false;
        }
        return true;
    }





    /**
     * 行稳数智追加名单【不自动启动任务，需要在任务管理页面手动启动任务】
     * @param req
     * @param localCallModel
     * @return
     */
    @PostMapping("/local/addCall")
    @ResponseBody
    public AjaxResult addLocalCall(HttpServletRequest req, @RequestBody LocalCallModel localCallModel) {
        // 获取任务
        String batchName = localCallModel.getBatchName();
        if (StringUtils.isEmpty(batchName)) {
            batchName = paramsService.getParamValueByCode("testNoticeCallTaskName", "test");
        }
        CcCallTask ccCallTask = callTaskService.selectCcCallTaskByBatchName(batchName, 1);

        log.info("接收到名单localCallModel：{}", JSONObject.toJSONString(localCallModel));
        log.info("batchName:{}", batchName);
        log.info("ccCallTask:{}", JSONObject.toJSONString(ccCallTask));

        // 追加名单
        String phoneNum = localCallModel.getPhone();
        JSONObject bizJson = new JSONObject();
        bizJson.put("caller", localCallModel.getCaller());
        bizJson.put("welcomeMessage", localCallModel.getWelcomeMessage());
        bizJson.put("questionChainId", localCallModel.getQuestionChainId());
        CcCallPhone callPhone = buildCcCallPhone(ccCallTask, phoneNum, bizJson);
        ccCallPhoneService.insertCcCallPhone(callPhone);

        // 暂时关闭自动启动逻辑（后续改成自动启停逻辑）
//        // 如果停止超过5分钟，则自动启动
//        if (ccCallTask.getIfcall() == 0
//                && (System.currentTimeMillis() - ccCallTask.getStopTime()) >= 5*60*1000L) {
//            ccCallTask.setIfcall(1);
//            ccCallTask.setExecuting(0L);
//            ccCallTask.setStopTime(0L);
//            callTaskService.updateCcCallTask(ccCallTask);
//        }

        JSONObject res = new JSONObject();
        res.put("sessionId", callPhone.getId().toString());
        log.info("名单处理完成:{}", JSONObject.toJSONString(callPhone));

        return AjaxResult.success(res);
    }

    /**
     * 自动绑定并获取分机信息
     * @param req
     * @param loginUser
     * @return
     */
    @GetMapping("/phoneBar/extnum/bind")
    @ResponseBody
    public AjaxResult getExtNumByUserName(HttpServletRequest req, @RequestParam String loginUser) {

        // 获取分机号
        CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByUserCode(loginUser);
        if (null != ccExtNum) {
            return AjaxResult.success("success", ccExtNum);
        }

        // 如果没有获取到分机，则自动绑定分机
        List<CcExtNum> ccExtNumList = ccExtNumService.selectUnBindCcExtNumList();
        if (CollectionUtils.isEmpty(ccExtNumList)) {
            log.error("没有可用分机，无法分配分机-:{}", loginUser);
            return AjaxResult.error("没有可用分机，无法分配分机");
        }
        ccExtNum = ccExtNumList.get(RandomUtils.nextInt(0, ccExtNumList.size()));
        ccExtNum.setUserCode(loginUser);
        ccExtNumService.updateCcExtNum(ccExtNum);
        return AjaxResult.success("success", ccExtNum);

    }

    /**
     * 获取电话工具条的网关列表
     * @param req
     * @param extNum
     * @return
     */
    @GetMapping("/phoneBar/params")
    @ResponseBody
    public AjaxResult getPhoneBaseParams(HttpServletRequest req, @RequestParam String extNum) {

        // 获取分机号
        CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByExtNum(Long.valueOf(extNum));

        String extnum = ccExtNum.getExtNum().toString();
        String opnum = ccExtNum.getUserCode();
        String groupId = "1";
        String skillLevel = "9";
        String projectId = "1";
        String loginToken = ccExtNumService.createToken(extnum, opnum, groupId, skillLevel, projectId);
        // 网关用途 0 dropped; 1 phonebar; 2 outbound tasks; 3. Unlimited
        Map<String, Object> params = new HashMap<>();
        params.put("purposes", Arrays.asList(1,3));
        List<CcGateways> gatewaysList = ccGatewaysService.selectCcGatewaysList(new CcGateways().setParams(params));
        List<JSONObject> gatewayList = new ArrayList<>();
        for (CcGateways ccGateways: gatewaysList) {
            JSONObject configGateway = new JSONObject();
            configGateway.put("uuid", ccGateways.getId().toString());
            configGateway.put("updateTime", ccGateways.getUpdateTime());
            configGateway.put("gatewayAddr", ccGateways.getGwAddr());
            configGateway.put("callerNumber", ccGateways.getCaller());
            configGateway.put("calleePrefix", ccGateways.getCalleePrefix());
            configGateway.put("callProfile", ccGateways.getProfileName());
            configGateway.put("priority", ccGateways.getPriority());
            configGateway.put("concurrency", ccGateways.getMaxConcurrency());
            configGateway.put("register", ccGateways.getRegister());
            configGateway.put("authUsername", ccGateways.getAuthUsername());
            configGateway.put("audioCodec", ccGateways.getCodec());
            gatewayList.add(configGateway);
        }
        JSONObject callConfig = new JSONObject();

        String scriptServer = ccParamsService.getParamValueByCode("call-center-server-ip-addr", "");
        String scriptPort = ccParamsService.getParamValueByCode("call-center-websocket-port", "");

        callConfig.put("scriptServer", scriptServer);
        callConfig.put("scriptPort", scriptPort);
        callConfig.put("loginToken", loginToken);
        callConfig.put("gatewayList", gatewayList);

        return AjaxResult.success(callConfig);

    }


    @PostMapping("/test/localChat")
    @ResponseBody
    public JSONObject localChat(HttpServletRequest request, @RequestBody JSONObject reqParams) {
//        {"code":200, "data":{"choices":[{"content":"xxxxxxx", "wavFilePath":"/home/Records/251224101457010001/20260113161253001914.wav"}]}}
        log.info("请求参数：{}", reqParams);
        Integer rounds = 0;
        for (int i = 0; i < reqParams.getJSONArray("messages").size(); i++) {
            if ("assistant".equals(reqParams.getJSONArray("messages").getJSONObject(i).getString("role"))) {
                rounds ++;
            }
        }
        String[] rspDatas = new String[]{
//                "您好，请问您是测试1本人是吗？| /home/Records/23/round-1.wav; ",
                "这里是中信银行委托方，这个电话号码是测试1在中信银行办理业务时登记的号码，您是测试1吗？ | /home/Records/23/round-2-1.wav;/home/Records/23/round-2-2.wav;/home/Records/23/round-2-3.wav",
                "这里是中信银行委托方，这边主要是通知您一下，您在中信银行卡尾号6388的信用卡已错过到期环款日，当期账单账面欠款总额9097元全国统一核账时间在今天下午5点需要您在此之前处理进来，可以吧？| /home/Records/23/round-3-1.wav;/home/Records/23/round-3-2.wav;",
                "您的账单现在已经过环款日了，能和我们说一下您是为什么还没处理欠款吗？ | /home/Records/23/round-4.wav",
                "好的，请您在今天下午5点之前至少处理您的最低还款额0元时间和资金都没问题，对吗？|/home/Records/23/round-5.wav",
                "银行稍后安排工作人员查账，建议您尽快处理欠款，不打扰您了，再见。hangupCall|/home/Records/23/round-6.wav"
        };
        JSONObject result = new JSONObject();
        result.put("code", 200);
        JSONObject data = new JSONObject();
        JSONArray choices = new JSONArray();
        JSONObject delta = new JSONObject();
        String[] rspData = rspDatas[rounds].split("\\|");
        delta.put("content", rspData[0].trim());
        delta.put("wavFilePath", rspData[1].trim());
        JSONObject choices0 = new JSONObject();
        choices0.put("delta", delta);
        choices.add(choices0);
        data.put("choices", choices);
        result.put("data", data);
        return result;
    }

    //=======================================================新增接口============================================================
    /**
     * 删除外呼任务
     */
    @PostMapping( "/removeTask")
    @ResponseBody
    @Transactional
    public AjaxResult removeTask(@RequestBody Map<String, Long[]> paramMap)
    {
        Long[] batchIds= paramMap.get("batchIds");
        if(batchIds==null){
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "batchIds不能为空！", "");
        }
        for (Long batchId : batchIds) {
            // 备份拨打记录数据
            ccCallPhoneService.bakCallPhoneByBatchId(batchId);
            // 删除拨打记录数据
            ccCallPhoneService.delCallPhoneByBatchId(batchId);
            // 备份任务数据
            ccCallTaskService.bakCallTaskByBatchId(batchId);
        }

        // 删除任务数据
        String batchIdsStr = Arrays.stream(batchIds)
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        return toAjax(ccCallTaskService.deleteCcCallTaskByBatchIds(batchIdsStr));
    }
    /**
     * 获取电话工具条的网关列表
     */
    @PostMapping("/myPhoneBar/params")
    @ResponseBody
    public AjaxResult getMyPhoneBaseParams(@RequestBody Map<String,String> param) {

        String extNum = param.get("extNum");
        String myGateway = param.get("myGateway");
        if(extNum == null){
            return AjaxResult.error("分机号参数缺失");
        }
        // 获取分机号
        CcExtNum ccExtNum = ccExtNumService.selectCcExtNumByExtNum(Long.valueOf(extNum));

        String extnum = ccExtNum.getExtNum().toString();
        String opnum = ccExtNum.getUserCode();
        String password = ccExtNum.getExtPass();
        String groupId = "1";
        String skillLevel = "9";
        String projectId = "1";
        //1.创建token
        String loginToken = ccExtNumService.createToken(extnum, opnum, groupId, skillLevel, projectId);
        //2.获取加密密码
        String encryptStr = DESUtil.encrypt(password + "," + DateUtils.format(DateUtils.addDays(new Date(), 1), "yyyyMMddHHmm"));
        encryptStr = String.format("var _phoneEncryptPassword='%s';", encryptStr);

        CcGateways ccGatewaysVo = new CcGateways();
        // 电话条仅允许 purposes 1/3，排除 2(外呼任务)
        Map<String, Object> phonebarPurposes = new HashMap<>();
        phonebarPurposes.put("purposes", Arrays.asList(1, 3));
        ccGatewaysVo.setParams(phonebarPurposes);
        //判断指定网关还是全部网关
        if(StringUtils.isNotBlank(myGateway)){
            List<Long> gatewayIds = Arrays.stream(myGateway.split(","))
                    .map(Long::parseLong)
                    .collect(Collectors.toList());
            ccGatewaysVo.setGatewayIds(gatewayIds);
        }else{
            String wgName = param.get("wgName");
            if(StringUtils.isNotBlank(wgName)){
                ccGatewaysVo.setGwName(wgName);
            }
        }
        //3.获取工具条网关列表
        List<CcGateways> gatewaysList = ccGatewaysService.selectCcGatewaysList(ccGatewaysVo);
        List<JSONObject> gatewayList = new ArrayList<>();
        for (CcGateways ccGateways: gatewaysList) {
            JSONObject configGateway = new JSONObject();
            configGateway.put("uuid", ccGateways.getId().toString());
            configGateway.put("updateTime", ccGateways.getUpdateTime());
            configGateway.put("gatewayAddr", ccGateways.getGwAddr());
            configGateway.put("callerNumber", ccGateways.getCaller());
            configGateway.put("calleePrefix", ccGateways.getCalleePrefix());
            configGateway.put("callProfile", ccGateways.getProfileName());
            configGateway.put("priority", ccGateways.getPriority());
            configGateway.put("concurrency", ccGateways.getMaxConcurrency());
            configGateway.put("register", ccGateways.getRegister());
            configGateway.put("authUsername", ccGateways.getAuthUsername());
            configGateway.put("audioCodec", ccGateways.getCodec());
            gatewayList.add(configGateway);
        }
        JSONObject callConfig = new JSONObject();

        String scriptServer = ccParamsService.getParamValueByCode("call-center-server-ip-addr", "");
        String scriptPort = ccParamsService.getParamValueByCode("call-center-websocket-port", "");

        callConfig.put("scriptServer", scriptServer);
        callConfig.put("scriptPort", scriptPort);
        callConfig.put("loginToken", loginToken);
        callConfig.put("encryptPsw", encryptStr);
        callConfig.put("gatewayList", gatewayList);
        //登录账号
        callConfig.put("opNum", opnum);
        //登录用户名称
        SysUser sysUser = userService.selectUserByLoginName(opnum);
        if(sysUser != null){
            callConfig.put("userName", sysUser.getUserName());
        }

        return AjaxResult.success(callConfig);

    }
    /**
     * 查询未绑定的分机管理列表
     */
    @GetMapping("/extnum/selectUnBindCcExtNumList")
    @ResponseBody
    public AjaxResult selectUnBindCcExtNumList()
    {
        return AjaxResult.success(ccExtNumService.selectUnBindCcExtNumList());
    }
    /**
     * 查询公司未绑定的分机管理列表
     */
    @PostMapping("/extnum/selectCompanyUnBindCcExtNumList")
    @ResponseBody
    public AjaxResult selectCompanyUnBindCcExtNumList(@RequestBody CcExtNum ccExtNum)
    {
        return AjaxResult.success(ccExtNumService.selectCcExtNumList(ccExtNum));
    }
    /**
     * 查询未绑定的分机管理列表分页
     */
    @PostMapping("/extnum/selectUnBindCcExtNumListPage")
    @ResponseBody
    public TableDataInfo selectUnBindCcExtNumListPage(@RequestBody CcExtNum ccExtNum)
    {
        Integer pageNum = ccExtNum.getPageNum() != null ? ccExtNum.getPageNum().intValue() : 1;
        Integer pageSize = ccExtNum.getPageSize() != null ? ccExtNum.getPageSize().intValue() : 10;
        startPage(pageNum, pageSize);
        List<CcExtNum> list = ccExtNumService.selectUnBindCcExtNumListPage(ccExtNum);
        return getDataTable(list);
    }
    /**
     * 通用查询分机列表分页
     */
    @PostMapping("/extnumPage")
    @ResponseBody
    public TableDataInfo extnumPage(@RequestBody CcExtNum ccExtNum)
    {
        Integer pageNum = ccExtNum.getPageNum() != null ? ccExtNum.getPageNum().intValue() : 1;
        Integer pageSize = ccExtNum.getPageSize() != null ? ccExtNum.getPageSize().intValue() : 10;
        startPage(pageNum, pageSize);
        List<CcExtNum> list = ccExtNumService.selectCcExtNumList(ccExtNum);
        if(!CollectionUtils.isEmpty(list)){
            list.forEach(extNum -> {
                if(StringUtils.isNotBlank(extNum.getUserCode()) && extNum.getUserCode().contains("_cp_")){
                    extNum.setUserCode("");
                }
            });
        }
        return getDataTable(list);
    }
    /**
     * 新增企业绑定未使用的分机（实际只是公司占位，需销售来绑定）
     */
    @PostMapping("/companyBindExtNum")
    @ResponseBody
    @Transactional
    public AjaxResult companyBindExtNum(@RequestBody Map<String,String> param)
    {
        String companyName = param.get("companyName");
        String sipExtNumIds = param.get("sipExtNumIds");
        if(StringUtils.isBlank(companyName) || StringUtils.isBlank(sipExtNumIds)){
            return AjaxResult.error("参数companyName或sipExtNumIds缺失");
        }
        //公司绑定分机
        List<Long> extNumList = Arrays.stream(sipExtNumIds.split(","))
                .map(String::trim)
                .filter(StringUtils::isNotBlank)
                .map(Long::parseLong)
                .collect(Collectors.toList());
        CcExtNum extNum = new CcExtNum();
        extNum.setUserCode(companyName);
        extNum.setSipExtNumList(extNumList);
        return toAjax(ccExtNumService.updateCompanyBindExtNum(extNum));
    }

    /**
     * 公司批量解绑分机,根据分机号
     */
    @PostMapping("/companyBatchUnbindSipExt")
    @ResponseBody
    public AjaxResult companyBatchUnbindSipExt(@RequestBody Set<Long> extNums)
    {
        if(extNums == null || extNums.isEmpty()){
            return AjaxResult.error("参数为空");
        }
        return toAjax(ccExtNumService.companyBatchUnbindSipExt(extNums));
    }


    /**
     * 新增用户且绑定未使用的分机返回用户
     */
    @PostMapping("/user/addUserOrBindExtNumReturnUser")
    @ResponseBody
    @Transactional
    public AjaxResult addUserOrBindExtNumReturnUser(@RequestBody SysUser user)
    {
        if(StringUtils.isBlank(user.getPassword())){
            throw new RuntimeException("密码不能为空");
        }

        // 校验分机号是否为空
        if (null == user.getExtNum()) {
            throw new RuntimeException("新增用户失败,分机号不能为空");
        }
        // 校验分机号是否存在
        CcExtNum extNum = ccExtNumService.selectCcExtNumByExtNum(user.getExtNum());
        if (null == extNum) {
            throw new RuntimeException("新增用户失败,分机号" + user.getExtNum() + "不存在");
        }

        user.setSalt(ShiroUtils.randomSalt());
        user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt()));
        user.setPwdUpdateDate(DateUtils.getNowDate());
        user.setCreateBy("ylrz");
        int i;
        SysUser sysUser = userService.selectUserByLoginName(user.getLoginName());
        if (sysUser != null)
        {
            //优化一下，如果账号存在就更新账号
            user.setUserId(sysUser.getUserId());
            i = userService.updateUser(user);
        }else{
            i = userService.insertUser(user);
        }

        if(i>0){
            //绑定分机
            extNum.setUserCode(user.getLoginName());
            int num = ccExtNumService.updateCcExtNum(extNum);
            if(num>0){
                user.setExtPass(extNum.getExtPass());
                return AjaxResult.success(user);
            } else {
                throw new RuntimeException("用户信息处理成功,但分机号绑定失败");
            }
        }
        throw new RuntimeException("新增用户和绑分机操作失败");
    }
    /**
     * 修改用户且绑分机
     */
    @PostMapping("/user/editUserOrUnBindExtNum")
    @ResponseBody
    @Transactional
    public AjaxResult editUserOrUnBindExtNum(@RequestBody SysUser user)
    {
        if (!userService.checkLoginNameUnique(user))
        {
            throw new RuntimeException("修改用户'" + user.getLoginName() + "'失败，登录账号已存在");
        }
        else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
        {
            throw new RuntimeException("修改用户'" + user.getLoginName() + "'失败，手机号码已存在");
        }
        user.setUpdateBy("ylrz");
        AuthorizationUtils.clearAllCachedAuthorizationInfo();
        int i = userService.updateUser(user);
        if(i>0){
            //这里可能不存在绑定信息
            if(StringUtils.isNotBlank(user.getUserSource()) && user.getUserSource().equals("0")){
                ccExtNumService.updateUserCodeByLoginName(user.getCompanyName(),user.getLoginName());
            }else{
                //先清除原分机绑定
                ccExtNumService.cleanCcExtNumByUserCode(user.getLoginName());
            }
            //修改绑定分机
            CcExtNum extNum = new CcExtNum();
            extNum.setExtNum(user.getExtNum());
            extNum.setUserCode(user.getLoginName());
            int updateNum = ccExtNumService.updateCcExtNumByUserCode(extNum);
            if(updateNum>0){
                CcExtNum num = ccExtNumService.selectCcExtNumByExtNum(user.getExtNum());
                if(null != num){
                    user.setExtPass(num.getExtPass());
                    return AjaxResult.success(user);
                }else{
                    throw new RuntimeException("分机号："+user.getExtNum()+" 不存在");
                }
            }else{
                throw new RuntimeException("分机号重绑失败");
            }
        }else{
            throw new RuntimeException("修改用户失败");
        }
    }

    /**
     * 删除用户且解绑分机
     */
    @PostMapping("/deleteUserOrunBindExtNum")
    @ResponseBody
    @Transactional
    public AjaxResult deleteUserOrunBindExtNum(@RequestBody SysUser user)
    {
        //先解绑分机
        CcExtNum extNum = new CcExtNum();
        extNum.setExtNum(user.getExtNum());
        extNum.setUserCode(user.getUserCode());
        int updateNum = ccExtNumService.updateCcExtNumByUserCode(extNum);
        if(updateNum>0){
            //删除用户
            int i = userService.deleteUserById(user.getUserId());
            if(i>0){
                return AjaxResult.success();
            }
        }
        throw new RuntimeException("删除用户且解绑分机失败");
    }

    /**
     * 获取手动外呼客户沟通信息
     * @param phoneNum 手机号
     * @param callType 类型  1呼入 2外呼
     * @param uuid  通话uuid
     */
    @GetMapping("/getCustCommunicationInfo")
    @ResponseBody
    public AjaxResult getCustCommunicationInfo(@RequestParam("phoneNum") String phoneNum, @RequestParam("callType") Integer callType, @RequestParam("uuid") String uuid)
    {
        Map<String,Object> mmap = new HashMap<>();
        CcCustInfo ccCustInfo = ccCustInfoService.selectCcCustInfoByPhoneNum(phoneNum);
        if (null == ccCustInfo) {
            ccCustInfo = new CcCustInfo();
            ccCustInfo.setCallRecordList(new ArrayList<>());
        } else {
            ccCustInfo.setCallRecordList(ccCustCallRecordService.selectCcCustCallRecordList(new CcCustCallRecord().setCustId(ccCustInfo.getId())));
        }
        ccCustInfo.setPhoneNum(phoneNum);
        mmap.put("ccCustInfo", ccCustInfo);
        mmap.put("callType", callType);
        mmap.put("uuid", uuid);
        // 省下拉框
        List<SysDivisionData> sysDivisionData = sysDivisionDataService.selectSysDivisionDataList(null);
        List<SysDivisionData> provinces = sysDivisionData.stream()
                .filter(d -> d.getDeep() == 0)
                .collect(Collectors.toList());
        mmap.put("provinces", provinces);
        // 市下拉框
        List<SysDivisionData> citys = sysDivisionData.stream()
                .filter(d -> d.getDeep() == 1)
                .collect(Collectors.toList());
        mmap.put("citys", citys);
        // 区县下拉框
        List<SysDivisionData> countys = sysDivisionData.stream()
                .filter(d -> d.getDeep() == 2)
                .collect(Collectors.toList());
        mmap.put("countys", countys);
        return AjaxResult.success(mmap);
    }
    /**
     * 同步本地分机号绑定
     * 远端传入分机号与工号集合，按 extNum 批量更新 userCode
     */
    @PostMapping("/syncLocalExtNumBind")
    @ResponseBody
    @Transactional
    public AjaxResult syncLocalExtNumBind(@RequestBody List<CcExtNum> extNumList)
    {
        if (CollectionUtils.isEmpty(extNumList)) {
            return AjaxResult.error("参数为空");
        }
        List<CcExtNum> validList = extNumList.stream()
                .filter(item -> item != null && item.getExtNum() != null)
                .map(item -> new CcExtNum()
                        .setExtNum(item.getExtNum())
                        .setUserCode(item.getUserCode() == null ? "" : item.getUserCode()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(validList)) {
            return AjaxResult.error("有效分机号为空");
        }
        return toAjax(ccExtNumService.batchUpdateUserCodeByExtNum(validList));
    }
    /**
     * 新增保存手动外呼沟通记录
     */
    @PostMapping("/add/custcallrecord")
    @ResponseBody
    public AjaxResult addAustcallrecord(@RequestBody CcCustInfo ccCustInfo)
    {
        ccCustInfoService.updateCcCustInfo(ccCustInfo);
        CcCustInfo custInfoBak = ccCustInfoService.selectCcCustInfoByPhoneNum(ccCustInfo.getPhoneNum());
        CcCustCallRecord callRecord = JSONObject.parseObject(ccCustInfo.getCallRecord(), CcCustCallRecord.class);
        callRecord.setCustId(custInfoBak.getId());
        //这里改成查询

        callRecord.setUserId(ccCustInfo.getOpNum());
        callRecord.setUserRealName(ccCustInfo.getUserName());
        callRecord.setCreateTime(new Date());
        CcCustCallRecord hisCallRecord = ccCustCallRecordService.selectCcCustCallRecordByUuid(callRecord.getUuid());
        if (null == hisCallRecord) {
            return toAjax(ccCustCallRecordService.insertCcCustCallRecord(callRecord));
        } else {
            callRecord.setId(hisCallRecord.getId());
            return toAjax(ccCustCallRecordService.updateCcCustCallRecord(callRecord));
        }
    }
    /**
     * 查询手动外呼记录列表
     */
    @PostMapping("/outboundcdrList")
    @ResponseBody
    public TableDataInfo outboundcdrList(@RequestBody CcOutboundCdr ccOutboundCdr)
    {
        startPage();
        List<CcOutboundCdr> list = ccOutboundCdrService.selectCcOutboundCdrList(ccOutboundCdr);
        for (CcOutboundCdr data: list) {
            data.setWavFileUrl("/recordings/files?filename=" + data.getRecordFilename());
            try {
                if(StringUtils.isNotEmpty(data.getCallee())){
                    data.setCallee(DESUtil.encrypt(URLEncoder.encode(data.getCallee(), "UTF-8")));
                }else{
                    data.setCallee("");
                }
                if(StringUtils.isNotEmpty(data.getCaller())){
                    data.setCaller(DESUtil.encrypt(URLEncoder.encode(data.getCaller(), "UTF-8")));
                }else{
                    data.setCaller("");
                }
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
        return getDataTable(list);
    }

    /**
     * 通话记录查询接口（返回完整的数据表格式）
     */
    @PostMapping("/call/phone/records")
    @ResponseBody
    public TableDataInfo getcallPhoneRecords(HttpServletRequest req, @RequestBody ApiCallRecordQueryParams queryParams)
    {
        TableDataInfo tableDataInfo;
        // 分页参数处理
        if (null == queryParams.getPageNum()
                && null == queryParams.getPageSize()) {
            queryParams.setPageNum(1);
            queryParams.setPageSize(200000);
        }
        if (null == queryParams.getPageNum()) {
            queryParams.setPageNum(1);
        }
        if (null == queryParams.getPageSize()) {
            queryParams.setPageSize(20);
        }
        // 类型（01:呼入， 02:AI外呼， 03:人工外呼）
        String callType = queryParams.getCallType();
        if (StringUtils.isBlank(callType)) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("callType不能为空!");
            return tableDataInfo;
        }
        // 校验参数
        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeStart())
                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeStart())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("calloutTimeStart格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }
        if (StringUtils.isNotEmpty(queryParams.getCalloutTimeEnd())
                && !DateValidatorUtils.isYmdHms(queryParams.getCalloutTimeEnd())) {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("calloutTimeStart格式不正确，请使用'yyyy-MM-dd HH:mm:ss'格式!");
            return tableDataInfo;
        }

        // 01:呼入， 02:AI外呼， 03:人工外呼
        if ("01".equals(callType)) {
            return getInboundRecords(queryParams);
        } else if ("02".equals(callType)) {
            return getAiCallRecordsTable(queryParams);
        } else if ("03".equals(callType)) {
            return getOutboundRecordsTable(queryParams);
        } else {
            tableDataInfo = new TableDataInfo();
            tableDataInfo.setTotal(0);
            tableDataInfo.setCode(AjaxResult.Type.INVALID_PARAM.value());
            tableDataInfo.setMsg("callType参数不合法，呼入请输入01，AI外呼请输入02，手工外呼请输入03!");
            return tableDataInfo;
        }
    }
    //ai外呼记录查询
    private TableDataInfo getAiCallRecordsTable(ApiCallRecordQueryParams queryParams) {
        Map<String, Object> params = new HashMap<>();
        if (null != queryParams.getTimeLenStart()) {
            params.put("timeLenStart", queryParams.getTimeLenStart());
        }
        if (null != queryParams.getTimeLenEnd()) {
            params.put("timeLenEnd", queryParams.getTimeLenEnd());
        }
        if (null != queryParams.getCalloutTimeStart()) {
            params.put("calloutTimeStart", queryParams.getCalloutTimeStart());
        }
        if (null != queryParams.getCalloutTimeEnd()) {
            params.put("calloutTimeEnd", queryParams.getCalloutTimeEnd());
        }
        if (null != queryParams.getAnsweredTimeStart()) {
            params.put("answeredTimeStart", queryParams.getAnsweredTimeStart());
        }
        if (null != queryParams.getAnsweredTimeEnd()) {
            params.put("answeredTimeEnd", queryParams.getAnsweredTimeEnd());
        }
        if (null != queryParams.getCallEndTimeStart()) {
            params.put("callEndTimeStart", queryParams.getCallEndTimeStart());
        }
        if (null != queryParams.getCallEndTimeEnd()) {
            params.put("callEndTimeEnd", queryParams.getCallEndTimeEnd());
        }
        CcCallPhone ccCallPhone = new CcCallPhone();
        if (null != queryParams.getBatchId() && queryParams.getBatchId() > 0) {
            ccCallPhone.setBatchId(queryParams.getBatchId());
        }
        ccCallPhone.setUuid(queryParams.getUuid());
        ccCallPhone.setTelephone(queryParams.getTelephone());
        ccCallPhone.setAcdOpnum(queryParams.getExtnum());
        ccCallPhone.setCallstatus(queryParams.getCallstatus());
        ccCallPhone.setCallerNumber(queryParams.getCallerNumber());
        ccCallPhone.setParams(params);
        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        List<CcCallPhone> list = ccCallPhoneService.selectCcCallPhoneYlrzList(ccCallPhone);
        list.forEach(callPhoneRecord -> {
            if(StringUtils.isNotBlank(callPhoneRecord.getWavfile())){
                if (callPhoneRecord.getWavfile().startsWith("/")) {
                    callPhoneRecord.setWavfile("/recordings/files?filename=" + callPhoneRecord.getWavfile().substring(1));
                }else{
                    callPhoneRecord.setWavfile("/recordings/files?filename=" + callPhoneRecord.getWavfile());
                }
            }
            callPhoneRecord.setTelephone(CommonUtils.maskPhoneNumber(callPhoneRecord.getTelephone()));
            callPhoneRecord.setCallerNumber(CommonUtils.maskPhoneNumber(callPhoneRecord.getCallerNumber()));
            callPhoneRecord.setCallstatusName( CcCallPhone.getCallStatusName(callPhoneRecord.getCallstatus()));
            callPhoneRecord.setCalloutTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getCalloutTime())));
            callPhoneRecord.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getAnsweredTime())));
            callPhoneRecord.setCallEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getCallEndTime())));
            callPhoneRecord.setTimeLenSec(DateUtils.formatTimeLength(callPhoneRecord.getTimeLen()/1000));
        });
        return getDataTable(list);
    }
    //人工外呼记录查询
    private TableDataInfo getOutboundRecordsTable(ApiCallRecordQueryParams queryParams) {
        Map<String, Object> params = new HashMap<>();
        if (null != queryParams.getTimeLenStart()) {
            params.put("timeLenStart", queryParams.getTimeLenStart());
        }
        if (null != queryParams.getTimeLenEnd()) {
            params.put("timeLenEnd", queryParams.getTimeLenEnd());
        }
        if (null != queryParams.getCalloutTimeStart()) {
            params.put("calloutTimeStart", queryParams.getCalloutTimeStart());
        }
        if (null != queryParams.getCalloutTimeEnd()) {
            params.put("calloutTimeEnd", queryParams.getCalloutTimeEnd());
        }
        if (null != queryParams.getAnsweredTimeStart()) {
            params.put("answeredTimeStart", queryParams.getAnsweredTimeStart());
        }
        if (null != queryParams.getAnsweredTimeEnd()) {
            params.put("answeredTimeEnd", queryParams.getAnsweredTimeEnd());
        }
        if (null != queryParams.getEndTimeStart()) {
            params.put("endTimeStart", queryParams.getEndTimeStart());
        }
        if (null != queryParams.getEndTimeEnd()) {
            params.put("endTimeEnd", queryParams.getEndTimeEnd());
        }
        CcCallPhone ccCallPhone = new CcCallPhone();
        if (null != queryParams.getBatchId() && queryParams.getBatchId() > 0) {
            ccCallPhone.setBatchId(queryParams.getBatchId());
        }

        CcOutboundCdr outboundCdr = new CcOutboundCdr();
        outboundCdr.setUuid(queryParams.getUuid());
        outboundCdr.setCaller(queryParams.getTelephone());
        outboundCdr.setOpnum(queryParams.getExtnum());
        outboundCdr.setParams(params);
        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        List<CcOutboundCdr> list = outboundCdrService.selectCcOutboundCdrYlrzList(outboundCdr);
        list.forEach(callPhoneRecord -> {
            if(StringUtils.isNotBlank(callPhoneRecord.getRecordFilename())){
                if (callPhoneRecord.getRecordFilename().startsWith("/")) {
                    callPhoneRecord.setRecordFilename("/recordings/files?filename=" + callPhoneRecord.getRecordFilename().substring(1));
                }else{
                    callPhoneRecord.setWavFileUrl("/recordings/files?filename=" + callPhoneRecord.getRecordFilename());
                }
                callPhoneRecord.setStartTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getStartTime())));
                callPhoneRecord.setAnsweredTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getAnsweredTime())));
                callPhoneRecord.setEndTimeStr(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", new Date(callPhoneRecord.getEndTime())));
                callPhoneRecord.setTimeLenSec(DateUtils.formatTimeLength(callPhoneRecord.getTimeLen()/1000));
                callPhoneRecord.setTimeLenValidStr(DateUtils.formatTimeLength(callPhoneRecord.getTimeLenValid()/1000));
            }
            callPhoneRecord.setCallee(CommonUtils.maskPhoneNumber(callPhoneRecord.getCallee()));
        });
        return getDataTable(list);
    }

    /**
     * 获取外呼网关列表接口
     * purposes 1手动外呼电话条，2AI外呼，3不限制
     * @param queryParams 网关参数
     * @param req
     */
    @PostMapping("/gateway/myList")
    @ResponseBody
    public TableDataInfo getGatewayMyList(@RequestBody CcGateways queryParams, HttpServletRequest req){
        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        return getDataTable(ccGatewaysService.selectCcGatewaysList(queryParams));
    }
    /**
     * 根据通话id集合查询uuid不为空的自动外呼数据
     */
    @PostMapping( "/getCcCallPhoneByIds")
    @ResponseBody
    public AjaxResult getCcCallPhoneByIds(@RequestBody List<String> callPhoneIds)
    {
        if(callPhoneIds==null){
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "callPhoneIds不能为空！", "");
        }
        List<CcCallPhone> list = ccCallPhoneService.selectCcCallPhoneListByIds(callPhoneIds);
        if(!CollectionUtils.isEmpty(list)){
            list.forEach(callPhoneRecord -> {
                callPhoneRecord.setBizJson("");
                try{
                    if(StringUtils.isNotEmpty(callPhoneRecord.getTelephone())){
                        callPhoneRecord.setTelephone(DESUtil.encrypt(URLEncoder.encode(callPhoneRecord.getTelephone(), "UTF-8")));
                    }else{
                        callPhoneRecord.setTelephone("");
                    }
                }catch (Exception e){
                    callPhoneRecord.setTelephone("");
                }
                try{
                    if(StringUtils.isNotEmpty(callPhoneRecord.getCallerNumber())){
                        callPhoneRecord.setCallerNumber(DESUtil.encrypt(URLEncoder.encode(callPhoneRecord.getCallerNumber(), "UTF-8")));
                    }else{
                        callPhoneRecord.setCallerNumber("");
                    }
                }catch (Exception e){
                    callPhoneRecord.setCallerNumber("");
                }
            });
        }
        return AjaxResult.success(list);
    }

    /**
     * 根据UUID查询自动外呼对话详情
     */
    @PostMapping( "/getCcCallPhoneByUuid")
    @ResponseBody
    public AjaxResult getCcCallPhoneByUuid(@RequestBody CcCallPhone callPhone)
    {
        if(callPhone==null || StringUtils.isBlank(callPhone.getUuid())){
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "callPhone或uuid不能为空！", "");
        }
        return AjaxResult.success(ccCallPhoneService.selectCcCallPhoneDialogueByUuid(callPhone.getUuid()));
    }

    /**
     * 修改任务
     * @param ccCallTask
     * @return
     */
    @PostMapping("/editTask")
    @ResponseBody
    public AjaxResult editTask(@RequestBody CcCallTask ccCallTask)
    {

        if ("acd".equals(ccCallTask.getAiTransferType())) {
            ccCallTask.setAiTransferData(ccCallTask.getAiTransferGroupId());
        } else if ("extension".equals(ccCallTask.getAiTransferType())) {
            ccCallTask.setAiTransferData(ccCallTask.getAiTransferExtNumber());
        } else if ("gateway".equals(ccCallTask.getAiTransferType())) {
            JSONObject aiTransferData = new JSONObject();
            aiTransferData.put("gatewayId", ccCallTask.getAiTransferGatewayId());
            aiTransferData.put("destNumber", ccCallTask.getAiTransferGatewayDestNumber());
            ccCallTask.setAiTransferData(JSONObject.toJSONString(aiTransferData));
        }
        return toAjax(ccCallTaskService.updateCcCallTask(ccCallTask));
    }

    /**
     * 获取音色分页列表
     */
    @PostMapping("/voicecodePage")
    @ResponseBody
    public TableDataInfo voicecodePage(@RequestBody CcTtsAliyun queryParams)
    {
        if(queryParams==null){
            queryParams = new CcTtsAliyun();
        }
        if(queryParams.getPageNum()==null){
            queryParams.setPageNum(1);
        }
        if(queryParams.getPageSize()== null){
            queryParams.setPageSize(10);
        }
        startPage(queryParams.getPageNum(), queryParams.getPageSize());
        // 获取音色列表
        List<CcTtsAliyun> list = ccTtsAliyunService.selectCcTtsAliyunList(queryParams);

        return getDataTable(list);
    }

    /**
     * 追加外呼名单（不自动启动任务，数据加密传输）
     * @param encryptedData
     */
    @PostMapping(value ="/import/autoCallData")
    @ResponseBody
    public AjaxResult addCommonCallList(@RequestBody String encryptedData) {
        // 解密数据
        String decryptedJson;
        try {
            //base64特殊处理
            encryptedData = URLDecoder.decode(encryptedData, "UTF-8").replaceAll("=+$", "");
            decryptedJson = DESUtil.decrypt(encryptedData);
        } catch (Throwable e) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数encryptedData解密失败", "");
        }
        // 2. 反序列化为 Java 对象
        CommonCallListModel commonCallListModel = JSONObject.parseObject(decryptedJson, CommonCallListModel.class);

        Long batchId = commonCallListModel.getBatchId();
        if (null == batchId) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不能为空", "");
        }
        if (CollectionUtils.isEmpty(commonCallListModel.getPhoneList())) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数phoneList不能为空", "");
        }
        // 获取任务
        CcCallTask ccCallTask = callTaskService.selectCcCallTaskByBatchId(batchId);
        if (null == ccCallTask) {
            return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "参数batchId不合法，请输入正确的batchId", "");
        }
        // 通知类的通知内容必填
        if (ccCallTask.getTaskType() == 2) {
            for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
                if (StringUtils.isBlank(commonPhoneModel.getNoticeContent())) {
                    return AjaxResult.error(AjaxResult.Type.INVALID_PARAM, "号码" + commonPhoneModel.getPhoneNum() + "的noticeContent不能为空！", "");
                }
            }
        }

        // 追加名单：逐批入库，每批成功后再汇总，批次互不影响
        List<CcCallPhone> batchList = new ArrayList<>(200);
        List<CcCallPhone> allCallPhoneList = new ArrayList<>();
        for (CommonPhoneModel commonPhoneModel : commonCallListModel.getPhoneList()) {
            String phoneNum = commonPhoneModel.getPhoneNum();
            if (StringUtils.isBlank(phoneNum)) {
                continue;
            }
            CcCallPhone callPhone = buildCcCallPhone(ccCallTask, phoneNum, commonPhoneModel.getBizJson());
            callPhone.setTtsText(commonPhoneModel.getNoticeContent());
            batchList.add(callPhone);
            if (batchList.size() >= 200) {
                insertCallPhoneBatch(batchList, allCallPhoneList);
            }
        }
        insertCallPhoneBatch(batchList, allCallPhoneList);
        int successCount = allCallPhoneList.size();
        log.info("成功追加" + successCount + "个名单");
        if(!CollectionUtils.isEmpty(allCallPhoneList)){
            allCallPhoneList.forEach(callPhoneRecord -> {
                try{
                    if(StringUtils.isNotEmpty(callPhoneRecord.getTelephone())){
                        callPhoneRecord.setTelephone(DESUtil.encrypt(URLEncoder.encode(callPhoneRecord.getTelephone(), "UTF-8")));
                    }else{
                        callPhoneRecord.setTelephone("");
                    }
                }catch (Throwable e){
                    callPhoneRecord.setTelephone("");
                }
                try{
                    if(StringUtils.isNotEmpty(callPhoneRecord.getCallerNumber())){
                        callPhoneRecord.setCallerNumber(DESUtil.encrypt(URLEncoder.encode(callPhoneRecord.getCallerNumber(), "UTF-8")));
                    }else{
                        callPhoneRecord.setCallerNumber("");
                    }
                }catch (Throwable e){
                    callPhoneRecord.setCallerNumber("");
                }
            });
        }
        return AjaxResult.success("成功追加" + successCount + "个名单", allCallPhoneList);
    }

    /**
     * 手动同步手动外呼记录（按 uuid；同一 uuid 多段断线重连合并为一条返回）
     * @param uuids 本地通话 uuid 列表
     */
    @PostMapping(value ="/outboundcdr/manualPullTest")
    @ResponseBody
    public AjaxResult outboundcdrManualPullTest(@RequestBody List<String> uuids) {
        List<CcOutboundCdr> list = outboundCdrService.selectMergedCcOutboundCdrByUuids(uuids);
        if (list == null) {
            list = Collections.emptyList();
        }
        if (!CollectionUtils.isEmpty(list)) {
            for (CcOutboundCdr outboundCdr : list) {
                if (StringUtils.isNotEmpty(outboundCdr.getCaller())) {
                    try {
                        outboundCdr.setCaller(DESUtil.encrypt(URLEncoder.encode(outboundCdr.getCaller(), "UTF-8")));
                    } catch (Throwable e) {
                        outboundCdr.setCaller("");
                    }
                }
                if (StringUtils.isNotEmpty(outboundCdr.getCallee())) {
                    try {
                        outboundCdr.setCallee(DESUtil.encrypt(URLEncoder.encode(outboundCdr.getCallee(), "UTF-8")));
                    } catch (Throwable e) {
                        outboundCdr.setCallee("");
                    }
                }
            }
        }
        return AjaxResult.success("成功获取" + list.size() + "条数据", list);
    }

    /**
     * 查询全部ttsprovider列表
     */
    @GetMapping("/tts/voiceSource/all")
    @ResponseBody
    public AjaxResult allTtsProvider()
    {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("config_tts_provider");
        List<SysConfig> list = sysConfigService.selectConfigList(sysConfig);
        Map<String, String> providerMap = new HashMap<>();
        for (SysConfig data: list) {
            providerMap.put(data.getConfigValue(), data.getConfigName());
        }
        return AjaxResult.success(providerMap);
    }

    /**
     * 查询全部asrprovider列表
     */
    @GetMapping("/asr/provider/all")
    @ResponseBody
    public AjaxResult allAsrProvider()
    {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("config_asr_provider");
        List<SysConfig> list = sysConfigService.selectConfigList(sysConfig);
        Map<String, String> providerMap = new HashMap<>();
        for (SysConfig data: list) {
            providerMap.put(data.getConfigValue(), data.getConfigName());
        }
        return AjaxResult.success(providerMap);
    }
    /**
     * 根据asr厂商获取语言
     */
    @GetMapping("/asr/language")
    @ResponseBody
    public AjaxResult getAsrLanguages(@RequestParam String asrProvider)
    {
        List<JSONObject> languageList = new ArrayList<>();
        Map<String, String> languageCodeMap = new HashMap<>();
        List<CcAsrLanguages> asrLanguagesList = asrLanguagesService.selectCcAsrLanguagesList(new CcAsrLanguages().setAsrProvider(asrProvider));
        for (CcAsrLanguages asrLanguages: asrLanguagesList) {
            if (StringUtils.isNotEmpty(asrLanguages.getLanguageCode())
                    && StringUtils.isEmpty(languageCodeMap.get(asrLanguages.getLanguageCode()))) {
                languageCodeMap.put(asrLanguages.getLanguageCode(), asrLanguages.getLanguageName());
            }
        }
        if (CollectionUtils.isEmpty(languageCodeMap)) {
            languageCodeMap.put("zh-CN", "中文");
        }
        for (String key: languageCodeMap.keySet()) {
            JSONObject obj = new JSONObject();
            obj.put("key", key);
            obj.put("value", languageCodeMap.get(key));
            languageList.add(obj);
        }
        return AjaxResult.success(languageList);
    }


    /**
     * 根据asr厂商和语言获取asr模型列表
     */
    @GetMapping("/asr/models")
    @ResponseBody
    public AjaxResult getAsrModels(@RequestParam String asrProvider, @RequestParam String asrLanguageCode)
    {
        List<JSONObject> modelsList = new ArrayList<>();
        Map<String, String> asrModelsMap = new HashMap<>();
        List<CcAsrLanguages> asrLanguagesList = asrLanguagesService.selectCcAsrLanguagesList(new CcAsrLanguages().setAsrProvider(asrProvider).setLanguageCode(asrLanguageCode));
        for (CcAsrLanguages asrLanguages: asrLanguagesList) {
            if (StringUtils.isNotEmpty(asrLanguages.getModels())
                    && StringUtils.isEmpty(asrModelsMap.get(asrLanguages.getModels()))) {
                asrModelsMap.put(asrLanguages.getModels(), asrLanguages.getModels());
            }
        }
        if (CollectionUtils.isEmpty(asrModelsMap)) {
            asrModelsMap.put("", "");
        }
        for (String key: asrModelsMap.keySet()) {
            JSONObject obj = new JSONObject();
            obj.put("key", key);
            obj.put("value", asrModelsMap.get(key));
            modelsList.add(obj);
        }
        return AjaxResult.success(modelsList);
    }
    /**
     * 根据tts厂商和语言获取asr模型列表
     */
    @GetMapping("/tts/models")
    @ResponseBody
    public AjaxResult getTtsModels(@RequestParam String voiceSource, @RequestParam String ttsLanguageCode)
    {
        List<JSONObject> modelsList = new ArrayList<>();
        Map<String, String> ttsModelMap = new HashMap<>();
        List<CcTtsAliyun> ccTtsAliyunList = ccTtsAliyunService.selectCcTtsAliyunList(new CcTtsAliyun().setVoiceSource(voiceSource).setLanguageCode(ttsLanguageCode));
        for (CcTtsAliyun ccTtsAliyun: ccTtsAliyunList) {
            if (StringUtils.isNotEmpty(ccTtsAliyun.getTtsModels())
                    && StringUtils.isEmpty(ttsModelMap.get(ccTtsAliyun.getTtsModels()))) {
                ttsModelMap.put(ccTtsAliyun.getTtsModels(), ccTtsAliyun.getTtsModels());
            }
        }
        if (CollectionUtils.isEmpty(ttsModelMap)) {
            ttsModelMap.put("", "");
        }
        for (String key: ttsModelMap.keySet()) {
            JSONObject obj = new JSONObject();
            obj.put("key", key);
            obj.put("value", ttsModelMap.get(key));
            modelsList.add(obj);
        }
        return AjaxResult.success(modelsList);
    }
    /**
     * 根据tts厂商获取支持语言
     */
    @GetMapping("/tts/language")
    @ResponseBody
    public AjaxResult getTtsLanguages(@RequestParam String voiceSource)
    {
        List<JSONObject> languageList = new ArrayList<>();
        Map<String, String> languageCodeMap = new HashMap<>();
        List<CcTtsAliyun> ccTtsAliyunList = ccTtsAliyunService.selectCcTtsAliyunList(new CcTtsAliyun().setVoiceSource(voiceSource));
        for (CcTtsAliyun ccTtsAliyun: ccTtsAliyunList) {
            if (StringUtils.isNotEmpty(ccTtsAliyun.getLanguageCode())
                    && StringUtils.isEmpty(languageCodeMap.get(ccTtsAliyun.getLanguageCode()))) {
                languageCodeMap.put(ccTtsAliyun.getLanguageCode(), ccTtsAliyun.getLanguageName());
            }
        }
        if (CollectionUtils.isEmpty(languageCodeMap)) {
            languageCodeMap.put("zh-CN", "中文");
        }
        for (String key: languageCodeMap.keySet()) {
            JSONObject obj = new JSONObject();
            obj.put("key", key);
            obj.put("value", languageCodeMap.get(key));
            languageList.add(obj);
        }
        return AjaxResult.success(languageList);
    }
}
