package com.fs.company.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.fs.common.utils.StringUtils;
import com.fs.company.domain.*;
import com.fs.company.mapper.*;
import com.fs.company.service.IAsyncCalleeProcessorService;
import com.fs.company.util.RandomNameGeneratorUtil;
import com.fs.company.vo.CompanyNodeInfoVo;
import com.fs.enums.ExecutionStatusEnum;
import com.fs.enums.NodeTypeEnum;
import com.fs.his.config.CidPhoneConfig;
import com.fs.system.service.ISysConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

@Service
public class AsyncCalleeProcessorServiceImpl implements IAsyncCalleeProcessorService {

    private static final Logger log = LoggerFactory.getLogger(AsyncCalleeProcessorServiceImpl.class);

    private static final int BATCH_SIZE = 1500;
    private static final int CPU_YIELD_INTERVAL = 500;
    private static final int SLEEP_INTERVAL = 2000;
    private static final int SLEEP_MILLIS = 10;
    private static final int PHONE_LENGTH = 11;
    private static final char DIGIT_ZERO = '0';
    private static final int RADIX_TEN = 10;

    private final ISysConfigService configService;
    private final CompanyWorkflowEdgeMapper edgeMapper;
    private final CompanyConfigMapper companyConfigMapper;
    private final CompanyWorkflowMapper companyWorkflowMapper;
    private final CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper;
    private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
    private final CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper;
    private final CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper;

    AsyncCalleeProcessorServiceImpl(CompanyConfigMapper companyConfigMapper,
                                    ISysConfigService configService,
                                    CompanyWorkflowEdgeMapper edgeMapper,
                                    CompanyWorkflowMapper companyWorkflowMapper,
                                    CompanyAiWorkflowExecMapper companyAiWorkflowExecMapper,
                                    CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper,
                                    CompanyVoiceRoboticBusinessMapper companyVoiceRoboticBusinessMapper,
                                    CompanyVoiceRoboticCalleesMapper companyVoiceRoboticCalleesMapper) {
        this.companyConfigMapper = companyConfigMapper;
        this.configService = configService;
        this.edgeMapper = edgeMapper;
        this.companyWorkflowMapper = companyWorkflowMapper;
        this.companyAiWorkflowExecMapper = companyAiWorkflowExecMapper;
        this.companyAiWorkflowExecLogMapper = companyAiWorkflowExecLogMapper;
        this.companyVoiceRoboticBusinessMapper = companyVoiceRoboticBusinessMapper;
        this.companyVoiceRoboticCalleesMapper = companyVoiceRoboticCalleesMapper;
    }

