package com.xisc.pm.app.service.autoboard.core.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.hzero.core.base.AopProxy;
import org.hzero.core.redis.RedisHelper;
import org.hzero.mybatis.base.BaseRepository;
import org.hzero.mybatis.domian.Condition;
import org.hzero.mybatis.util.Sqls;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import com.google.common.collect.Maps;
import com.xisc.boot.common.utils.BaseConstants;
import com.xisc.pm.app.service.PmAutoPdResultService;
import com.xisc.pm.app.service.PmRequestInfoService;
import com.xisc.pm.app.service.autoboard.config.CalcLog;
import com.xisc.pm.app.service.autoboard.core.PmAutoBoardExecCallable;
import com.xisc.pm.app.service.autoboard.core.PmAutoBoardService;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardExecParam;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardExecResult;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardMoldDTO;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardPolicyDTO;
import com.xisc.pm.app.service.autoboard.dto.PmAutoBoardPolicyGroupDTO;
import com.xisc.pm.app.service.autoboard.dto.PmContBoardParam;
import com.xisc.pm.app.service.autoboard.dto.PmPolicyStDTO;
import com.xisc.pm.domain.entity.PmContractLineSpec;
import com.xisc.pm.domain.entity.PmCutPlaceBt;
import com.xisc.pm.domain.entity.PmPdConstBt;
import com.xisc.pm.domain.entity.PmPlateCutLenBt;
import com.xisc.pm.domain.entity.PmPlateSpecBt;
import com.xisc.pm.domain.entity.PmPlateTrimBt;
import com.xisc.pm.domain.entity.PmPolicy;
import com.xisc.pm.domain.entity.PmPolicyGrKpi;
import com.xisc.pm.domain.entity.PmPolicyGroup;
import com.xisc.pm.domain.entity.PmPolicyGroupAttr;
import com.xisc.pm.domain.entity.PmPolicySection;
import com.xisc.pm.domain.entity.PmPolicySlabMethod;
import com.xisc.pm.domain.entity.PmPolicySteelType;
import com.xisc.pm.domain.entity.PmPolicyVip;
import com.xisc.pm.domain.entity.PmProdLineSection;
import com.xisc.pm.domain.entity.PmProdLineSectionSpec;
import com.xisc.pm.domain.entity.PmProductionLine;
import com.xisc.pm.domain.entity.PmRequestInfo;
import com.xisc.pm.domain.entity.PmRuntimeContract;
import com.xisc.pm.domain.entity.PmSdivStLimitBt;
import com.xisc.pm.domain.entity.PmSlabMethod;
import com.xisc.pm.domain.entity.PmSlabSpecBt;
import com.xisc.pm.domain.entity.PmSpeStAddvBt;
import com.xisc.pm.domain.entity.PmThickAddvBt;
import com.xisc.pm.domain.repository.PmContractLineSpecRepository;
import com.xisc.pm.domain.repository.PmCutPlaceBtRepository;
import com.xisc.pm.domain.repository.PmPdConstBtRepository;
import com.xisc.pm.domain.repository.PmPlateCutLenBtRepository;
import com.xisc.pm.domain.repository.PmPlateSpecBtRepository;
import com.xisc.pm.domain.repository.PmPlateTrimBtRepository;
import com.xisc.pm.domain.repository.PmPolicyGrKpiRepository;
import com.xisc.pm.domain.repository.PmPolicyGroupAttrRepository;
import com.xisc.pm.domain.repository.PmPolicyGroupRepository;
import com.xisc.pm.domain.repository.PmPolicyRepository;
import com.xisc.pm.domain.repository.PmPolicySectionRepository;
import com.xisc.pm.domain.repository.PmPolicySlabMethodRepository;
import com.xisc.pm.domain.repository.PmPolicySteelGroupRepository;
import com.xisc.pm.domain.repository.PmPolicySteelTypeRepository;
import com.xisc.pm.domain.repository.PmPolicyVipRepository;
import com.xisc.pm.domain.repository.PmProContRepository;
import com.xisc.pm.domain.repository.PmProdLineSectionRepository;
import com.xisc.pm.domain.repository.PmProdLineSectionSpecRepository;
import com.xisc.pm.domain.repository.PmProductionLineRepository;
import com.xisc.pm.domain.repository.PmRequestInfoRepository;
import com.xisc.pm.domain.repository.PmSdivStLimitBtRepository;
import com.xisc.pm.domain.repository.PmSlabMethodRepository;
import com.xisc.pm.domain.repository.PmSlabSpecBtRepository;
import com.xisc.pm.domain.repository.PmSpeStAddvBtRepository;
import com.xisc.pm.domain.repository.PmThickAddvBtRepository;
import com.xisc.pm.infra.constant.PmPlateConstants;
import com.xisc.pm.infra.util.BatchInsertUtils;
import com.xisc.pm.infra.util.BeanUtils;
import com.xisc.pm.infra.util.Utils;

import io.choerodon.core.exception.CommonException;
import io.choerodon.core.oauth.CustomUserDetails;
import io.choerodon.core.oauth.DetailsHelper;
import lombok.extern.slf4j.Slf4j;

/**
 * 自动组板服务
 * 
 * @author jun.zeng
 *
 */
@Slf4j
@Component
public class PmAutoBoardServiceimpl implements PmAutoBoardService ,AopProxy<PmAutoBoardService> {

    private static final String MOLD_ID_KEY = "%s_%s_%s";

    @Autowired
    private ThreadPoolTaskExecutor calcThreadPoolTaskExecutor;

    @Autowired
    private PmProContRepository pmProContRepository;

    @Autowired
    private PmPolicySteelGroupRepository pmPolicySteelGroupRepository;

    @Autowired
    private PmPolicySteelTypeRepository pmPolicySteelTypeRepository;

    @Autowired
    protected PmCutPlaceBtRepository pmCutPlaceBtRepository;

    @Autowired
    protected PmSlabSpecBtRepository pmSlabSpecBtRepository;

    @Autowired
    protected PmPlateTrimBtRepository pmPlateTrimBtRepository;

    @Autowired
    protected PmThickAddvBtRepository pmThickAddvBtRepository;

    @Autowired
    protected PmPlateCutLenBtRepository pmPlateCutLenBtRepository;

    @Autowired
    protected PmSpeStAddvBtRepository pmSpeStAddvBtRepository;

    @Autowired
    protected PmPlateSpecBtRepository pmPlateSpecBtRepository;

    @Autowired
    private PmPdConstBtRepository pmPdConstBtRepository;

    @Autowired
    private PmPolicyGroupRepository pmPolicyGroupRepository;

    @Autowired
    private PmPolicyGroupAttrRepository pmPolicyGroupAttrRepository;

    @Autowired
    private PmPolicyRepository pmPolicyRepository;

    @Autowired
    private PmProdLineSectionRepository pmProdLineSectionRepository;

    @Autowired
    private PmPolicySectionRepository pmPolicySectionRepository;

    @Autowired
    private PmContractLineSpecRepository pmContractLineSpecRepository;

    @Autowired
    private PmProductionLineRepository pmProductionLineRepository;

    @Autowired
    private PmPolicySlabMethodRepository pmPolicySlabMethodRepository;

    @Autowired
    private PmProdLineSectionSpecRepository pmProdLineSectionSpecRepository;

    @Autowired
    private PmPolicyVipRepository pmPolicyVipRepository;
    
    @Autowired
    private PmSlabMethodRepository pmSlabMethodRepository;
    
    @Autowired
    private PmSdivStLimitBtRepository pmSdivStLimitBtRepository; 
    
    @Autowired
    private PmPolicyGrKpiRepository pmPolicyGrKpiRepository;
    
    @Autowired
    private RedisHelper redisHelper;
    
    @Autowired
    private PmAutoPdResultService pmAutoPdResultService;
    
    @Autowired
    private PmRequestInfoRepository pmRequestInfoRepository;
    
    @Autowired
    private PmRequestInfoService pmRequestInfoService;
    
    @Autowired
    private BatchInsertUtils batchInsertUtils;
    
    private static final String PM_AUTO_PD_EXECUTE_LOCK_KEY = "pm.auto.pd.execute.lock1";
    
    @Override
    public void checkAutoPdExecStatus(Long tenantId) {
        if(redisHelper.hasKey(PM_AUTO_PD_EXECUTE_LOCK_KEY)) {
            throw new CommonException("当前存在自动组板执行任务，请稍后重试！");
        }
        if(pmRequestInfoRepository.checkAutoPdExecStatus(tenantId)) {
            throw new CommonException("当前存在自动组板执行任务，请稍后重试！");
        }
    }