    @Override
    @Async("calleeTaskExecutor")
    public void generateCustomerInfo(List<CompanyVoiceRoboticCallees> calleesList,
                                     Map<String, CompanyWxClient> clientMp,
                                     CompanyVoiceRobotic robotic) {
        if (calleesList == null || calleesList.isEmpty() || robotic == null) {
            return;
        }

        Thread currentThread = Thread.currentThread();
        if (currentThread.isInterrupted()) {
            log.info("任务被中断，退出处理");
            return;
        }

        CidPhoneConfig phoneConfig = loadPhoneConfig(robotic.getCompanyId());
        if (phoneConfig == null || !Boolean.TRUE.equals(phoneConfig.getEnablePhoneConfig())) {
            log.warn("电话配置未启用或为空，companyId: {}", robotic.getCompanyId());
            return;
        }

        CompanyWorkflow workflow = loadWorkflow(robotic.getCompanyAiWorkflowId());
        if (workflow == null) {
            log.warn("工作流不存在，workflowId: {}", robotic.getCompanyAiWorkflowId());
            return;
        }

        CompanyNodeInfoVo nodeInfoVo = loadNodeInfo(workflow.getWorkflowId(), workflow.getStartNodeKey());
        if (nodeInfoVo == null) {
            log.warn("节点信息不存在，workflowId: {}, startNodeKey: {}", workflow.getWorkflowId(), workflow.getStartNodeKey());
            return;
        }

        List<CompanyVoiceRoboticCallees> batchToInsert = new ArrayList<>(BATCH_SIZE);
        int processedCount = 0;

        for (CompanyVoiceRoboticCallees callees : calleesList) {
            if (currentThread.isInterrupted()) {
                log.info("任务被中断，已处理 {} 条", processedCount);
                break;
            }

            try {
                generatePhoneNumberInBatch(phoneConfig, callees, batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
                processedCount++;

                if (processedCount % CPU_YIELD_INTERVAL == 0) {
                    Thread.yield();
                }

                if (processedCount % SLEEP_INTERVAL == 0) {
                    Thread.sleep(SLEEP_MILLIS);
                }
            } catch (InterruptedException e) {
                log.info("任务被中断，已处理 {} 条", processedCount);
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                log.error("处理被叫信息异常，phone: {}", callees.getPhone(), e);
            }
        }

        log.info("generateCustomerInfo处理完成，共处理 {} 条", processedCount);
    }

    private CidPhoneConfig loadPhoneConfig(Long companyId) {
        try {
            CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(companyId, "cid.config");
            if (companyConfig != null && StringUtils.isNotEmpty(companyConfig.getConfigValue())) {
                return JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
            }
            String json = configService.selectConfigByKey("his.store");
            if (StringUtils.isNotEmpty(json)) {
                return JSONObject.parseObject(json, CidPhoneConfig.class);
            }
        } catch (Exception e) {
            log.error("加载电话配置异常，companyId: {}", companyId, e);
        }
        return null;
    }

    private CompanyWorkflow loadWorkflow(Long workflowId) {
        try {
            return companyWorkflowMapper.selectCompanyWorkflowById(workflowId);
        } catch (Exception e) {
            log.error("加载工作流异常，workflowId: {}", workflowId, e);
            return null;
        }
    }

    private CompanyNodeInfoVo loadNodeInfo(Long workflowId, String startNodeKey) {
        try {
            return edgeMapper.slectNodeInfoByWorkflowId(workflowId, startNodeKey);
        } catch (Exception e) {
            log.error("加载节点信息异常，workflowId: {}, startNodeKey: {}", workflowId, startNodeKey, e);
            return null;
        }
    }

    private void generatePhoneNumberInBatch(CidPhoneConfig config,
                                            CompanyVoiceRoboticCallees callees,
                                            List<CompanyVoiceRoboticCallees> batchToInsert,
                                            Map<String, CompanyWxClient> clientMp,
                                            CompanyVoiceRobotic robotic,
                                            CompanyWorkflow workflow,
                                            CompanyNodeInfoVo nodeInfoVo) {
        String basePhone = callees.getPhone();
        if (basePhone == null || basePhone.length() != PHONE_LENGTH) {
            return;
        }

        int start = config.getStartIndex();
        int end = config.getEndIndex();
        int totalCount = config.getGenerateCount();

        if (!isValidRange(start, end)) {
            return;
        }

        int startIdx = start - 1;
        int endIdx = end - 1;
        char[] baseChars = basePhone.toCharArray();
        ThreadLocalRandom random = ThreadLocalRandom.current();
        int nameIndex = 0;

        while (nameIndex < totalCount) {
            if (Thread.currentThread().isInterrupted()) {
                break;
            }

            int currentBatchSize = Math.min(BATCH_SIZE, totalCount - nameIndex);

            for (int i = 0; i < currentBatchSize; i++) {
                CompanyVoiceRoboticCallees roboticCallees = createCalleesWithPhone(
                        baseChars, startIdx, endIdx, random, callees);
                batchToInsert.add(roboticCallees);
            }

            nameIndex += currentBatchSize;

            if (batchToInsert.size() >= BATCH_SIZE || nameIndex >= totalCount) {
                flushGeneratedCalleesBatch(batchToInsert, clientMp, robotic, workflow, nodeInfoVo);
            }

            if (nameIndex % SLEEP_INTERVAL == 0) {
                try {
                    Thread.sleep(SLEEP_MILLIS);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }

    private boolean isValidRange(int start, int end) {
        return start >= 1 && start <= PHONE_LENGTH
                && end >= 1 && end <= PHONE_LENGTH
                && start <= end;
    }

    private CompanyVoiceRoboticCallees createCalleesWithPhone(char[] baseChars, int startIdx, int endIdx,
                                                              ThreadLocalRandom random,
                                                              CompanyVoiceRoboticCallees template) {
        char[] newChars = baseChars.clone();
        for (int j = startIdx; j <= endIdx; j++) {
            newChars[j] = (char) (DIGIT_ZERO + random.nextInt(RADIX_TEN));
        }

        CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
        roboticCallees.setPhone(new String(newChars));
        roboticCallees.setRoboticId(template.getRoboticId());
        roboticCallees.setTaskFlow(template.getTaskFlow());
        roboticCallees.setRunTaskFlow(template.getRunTaskFlow());
        roboticCallees.setIsWeCom(template.getIsWeCom());
        roboticCallees.setUserId(0L);
        roboticCallees.setUserName(RandomNameGeneratorUtil.generateOne());
        roboticCallees.setIsGenerate(1);

        return roboticCallees;
    }

    private void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
                                            Map<String, CompanyWxClient> clientMap,
                                            CompanyVoiceRobotic robotic,
                                            CompanyWorkflow workflow,
                                            CompanyNodeInfoVo nodeInfoVo) {
        if (batchToInsert == null || batchToInsert.isEmpty()) {
            return;
        }

        try {
            int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
            if (rows > 0) {
                generateVoiceRoboticBusiness(batchToInsert, clientMap, robotic, workflow, nodeInfoVo);
            }
        } catch (Exception e) {
            log.error("批量插入被叫数据异常，size: {}", batchToInsert.size(), e);
        } finally {
            batchToInsert.clear();
        }
    }

    private void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
                                              Map<String, CompanyWxClient> clientMp,
                                              CompanyVoiceRobotic robotic,
                                              CompanyWorkflow workflow,
                                              CompanyNodeInfoVo nodeInfoVo) {
        if (calleesList == null || calleesList.isEmpty()) {
            return;
        }

        List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>(calleesList.size());
        Date date = new Date();

        for (CompanyVoiceRoboticCallees callees : calleesList) {
            CompanyVoiceRoboticBusiness business = createBusiness(callees, clientMp, date);
            addList.add(business);
        }

        if (!addList.isEmpty()) {
            flushGeneratedBusinessBatch(addList, robotic, workflow, nodeInfoVo);
        }
    }

    private CompanyVoiceRoboticBusiness createBusiness(CompanyVoiceRoboticCallees callees,
                                                       Map<String, CompanyWxClient> clientMp,
                                                       Date date) {
        CompanyVoiceRoboticBusiness business = new CompanyVoiceRoboticBusiness();
        business.setRoboticId(callees.getRoboticId());
        business.setCalleeId(callees.getId());

        String clientKey = callees.getRoboticId() + "-" + callees.getUserId();
        CompanyWxClient client = clientMp.get(clientKey);
        business.setWxClientId(client != null ? client.getId() : null);

        business.setAddWxDone(0);
        business.setCallPhoneDone(0);
        business.setSendMsgDone(0);
        business.setCreateTime(date);
        business.setIsGenerate(1);

        return business;
    }

    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,
                                             CompanyVoiceRobotic robotic,
                                             CompanyWorkflow workflow,
                                             CompanyNodeInfoVo nodeInfoVo) {
        if (batchToInsert == null || batchToInsert.isEmpty()) {
            return;
        }

        try {
            int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
            if (rows > 0) {
                generateWorkflowExecRecords(batchToInsert, robotic, workflow, nodeInfoVo);
            }
        } catch (Exception e) {
            log.error("批量插入业务数据异常，size: {}", batchToInsert.size(), e);
        } finally {
            batchToInsert.clear();
        }
    }

    private void generateWorkflowExecRecords(List<CompanyVoiceRoboticBusiness> businessList,
                                             CompanyVoiceRobotic robotic,
                                             CompanyWorkflow workflow,
                                             CompanyNodeInfoVo nodeInfoVo) {
        if (businessList == null || businessList.isEmpty()) {
            return;
        }

        LocalDateTime now = LocalDateTime.now();
        List<CompanyAiWorkflowExec> startExecList = new ArrayList<>(businessList.size());
        List<CompanyAiWorkflowExec> targetExecList = new ArrayList<>(businessList.size());

        for (CompanyVoiceRoboticBusiness business : businessList) {
            if (Thread.currentThread().isInterrupted()) {
                break;
            }

            CompanyAiWorkflowExec startExec = createStartExec(business, robotic, workflow, now);
            startExecList.add(startExec);

            CompanyAiWorkflowExec targetExec = createTargetExec(startExec, nodeInfoVo, now);
            targetExecList.add(targetExec);

            if (targetExecList.size() >= BATCH_SIZE) {
                persistExecLogs(targetExecList, startExecList);
            }
        }

        if (!targetExecList.isEmpty()) {
            persistExecLogs(targetExecList, startExecList);
        }
    }

    private CompanyAiWorkflowExec createStartExec(CompanyVoiceRoboticBusiness business,
                                                  CompanyVoiceRobotic robotic,
                                                  CompanyWorkflow workflow,
                                                  LocalDateTime now) {
        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
        exec.setWorkflowInstanceId(generateInstanceId());
        exec.setWorkflowId(robotic.getCompanyAiWorkflowId());
        exec.setCurrentNodeKey(workflow.getStartNodeKey());
        exec.setCurrentNodeType(NodeTypeEnum.START.getValue());
        exec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
        exec.setStatus(ExecutionStatusEnum.SUCCESS.getValue());
        exec.setStartTime(now);
        exec.setVariables(buildVariables(robotic, business));
        exec.setBusinessKey(business.getId());
        exec.setStartNodeKey(workflow.getStartNodeKey());
        exec.setEndNodeKey(workflow.getEndNodeKey());
        exec.setCidGroupNo(robotic.getCidGroupNo());
        exec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
        exec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
        exec.setIsGenerate(1);
        return exec;
    }

    private CompanyAiWorkflowExec createTargetExec(CompanyAiWorkflowExec startExec,
                                                   CompanyNodeInfoVo nodeInfoVo,
                                                   LocalDateTime now) {
        CompanyAiWorkflowExec exec = new CompanyAiWorkflowExec();
        exec.setWorkflowInstanceId(startExec.getWorkflowInstanceId());
        exec.setWorkflowId(startExec.getWorkflowId());
        exec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
        exec.setCurrentNodeName(nodeInfoVo.getNodeName());
        exec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
        exec.setStatus(ExecutionStatusEnum.INTERRUPT.getValue());
        exec.setStartTime(now);
        exec.setVariables(startExec.getVariables());
        exec.setBusinessKey(startExec.getBusinessKey());
        exec.setStartNodeKey(startExec.getStartNodeKey());
        exec.setEndNodeKey(startExec.getEndNodeKey());
        exec.setCidGroupNo(startExec.getCidGroupNo());
        exec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
        exec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
        exec.setIsGenerate(1);
        return exec;
    }

    private String buildVariables(CompanyVoiceRobotic robotic, CompanyVoiceRoboticBusiness business) {
        JSONObject variables = new JSONObject(5);
        variables.put("roboticId", robotic.getId());
        variables.put("businessId", business.getId());
        variables.put("cidGroupNo", robotic.getCidGroupNo());
        variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
        variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
        return variables.toJSONString();
    }

    private void persistExecLogs(List<CompanyAiWorkflowExec> workflowExecs,
                                 List<CompanyAiWorkflowExec> startExecList) {
        if (workflowExecs == null || workflowExecs.isEmpty()) {
            return;
        }

        try {
            int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
            if (rows > 0) {
                workflowExecLogBatchInsert(startExecList);
                workflowExecs.forEach(exec -> exec.setStatus(ExecutionStatusEnum.FAILURE.getValue()));
                workflowExecLogBatchInsert(workflowExecs);
            }
        } catch (Exception e) {
            log.error("持久化执行日志异常，size: {}", workflowExecs.size(), e);
        } finally {
            workflowExecs.clear();
            startExecList.clear();
        }
    }

    private void workflowExecLogBatchInsert(List<CompanyAiWorkflowExec> workflowExecs) {
        if (workflowExecs == null || workflowExecs.isEmpty()) {
            return;
        }

        List<CompanyAiWorkflowExecLog> batch = new ArrayList<>(workflowExecs.size());
        Date date = new Date();

        for (CompanyAiWorkflowExec w : workflowExecs) {
            CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
            execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
            execLog.setNodeKey(w.getCurrentNodeKey());
            execLog.setNodeName(w.getCurrentNodeName());
            execLog.setNodeType(w.getCurrentNodeType());
            execLog.setInputData(w.getVariables());
            execLog.setStatus(w.getStatus());
            execLog.setOutputData("null");
            execLog.setStartTime(date);
            execLog.setEndTime(date);
            execLog.setIsGenerate(1);
            batch.add(execLog);
        }

        try {
            companyAiWorkflowExecLogMapper.batchInsert(batch);
        } catch (Exception e) {
            log.error("批量插入执行日志异常，size: {}", batch.size(), e);
        } finally {
            batch.clear();
        }
    }

    private String generateInstanceId() {
        return "wf_" + System.currentTimeMillis() + "_" +
                UUID.randomUUID().toString().replace("-", "");
    }
}