    @Override
    public void executeAutoPd(Boolean async, String policyGroupCode, Long tenantId) {
        checkAutoPdExecStatus(tenantId);
        PmRequestInfo pmRequestInfo = null;
        try {
            redisHelper.strSet(PM_AUTO_PD_EXECUTE_LOCK_KEY, "1", 5L, TimeUnit.MINUTES);
            long start = System.currentTimeMillis();
            PmAutoBoardPolicyGroupDTO policyGroup = new PmAutoBoardPolicyGroupDTO();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            Date date = new Date();
            policyGroup.setBatch(String.format("%s%03d", sdf.format(date) , date.getTime() % 1000));
            PmPolicyGroup query = new PmPolicyGroup();
            query.setPolicyGroupCode(policyGroupCode);
            query.setTenantId(tenantId);
            PmPolicyGroup pmPolicyGroup = pmPolicyGroupRepository.selectOne(query);
    
            PmPolicyGroupAttr pmPolicyGroupAttr = pmPolicyGroupAttrRepository.selectByPrimary(pmPolicyGroup.getPolicyGroupId());
            policyGroup.setPolicyGroupId(pmPolicyGroup.getPolicyGroupId());
            policyGroup.setPolicyGroupCode(policyGroupCode);
            policyGroup.setDelivyDateFrom(pmPolicyGroup.getDelivyDateFrom());
            policyGroup.setDelivyDateTo(pmPolicyGroup.getDelivyDateTo());
            policyGroup.setBetterToWorseFlag(pmPolicyGroup.getBetterToWorseFlag());
            policyGroup.setDesignLengthMax(pmPolicyGroup.getDesignLengthMax());
            policyGroup.setInvertedFlag(pmPolicyGroup.getInvertedFlag());
            policyGroup.setOvercastDelivyDateTo(pmPolicyGroup.getOvercastDelivyDateTo());
            policyGroup.setOvercastQty(pmPolicyGroup.getOvercastQty());
            policyGroup.setYieldLower(pmPolicyGroup.getYieldLower());
    
            policyGroup.setContractAllowance(pmPolicyGroupAttr.getPriorityContractAllowance());
            policyGroup.setRemainingDelivyDateTo(pmPolicyGroupAttr.getPriorityDelivyDateTo());
            if(StringUtils.isNotBlank(pmPolicyGroup.getContractNums())) {
                policyGroup.setContractNumList(Arrays.asList(pmPolicyGroup.getContractNums().split(",")));
            }
            // 查询策略组下所有策略
            List<PmPolicy> pmPolicies = pmPolicyRepository.selectByPolicyGroupId(tenantId,
                    pmPolicyGroup.getPolicyGroupId());
            List<Long> policyIds = new ArrayList<>();
            for (PmPolicy pmPolicy : pmPolicies) {
                policyIds.add(pmPolicy.getPolicyId());
            }
    
            // 获取产线配置
            Map<String, Object> params = new HashMap<>();
            params.put(PmProductionLine.FIELD_VALID_FLAG, BaseConstants.ONE);
            
            Map<Long, PmProductionLine> pmProductionLineMap = initConfigSelectOne(PmProductionLine.class,pmProductionLineRepository,
                    PmProductionLine::getProdLineId, tenantId, params);
    
            // 设置合同指定断面
            Map<String, List<PmContractLineSpec>> pmContractLineSpecMap = initConfig(PmContractLineSpec.class ,pmContractLineSpecRepository,
                    PmContractLineSpec::getOrderNo, tenantId, null);
            policyGroup.setAssignMoldMap(Maps.newHashMap());
            for (Entry<String, List<PmContractLineSpec>> entry : pmContractLineSpecMap.entrySet()) {
                for (PmContractLineSpec contractLineSpec : entry.getValue()) {
                    PmProductionLine pmProductionLine = pmProductionLineMap.get(contractLineSpec.getAssignedProdLineId());
                    if(contractLineSpec.getMoldWid() == null || contractLineSpec.getMoldThick() == null) {
                        if (!policyGroup.getAssignMscLineMap().containsKey(entry.getKey())) {
                            policyGroup.getAssignMscLineMap().put(entry.getKey(), new HashSet<>());
                        }
                        policyGroup.getAssignMscLineMap().get(entry.getKey()).add(pmProductionLine.getProdLineCode());
                        continue;
                    }
                    String moldId = String.format(MOLD_ID_KEY, pmProductionLine.getProdLineCode(),
                            contractLineSpec.getMoldThick().toPlainString(), contractLineSpec.getMoldWid().toPlainString());
                    if (!policyGroup.getAssignMoldMap().containsKey(entry.getKey())) {
                        policyGroup.getAssignMoldMap().put(entry.getKey(), new HashSet<>());
                    }
                    policyGroup.getAssignMoldMap().get(entry.getKey()).add(moldId);
                }
            }
    
            // 获取产线断面关系数据
            Map<String, Object> paramsPmProdLineSection = new HashMap<>();
            paramsPmProdLineSection.put(PmProdLineSection.FIELD_VALID_FLAG, BaseConstants.ONE);
            Map<Long, List<PmProdLineSection>> lineNoAndMoldeRelMap = initConfig(PmProdLineSection.class,pmProdLineSectionRepository,
                    PmProdLineSection::getProdLineId, tenantId, paramsPmProdLineSection, PmProdLineSection.FIELD_PRIORITY);
            Map<String, Long> moldIdMapping = Maps.newHashMap();
            for (List<PmProdLineSection> pmProdLineSections : lineNoAndMoldeRelMap.values()) {
                for (PmProdLineSection pmProdLineSection : pmProdLineSections) {
                    moldIdMapping.put(String.format(MOLD_ID_KEY, pmProductionLineMap.get(pmProdLineSection.getProdLineId()).getProdLineCode(),
                            pmProdLineSection.getMoldThick().toPlainString(),
                            pmProdLineSection.getMoldWid().toPlainString()), pmProdLineSection.getProdLineId());
                }
            }
            // 策略指定断面
    
            Map<String, Object> policyParams = new HashMap<>();
            policyParams.put(PmPolicySection.FIELD_POLICY_ID, policyIds);
            Map<Long, List<PmPolicySection>> pmPolicySectionMap = initConfig(PmPolicySection.class ,pmPolicySectionRepository,
                    PmPolicySection::getPolicyId, tenantId, policyParams, PmPolicySection.FIELD_PRIORITY);
    
            // 加载【策略】组板方式
            Map<String, Object> pmPolicySLabMethodParmas = new HashMap<>();
            pmPolicySLabMethodParmas.put(PmPolicyVip.FIELD_POLICY_ID, policyIds);
            Map<Long, List<PmPolicySlabMethod>> pmPolicySLabMethodMap = initConfig(PmPolicySlabMethod.class,pmPolicySlabMethodRepository,
                    PmPolicySlabMethod::getPolicyId, tenantId, pmPolicySLabMethodParmas, PmPolicySlabMethod.FIELD_PRIORITY);
            
            // 加载产线组板方式表
            Map<Long , List<PmSlabMethod>> pmSlabMethodMap = initConfig(PmSlabMethod.class, pmSlabMethodRepository, PmSlabMethod::getProdLineId, tenantId, null, PmSlabMethod.FIELD_PRIORITY);;
            
            // 加载产线断面规格
            Map<String, Object> pmProdLineSectionSpecParam = new HashMap<>();
            pmProdLineSectionSpecParam.put(PmProdLineSection.FIELD_VALID_FLAG, BaseConstants.ONE);
            Map<Long, List<PmProdLineSectionSpec>> pmProdLineSectionSpecMap = initConfig(PmProdLineSectionSpec.class,pmProdLineSectionSpecRepository,
                    PmProdLineSectionSpec::getProdLineSectionId, tenantId, pmProdLineSectionSpecParam);
    
            // 重要客户
            Map<String, Object> policyVipPamms = new HashMap<>();
            policyVipPamms.put(PmPolicyVip.FIELD_POLICY_ID, policyIds);
            Map<Long, List<PmPolicyVip>> pmPolicyVipMap = initConfig(PmPolicyVip.class,pmPolicyVipRepository, PmPolicyVip::getPolicyId,
                    tenantId, policyVipPamms, PmPolicyVip.FIELD_PRIORITY);
            
            Map<String , Object> pmSdivStLimitBtParams = new HashMap<>();
            pmSdivStLimitBtParams.put(PmSdivStLimitBt.FIELD_VALID_FLAG, BaseConstants.ONE);
            Map<String , List<PmSdivStLimitBt>> pmSdivStLimitBtMap = initConfigDesc(PmSdivStLimitBt.class, pmSdivStLimitBtRepository, PmSdivStLimitBt::getStNo
                    , tenantId, pmSdivStLimitBtParams);
            policyGroup.setPmSdivStLimitBtMap(pmSdivStLimitBtMap);
            List<PmAutoBoardPolicyDTO> policyList = new ArrayList<>();
            policyGroup.setPolicyList(policyList);
            for (PmPolicy pmPolicy : pmPolicies) {
                PmAutoBoardPolicyDTO policy = new PmAutoBoardPolicyDTO();
                policy.setConsignCust(pmPolicy.getConsignCust());
                policy.setPolicyId(pmPolicy.getPolicyId());
                policy.setPolicyCode(pmPolicy.getPolicyCode());
                policy.setA2WidDeviation(pmPolicy.getA2WidDeviation());
                policy.setCustLowerEconomicLots(pmPolicy.getCustLowerEconomicLots());
                policy.setStLowerEconomicLots(pmPolicy.getStLowerEconomicLots());
                policy.setMscLineNo(pmPolicy.getProdLineCode());
                policy.setConsignCust(pmPolicy.getConsignCust());
                PmProductionLine pmProductionLine = pmProductionLineMap.get(pmPolicy.getProdLineId());
                policy.setProdLineProductivity(pmProductionLine.getProdLineProductivity());
                List<String> formModeCodeList = new ArrayList<>();
                if(!pmPolicySLabMethodMap.containsKey(pmPolicy.getPolicyId())) {
                    // 如果政策上没有，就从产线上找
                    if(!pmSlabMethodMap.containsKey(pmPolicy.getProdLineId())) {
                        throw new CommonException("政策" + pmPolicy.getPolicyCode() + "未配置组板方式！") ;
                    }
                    for (PmSlabMethod pmSlabMethod : pmSlabMethodMap.get(pmPolicy.getProdLineId())) {
                        formModeCodeList.add(pmSlabMethod.getComboSlabMethod());
                    }
                } else {
                    for (PmPolicySlabMethod pmPolicySlabMethod : pmPolicySLabMethodMap.get(pmPolicy.getPolicyId())) {
                        formModeCodeList.add(pmPolicySlabMethod.getComboSlabMethod());
                    }
                }
                // 组板方式
                policy.setFormModeCodeList(formModeCodeList);
    
                List<PmPolicyVip> vips = pmPolicyVipMap.get(pmPolicy.getPolicyId());
                policy.setCustomerVip(new HashMap<>(vips == null ? 1 : vips.size()));
                if(vips != null) {
                    for (PmPolicyVip vip : vips) {
                        policy.getCustomerVip().put(vip.getCustomerCode(), vip.getPriority());
                    }
                }
                
                List<PmAutoBoardMoldDTO> moldList = new ArrayList<>();
                policy.setMoldList(moldList);
                if (pmPolicySectionMap.containsKey(policy.getPolicyId())) {
                    // 如果策略指定了断面取策略上的
                    for (PmPolicySection pmPolicySection : pmPolicySectionMap.get(policy.getPolicyId())) {
                        PmAutoBoardMoldDTO pmAutoBoardMold = new PmAutoBoardMoldDTO();
                        pmAutoBoardMold.setMoldId(String.format(MOLD_ID_KEY, pmPolicy.getProdLineCode(),
                                pmPolicySection.getMoldThick().toPlainString(),
                                pmPolicySection.getMoldWid().toPlainString()));
                        pmAutoBoardMold.setMoldThick(pmPolicySection.getMoldThick());
                        pmAutoBoardMold.setMoldWidth(pmPolicySection.getMoldWid());
                        pmAutoBoardMold.setMscLineNo(pmPolicy.getProdLineCode());
                        pmAutoBoardMold.setPriority(pmPolicySection.getPriority());
                        Long prodLineSelectionId = moldIdMapping.get(pmAutoBoardMold.getMoldId());
                        pmAutoBoardMold.setMoldSpecs(pmProdLineSectionSpecMap.get(prodLineSelectionId));
                        moldList.add(pmAutoBoardMold);
                    }
                } else {
                    // 如果策略上没有指定，取产线断面关系表上的
                    for (PmProdLineSection pmProdLineSection : lineNoAndMoldeRelMap.get(pmPolicy.getProdLineId())) {
                        PmAutoBoardMoldDTO pmAutoBoardMold = new PmAutoBoardMoldDTO();
                        pmAutoBoardMold.setMoldId(String.format(MOLD_ID_KEY, pmPolicy.getProdLineCode(),
                                pmProdLineSection.getMoldThick().toPlainString(),
                                pmProdLineSection.getMoldWid().toPlainString()));
                        pmAutoBoardMold.setMoldThick(pmProdLineSection.getMoldThick());
                        pmAutoBoardMold.setMoldWidth(pmProdLineSection.getMoldWid());
                        pmAutoBoardMold.setMscLineNo(pmPolicy.getProdLineCode());
                        pmAutoBoardMold.setPriority(pmProdLineSection.getPriority());
                        moldList.add(pmAutoBoardMold);
                    }
                }
    
                policyList.add(policy);
            }
            
            List<Long> policyIdList = new ArrayList<>();
            policyGroup.setMscLineNoList(new HashSet<>());
            for (PmAutoBoardPolicyDTO pmAutoBoardPolicyDTO : policyGroup.getPolicyList()) {
                policyIdList.add(pmAutoBoardPolicyDTO.getPolicyId());
                policyGroup.getMscLineNoList().add(pmAutoBoardPolicyDTO.getMscLineNo());
            }

            // 查询所有钢种信息
            Map<Long, Set<String>> stNoMap = selectStNoByPolicyId(policyGroup , policyIdList, tenantId);
            
            // 合同分组配置
            policyGroup.setOrderGroup(
                    pmPolicyGroupRepository.selectContGroupConditionByGroupId(pmPolicyGroup.getPolicyGroupId(), tenantId));
            BigDecimal maxCombinePlateLen = pmPlateSpecBtRepository.maxCombinePlateLenMax(tenantId);
            policyGroup.setMaxCombinePlateLen(maxCombinePlateLen);
            // 评价指标
            policyGroup.setKpis(pmPolicyGrKpiRepository.selectByGroupId(pmPolicyGroup.getPolicyGroupId(), tenantId));
            if(async) {
                pmRequestInfo = pmRequestInfoService.createAutoBoardReqeust(policyGroup , "MANUAL", pmPolicyGroup, pmPolicyGroupAttr, tenantId);
            } else {
                pmRequestInfo = pmRequestInfoService.createAutoBoardReqeust(policyGroup , "AUTO", pmPolicyGroup, pmPolicyGroupAttr, tenantId);
            }
            
            executeAutoPd(stNoMap ,async ,policyGroup, pmRequestInfo , tenantId);
            long end = System.currentTimeMillis();
            log.info("组板执行耗时：" + (end - start));
        } catch (Exception e) {
            if(pmRequestInfo != null) {
                pmRequestInfoRepository.markExecFailed(pmRequestInfo, tenantId);
            }
            throw e;
        } finally {
            redisHelper.delKey(PM_AUTO_PD_EXECUTE_LOCK_KEY);
        }
    }

    private void executeAutoPd(Map<Long, Set<String>> stNoMap, Boolean async , PmAutoBoardPolicyGroupDTO policyGroup, PmRequestInfo pmRequestInfo, Long tenantId) {

        Map<String, List<PmSpeStAddvBt>> pmSpeStAddvBtMap = initConfigDesc(PmSpeStAddvBt.class,pmSpeStAddvBtRepository,
                PmSpeStAddvBt::getMscLineNo, tenantId, null ,PmSpeStAddvBt.FIELD_SG_SIGN);
        Map<String, List<PmPlateCutLenBt>> pmPlateCutLenBtMap = initConfigDesc(PmPlateCutLenBt.class ,pmPlateCutLenBtRepository,
                PmPlateCutLenBt::getMscLineNo, tenantId, null , PmPlateCutLenBt.FIELD_ROLL_MODE);
        Map<String, List<PmThickAddvBt>> pmThickAddvBtMap = initConfigDesc(PmThickAddvBt.class ,pmThickAddvBtRepository,
                PmThickAddvBt::getMscLineNo, tenantId, null,PmThickAddvBt.FIELD_TOL_TYPE_CODE ,PmThickAddvBt.FIELD_WHOLE_HEAT_TREATMENT_TYPE);
        Map<String, List<PmPlateTrimBt>> pmPlateTrimBtMap = initConfigDesc(PmPlateTrimBt.class ,pmPlateTrimBtRepository,
                PmPlateTrimBt::getMscLineNo, tenantId, null);
        Map<String, List<PmSlabSpecBt>> pmSlabSpecBtMap = initConfigDesc(PmSlabSpecBt.class ,pmSlabSpecBtRepository,
                PmSlabSpecBt::getRollLineNo, tenantId, null);
        Map<String, List<PmCutPlaceBt>> pmCutPlaceBtMap = initConfigDesc(PmCutPlaceBt.class ,pmCutPlaceBtRepository,
                PmCutPlaceBt::getRollLineNo, tenantId, null);
        Map<String, List<PmPlateSpecBt>> pmPlateSpecBtMap = initConfigDesc(PmPlateSpecBt.class ,pmPlateSpecBtRepository,
                PmPlateSpecBt::getRollLineNo, tenantId, null,PmPlateSpecBt.FIELD_ST_CODE , PmPlateSpecBt.FIELD_FORM_PLATE_MODE 
                ,PmPlateSpecBt.FIELD_AD_TYPE_MODE , PmPlateSpecBt.FIELD_ROLL_MODE , PmPlateSpecBt.FIELD_WHOLE_HEAT_TREATMENT_TYPE);
        Map<String, PmPdConstBt> pmPdConstBtMap = initConfigSelectOne(PmPdConstBt.class ,pmPdConstBtRepository, PmPdConstBt::getRollLineNo,
                tenantId, null);

        
        // 如果存在一个政策没有配置钢种信息，标记获取合同时不添加钢种条件
        boolean needQueryAllStNoData = false;
        Map<String , PmAutoBoardExecParam> allMoldMap = new HashMap<>(16);
        List<List<String>> moldIdList = new ArrayList<>();
        for (PmAutoBoardPolicyDTO policy : policyGroup.getPolicyList()) {
            if (!stNoMap.containsKey(policy.getPolicyId()) || stNoMap.get(policy.getPolicyId()) == null
                    || stNoMap.get(policy.getPolicyId()).isEmpty()) {
                needQueryAllStNoData = true;
            } else {
                policy.setStNoList(stNoMap.get(policy.getPolicyId()));
                policyGroup.getStNoList().addAll(stNoMap.get(policy.getPolicyId()));
            }
            List<String> moldIds = new ArrayList<>();
            for (PmAutoBoardMoldDTO mold : policy.getMoldList()) {
                moldIds.add(mold.getMoldId());
                PmAutoBoardExecParam pmAutoBoardExecParam = new PmAutoBoardExecParam(mold);
                String rollLineNo = Utils.mscLineNoToRoolLineNo(pmAutoBoardExecParam.getMscLineNo());
                pmAutoBoardExecParam.setPolicy(policy);
                pmAutoBoardExecParam.setPmSpeStAddvBts(pmSpeStAddvBtMap.get(pmAutoBoardExecParam.getMscLineNo()));
                pmAutoBoardExecParam.setPmPlateCutLenBts(pmPlateCutLenBtMap.get(pmAutoBoardExecParam.getMscLineNo()));
                pmAutoBoardExecParam.setPmPlateTrimBts(pmPlateTrimBtMap.get(pmAutoBoardExecParam.getMscLineNo()));
                pmAutoBoardExecParam.setPmThickAddvBts(pmThickAddvBtMap.get(pmAutoBoardExecParam.getMscLineNo()));
                pmAutoBoardExecParam.setPmCutPlaceBts(pmCutPlaceBtMap.get(rollLineNo));
                pmAutoBoardExecParam.setPmSlabSpecBts(pmSlabSpecBtMap.get(rollLineNo));
                pmAutoBoardExecParam.setPmPdConstBt(pmPdConstBtMap.get(rollLineNo));
                pmAutoBoardExecParam.setMoldSpecs(mold.getMoldSpecs());
                pmAutoBoardExecParam.initSteelSpecificationWeight();
                pmAutoBoardExecParam.setPmPlateSpecBts(pmPlateSpecBtMap.get(rollLineNo));
                pmAutoBoardExecParam.setRequestId(pmRequestInfo.getRequestId());
                allMoldMap.put(mold.getMoldId(), pmAutoBoardExecParam);
            }
            List<List<String>> tmpMoldIdList = new ArrayList<>();
            for (String moldId : moldIds) {
                List<String> initMoldIdList = new ArrayList<>();
                initMoldIdList.add(moldId);
                for (String moldId2 : moldIds) {
                    if(!moldId.equals(moldId2)) {
                        initMoldIdList.add(moldId2);
                    }
                }
                tmpMoldIdList.add(initMoldIdList);
            }
            if(moldIdList.isEmpty()) {
                moldIdList.addAll(tmpMoldIdList);
            } else {
                List<List<String>> tmpMoldIdListCopies = new ArrayList<>();
                for (List<String> origin : moldIdList) {
                    for (List<String> tmp : tmpMoldIdList) {
                        List<String> copies = new ArrayList<>(origin);
                        copies.addAll(tmp);
                        tmpMoldIdListCopies.add(copies);
                    }
                }
                moldIdList.clear();
                moldIdList.addAll(tmpMoldIdListCopies);
            }
        }
        // 准备断面执行版本数据
        List<PmAutoBoardExecParam> pmAutoBoardExecParams = new ArrayList<>(16);
        for (List<String> moldIds : moldIdList) {
            PmAutoBoardExecParam first = null;
            for (String moldId : moldIds) {
                PmAutoBoardExecParam pmAutoBoardExecParam = allMoldMap.get(moldId);
                PmAutoBoardExecParam copy = BeanUtils.copy(pmAutoBoardExecParam,
                        PmAutoBoardExecParam.class);
                copy.setLastNode(copy);
                if(first == null) {
                    first = copy;
                } else {
                    if(!first.getLastNode().getMscLineNo().equals(copy.getMscLineNo())) {
                        first.setMscLineNoChangeFlag(true);
                    }
                    first.addNode(copy);
                }
            }
            pmAutoBoardExecParams.add(first);
        }
        
        if (needQueryAllStNoData) {
            // 如果策略数据中存在无限定钢种的策略，则不加钢种条件
            policyGroup.setStNoList(null);
        }
        final CustomUserDetails customUserDetails = DetailsHelper.getUserDetails();
        // 准备参与组板的收池合同数据
        if(async) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    DetailsHelper.setCustomUserDetails(customUserDetails);
                    execute(async , pmAutoBoardExecParams, policyGroup, pmRequestInfo , tenantId);
                }
            }).start();
        } else {
            execute(async ,pmAutoBoardExecParams, policyGroup, pmRequestInfo , tenantId);
        }
    }
    
    /**
     * 执行
     * @param pmAutoBoardExecParams
     * @param policyGroup
     * @param pmRequestInfo 
     * @param tenantId
     */
    private void execute(Boolean async ,List<PmAutoBoardExecParam> pmAutoBoardExecParams , PmAutoBoardPolicyGroupDTO policyGroup, PmRequestInfo pmRequestInfo, Long tenantId) {
        try {
            Map<String, PmContBoardParam> orignData = initBoardContData(pmRequestInfo ,policyGroup, tenantId);
            List<Future<PmAutoBoardExecResult>> futureList = new ArrayList<>();
            int i = 1 ;
            for (PmAutoBoardExecParam pmAutoBoardExecParam : pmAutoBoardExecParams) {
                if(i > 64) {
                    // 最多只执行64个版本数据
                    break;
                }
                Future<PmAutoBoardExecResult> future = calcThreadPoolTaskExecutor
                        .submit(new PmAutoBoardExecCallable(pmAutoBoardExecParam, orignData, policyGroup , i++));
                futureList.add(future);
            }
            List<PmAutoBoardExecResult> results = new ArrayList<>(futureList.size());
            for (Future<PmAutoBoardExecResult> future : futureList) {
                try {
                    PmAutoBoardExecResult result = future.get();
                    results.add(result);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                    Thread.currentThread().interrupt();
                } catch (ExecutionException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if(results.size() > 4) {
                // TODO 评价指标
                for (PmAutoBoardExecResult pmAutoBoardExecResult : results) {
                    calcSocre(policyGroup, pmAutoBoardExecResult, pmRequestInfo);
                    CalcLog.setVersion(pmAutoBoardExecResult.getVersion());
                    CalcLog.log(pmAutoBoardExecResult.getBoardSummaryData());
                    log.info(pmAutoBoardExecResult.getBoardSummaryData());
                    CalcLog.clear();
                }
                results.sort(new Comparator<PmAutoBoardExecResult>() {
                    @Override
                    public int compare(PmAutoBoardExecResult o1, PmAutoBoardExecResult o2) {
                        return o2.getScore().compareTo(o1.getScore());
                    }
                });
                // 记录执行结果
                pmAutoPdResultService.saveCalcResult(pmRequestInfo, results.subList(0, 4), policyGroup, tenantId);
            } else {
                pmAutoPdResultService.saveCalcResult(pmRequestInfo, results, policyGroup, tenantId);
            }
        } catch (Exception e) {
            if(async) {
                pmRequestInfoRepository.markExecFailed(pmRequestInfo, tenantId);
            }
            throw e ;
        }
    }
    
    private void calcSocre(PmAutoBoardPolicyGroupDTO policyGroup , PmAutoBoardExecResult pmAutoBoardExecResult , PmRequestInfo pmRequestInfo ) {
        BigDecimal socre = BigDecimal.ZERO;
        for (PmPolicyGrKpi kpi : policyGroup.getKpis()) {
            if("YIELD".equals(kpi.getKpiType())) {
                if(pmAutoBoardExecResult.getSlabNoCount() != 0) {
                    BigDecimal yieldSocre = pmAutoBoardExecResult.getAverageYield().divide(BigDecimal.valueOf(pmAutoBoardExecResult.getSlabNoCount()) , PmPlateConstants.YIELD_SCALE, RoundingMode.HALF_UP);
                    socre = socre.add(yieldSocre.multiply(kpi.getKpiPercent()));
                }
            } else if("CONTRACT_AMOUNT".equals(kpi.getKpiType())) {
                if(policyGroup.getAllContractAllowance() != null && BigDecimal.ZERO.compareTo(policyGroup.getAllContractAllowance()) <= 0) {
                    BigDecimal ContractAllowanceSocre = pmAutoBoardExecResult.getContractAllowance().divide(policyGroup.getAllContractAllowance(), PmPlateConstants.YIELD_SCALE, RoundingMode.HALF_UP);
                    socre = socre.add(ContractAllowanceSocre.multiply(kpi.getKpiPercent()));
                }
            } else if("CONTRACT_REMAINING".equals(kpi.getKpiType())) {
                if(pmRequestInfo.getTotSmallPlateNum() == null && BigDecimal.ZERO.compareTo(pmRequestInfo.getTotSmallPlateNum()) <= 0) {
                    BigDecimal ContractAllowanceSocre = BigDecimal.valueOf(pmAutoBoardExecResult.getSpCount()).divide(pmRequestInfo.getTotSmallPlateNum(), PmPlateConstants.YIELD_SCALE, RoundingMode.HALF_UP);
                    socre = socre.add(ContractAllowanceSocre.multiply(kpi.getKpiPercent()));
                }
            }
        }
        pmAutoBoardExecResult.setScore(socre);
    }

    private Map<Long, Set<String>> selectStNoByPolicyId(PmAutoBoardPolicyGroupDTO policyGroup, List<Long> policyIdList, Long tenantId) {
        // 查询所有策略的钢种组钢种信息
        List<PmPolicyStDTO> pmPolicyStDTOs = pmPolicySteelGroupRepository.selectStNoList(policyIdList, tenantId);
        Map<Long, Set<String>> stNoMap = policyGroup.getStMap();
        Map<Long, String> stGroupMap = policyGroup.getStGroupMap();
        for (PmPolicyStDTO pmPolicyStDTO : pmPolicyStDTOs) {
            if(stGroupMap.containsKey(pmPolicyStDTO.getPolicyId())) {
                String groupCode = stGroupMap.get(pmPolicyStDTO.getPolicyId()) +","+pmPolicyStDTO.getSteelGroupCode();
                stGroupMap.put(pmPolicyStDTO.getPolicyId(), groupCode);
            } else {
                stGroupMap.put(pmPolicyStDTO.getPolicyId(), pmPolicyStDTO.getSteelGroupCode());
            }
            
            if (!stNoMap.containsKey(pmPolicyStDTO.getPolicyId())) {
                stNoMap.put(pmPolicyStDTO.getPolicyId(), new HashSet<>());
            }
            if (StringUtils.isNotBlank(pmPolicyStDTO.getStNo())) {
                stNoMap.get(pmPolicyStDTO.getPolicyId()).add(pmPolicyStDTO.getStNo());
            }
        }
        for (Entry<Long, Set<String>> entry : stNoMap.entrySet()) {
            if (entry.getValue() == null || entry.getValue().isEmpty()) {
                throw new CommonException("钢种组【" + stGroupMap.get(entry.getKey()) + "】中未配置有效的钢种");
            }
        }
        List<PmPolicySteelType> pmPolicySteelTypes = pmPolicySteelTypeRepository
                .selectByCondition(Condition.builder(PmPolicySteelType.class)
                        .andWhere(Sqls.custom().andEqualTo(PmPolicySteelType.FIELD_TENANT_ID, tenantId)
                                .andIn(PmPolicySteelType.FIELD_POLICY_ID, policyIdList))
                        .build());
        for (PmPolicySteelType pmPolicySteelType : pmPolicySteelTypes) {
            if (!stGroupMap.containsKey(pmPolicySteelType.getPolicyId())) {
                // 如果不存在钢种组，则继续取策略上维护的钢种
                if (!stNoMap.containsKey(pmPolicySteelType.getPolicyId())) {
                    stNoMap.put(pmPolicySteelType.getPolicyId(), new HashSet<>());
                }
                stNoMap.get(pmPolicySteelType.getPolicyId()).add(pmPolicySteelType.getStNo());
            }
        }
        return stNoMap;
    }

    /**
     * 初始化组板合同数据
     * 
     * @param request
     * @return
     */
    private Map<String, PmContBoardParam> initBoardContData(PmRequestInfo pmRequestInfo ,PmAutoBoardPolicyGroupDTO policyGroup, Long tenantId) {
        List<PmContBoardParam> contBoardParams = pmProContRepository.selectAutoBoardContList(policyGroup, tenantId);
        policyGroup.setSummaryContractNum(new HashMap<>(contBoardParams.size()));
        List<PmRuntimeContract> pmRuntimeContracts = new ArrayList<>();
        Map<String , PmContBoardParam> maps = new HashMap<>();
        BigDecimal totSmallPlateNum = BigDecimal.ZERO; 
        for (PmContBoardParam pmContBoardParam : contBoardParams) {
            if(pmContBoardParam.getLockQty() != null && pmContBoardParam.getLockQty().compareTo(BigDecimal.ZERO) > 0) {
                pmContBoardParam.setPdLockQty(pmContBoardParam.getPdLockQty().subtract(pmContBoardParam.getLockQty()));
                if(pmContBoardParam.getPdLockQty().compareTo(BigDecimal.ZERO) < 0) {
                    // 如果锁定量不为空，减去锁定量，如果减去锁定量为负数后，取零，正常情况不会为负数
                    pmContBoardParam.setPdLockQty(BigDecimal.ZERO);
                }
            }
            if(!maps.containsKey(pmContBoardParam.getOrderNo())) {
                maps.put(pmContBoardParam.getOrderNo(), pmContBoardParam);
                if (policyGroup.getRemainingDelivyDateTo() != null
                        && pmContBoardParam.getDelivyDate().before(policyGroup.getRemainingDelivyDateTo())) {
                    String contractNo = Utils.removeLastThreeChars(pmContBoardParam.getOrderNo());
                    BigDecimal contractAllowance = pmContBoardParam.getContractAllowance() != null
                            ? pmContBoardParam.getContractAllowance()
                            : BigDecimal.ZERO;
                    policyGroup.getSummaryContractNum().put(contractNo, policyGroup.getSummaryContractNum()
                            .getOrDefault(contractNo, BigDecimal.ZERO).add(contractAllowance));
                }
                PmRuntimeContract pmRuntimeContract = new PmRuntimeContract();
                pmRuntimeContract.setRequestId(pmRequestInfo.getRequestId());
                pmRuntimeContract.setOrderNo(pmContBoardParam.getOrderNo());
                pmRuntimeContract.setTenantId(tenantId);
                pmRuntimeContract.setStNo(pmContBoardParam.getStNo());
                pmRuntimeContract.setQty(pmContBoardParam.getPdLockQty());
                pmRuntimeContracts.add(pmRuntimeContract);
                totSmallPlateNum = totSmallPlateNum.add(pmContBoardParam.getPdLockQty());
            }
            PmContBoardParam currentPmContBoardParam = maps.get(pmContBoardParam.getOrderNo());
            if(StringUtils.isNotBlank(pmContBoardParam.getMscLineNoStr())) {
                currentPmContBoardParam.getMscLineNo().add(pmContBoardParam.getMscLineNoStr());
            }
            currentPmContBoardParam.getAdTypeCodeMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getAdTypeCode());
            currentPmContBoardParam.getCutModeCodeMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getCutModeCode());
            currentPmContBoardParam.getHeadCutMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getHeadCut());
            currentPmContBoardParam.getRollDirectCodeMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getRollDirectCode());
            currentPmContBoardParam.getRollModeMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getRollMode());
            currentPmContBoardParam.getTailCutMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getTailCut());
            currentPmContBoardParam.getSlabThkMaxMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getSlabThkMax());
            currentPmContBoardParam.getSlabThkMinMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getSlabThkMin());
            currentPmContBoardParam.getWholeHeatTreatmentTypeMap().put(pmContBoardParam.getMscLineNoStr(), pmContBoardParam.getWholeHeatTreatmentType());
        }
        for (PmRuntimeContract pmRuntimeContract : pmRuntimeContracts) {
            String contractNo = Utils.removeLastThreeChars(pmRuntimeContract.getOrderNo());
            BigDecimal contractAllowance = policyGroup.getSummaryContractNum().get(contractNo);
            PmContBoardParam pmContBoardParam = maps.get(pmRuntimeContract.getOrderNo()) ;
            if(contractAllowance != null && policyGroup.getContractAllowance()  != null && contractAllowance.compareTo(policyGroup.getContractAllowance()) <= 0) {
                pmRuntimeContract.setRemainingFlag(Long.valueOf(BaseConstants.ONE));
                policyGroup.setAllContractAllowance(policyGroup.getAllContractAllowance().add(contractAllowance));
                pmContBoardParam.setContractAllowanceFlag(Long.valueOf(BaseConstants.ONE));
            } else {
                pmContBoardParam.setContractAllowanceFlag(0L);
                pmRuntimeContract.setRemainingFlag(0L);
            }
        }
        pmRequestInfo.setTotSmallPlateNum(totSmallPlateNum);
        batchInsertUtils.execBatchInsert(pmRuntimeContracts, PmRuntimeContract.class, "PM_RUNTIME_CONTRACT", 3000L);
        return maps;
    }

    @SuppressWarnings({  "rawtypes" })
    private <T, R> Map<R, T> initConfigSelectOne(Class<T> clazz ,BaseRepository<T> baseRepository, Function<T, R> groupBy,
            Long tenantId, Map<String, Object> params) {
        Sqls sqls = Sqls.custom().andEqualTo("tenantId", tenantId);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() instanceof List) {
                    sqls.andIn(entry.getKey(), (List) entry.getValue());
                } else {
                    sqls.andEqualTo(entry.getKey(), entry.getValue());
                }
            }
        }
        Map<R, T> maps = new HashMap<>();
        List<T> list = baseRepository.selectByCondition(Condition.builder(clazz).andWhere(sqls).build());
        if (list == null || list.isEmpty()) {
            return Maps.newHashMap();
        }
        for (T t : list) {
            maps.put(groupBy.apply(t), t);
        }
        return maps;
    }

    /**
     * 初始化全量配置，并按产线号分组
     * 
     * @param baseRepository
     * @param groupBy
     * @param tenantId
     * @return
     */
    @SuppressWarnings({ "rawtypes" })
    private <T, R> Map<R, List<T>> initConfig(Class<T> clazz ,BaseRepository<T> baseRepository, Function<T, R> groupBy, Long tenantId,
            Map<String, Object> params, String... orderBy ) {
        Sqls sqls = Sqls.custom().andEqualTo("tenantId", tenantId);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() instanceof List) {
                    sqls.andIn(entry.getKey(), (List) entry.getValue());
                } else {
                    sqls.andEqualTo(entry.getKey(), entry.getValue());
                }
            }
        }
        Condition.Builder builder = Condition.builder(clazz).andWhere(sqls);
        if(orderBy  != null && orderBy.length > 0) {
            builder.orderByAsc(orderBy);
        }
        List<T> list = baseRepository.selectByCondition(builder.build());
        if (list == null || list.isEmpty()) {
            return Maps.newHashMap();
        }
        return list.stream().collect(Collectors.groupingBy(groupBy));
    }
    
    /**
     * 初始化全量配置，并按产线号分组
     * 
     * @param baseRepository
     * @param groupBy
     * @param tenantId
     * @return
     */
    @SuppressWarnings({ "rawtypes" })
    private <T, R> Map<R, List<T>> initConfigDesc(Class<T> clazz ,BaseRepository<T> baseRepository, Function<T, R> groupBy, Long tenantId,
            Map<String, Object> params, String... orderBy ) {
        Sqls sqls = Sqls.custom().andEqualTo("tenantId", tenantId);
        if (params != null && !params.isEmpty()) {
            for (Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() instanceof List) {
                    sqls.andIn(entry.getKey(), (List) entry.getValue());
                } else {
                    sqls.andEqualTo(entry.getKey(), entry.getValue());
                }
            }
        }
        Condition.Builder builder = Condition.builder(clazz).andWhere(sqls);
        if(orderBy  != null && orderBy.length > 0) {
            builder.orderByDesc(orderBy);
        }
        List<T> list = baseRepository.selectByCondition(builder.build());
        if (list == null || list.isEmpty()) {
            return Maps.newHashMap();
        }
        return list.stream().collect(Collectors.groupingBy(groupBy));
    }

}
