package com.sifang.modules.sys.util.insurance;

import com.jfinal.kit.HashKit;
import com.jfinal.plugin.activerecord.Db;
import com.sifang.modules.alioss.AliyunOSSClientUtil;
import com.sifang.modules.customer.constant.PartnerConstants;
import com.sifang.modules.customer.dao.CrmCustomerIncomingApplicationInsuranceDao;
import com.sifang.modules.customer.entity.CustomerEntry;
import com.sifang.modules.insurance.constants.InsuranceOrderConstants;
import com.sifang.modules.insurance.dao.*;
import com.sifang.modules.insurance.entity.*;
import com.sifang.modules.order.utils.Target;
import com.sifang.modules.order.utils.WebPageFunUtils;
import com.sifang.modules.sunshine.SystemSetting.dao.ProjectCompanyDao;
import com.sifang.modules.sunshine.SystemSetting.dao.ProjectSettingDao;
import com.sifang.modules.sunshine.SystemSetting.entity.CrmProjectSetting;
import com.sifang.modules.sunshine.SystemSetting.entity.ProjectCompanySettings;
import com.sifang.modules.sunshine.SystemSetting.utils.BusinessException;
import com.sifang.modules.sunshine.SystemSetting.utils.Result;
import com.sifang.modules.sunshine.SystemSetting.utils.StringUtil;
import com.sifang.modules.sunshine.partnerManagement.dao.PartnerDao;
import com.sifang.modules.sunshine.partnerManagement.entity.CrmPartner;
import com.sifang.modules.sys.common.UserUtils;
import com.sifang.modules.sys.common.entity.User;
import com.sifang.modules.sys.common.utils.IdGen;
import com.sifang.modules.sys.util.IOUtil;
import com.sifang.modules.sys.util.ScheduledService;
import com.sifang.modules.sys.util.insurance.form.CertFileInfo;
import com.sifang.modules.sys.util.insurance.form.InsuranceConfig;
import com.sifang.modules.sys.util.insurance.form.endorse.EndorseInsured;
import com.sifang.modules.sys.util.insurance.form.endorse.EndorseReq;
import com.sifang.modules.sys.util.insurance.form.endorse.EndorseResp;
import com.sifang.modules.sys.util.insurance.form.endorsequery.EndorseQueryReq;
import com.sifang.modules.sys.util.insurance.form.endorsequery.EndorseQueryResp;
import com.sifang.modules.sys.util.insurance.form.orderquery.OrderQueryReq;
import com.sifang.modules.sys.util.insurance.form.orderquery.OrderQueryResp;
import com.sifang.modules.sys.util.insurance.form.ordersubmit.*;
import com.sifang.modules.sys.util.insurance.form.upload.FileUploadReq;
import com.sifang.modules.sys.util.insurance.form.upload.FileUploadResp;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Service
public class InsuranceBizService {

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

    @Value("${img.domain}")
    private String filePrePath;

    @Autowired
    PartnerDao partnerDao;
    @Autowired
    ProjectCompanyDao projectCompanyDao;
    @Autowired
    InsuranceApiService apiService;
    @Autowired
    CustomerEntryInsuranceDao entryInsuranceDao;
    @Autowired
    CustomerEntryInsuranceInsuredDao entryInsuranceInsuredDao;
    @Autowired
    CustomerEntryInsuranceEndorseDao entryInsuranceEndorseDao;
    @Autowired
    ProjectSettingDao projectSettingDao;
    @Autowired
    InsuranceListDao insuranceListDao;
    @Autowired
    ScheduledService scheduledService;

    @Autowired
    private CrmCustomerIncomingApplicationInsuranceDao applicationInsuranceDao;

    @Autowired
    private CrmCustomerNoSystemEntryInsuranceDao noSystemEntryInsuranceDao;

    /**
     * 批改
     * // type   1投被保人 2标的地址 3保额 4保费 5特别约定第一受益人
     * //    注：类型125可以多传，3、4只能单独批改，多传时使用 | 隔开，如1|2|5
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> endorse(String type, String insuranceId, CustomerEntry entry) {
        String[] split = type.split("\\|");
        Set<String> modTypes = new HashSet<>(Arrays.asList(split));
        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_SUM_AMOUNT) && modTypes.size() > 1) {
            throw new BusinessException("保费只能单独批改！");
        }

        List<Consumer<CrmCustomerEntryInsurance>> updateHolder = new ArrayList<>();
        CrmCustomerEntryInsurance insurance = entryInsuranceDao.get(insuranceId);
        InsuranceConfig config = InsuranceConfig.load();
        EndorseReq req = new EndorseReq();
        req.setRequestNo(IdGen.uuid());
        req.setChannelCode(config.getChannelCode());
        req.setSign(HashKit.md5(req.getRequestNo() + config.getChannelCode() + config.getKey()));
        req.setEndorseType(type);
        req.setPolicyNo(insurance.getNumber());

        CrmInsuranceList insuranceSetting = getInsuranceSetting(entry);
        assert insuranceSetting != null;
        List<Insured> thisInsuredList = null;
        List<CrmCustomerEntryInsuranceInsured> prevInsuredList = null;
        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_INSURE) || modTypes.contains(InsuranceConst.ENDORSE_TYPE_REMARK)) {
            thisInsuredList = getInsuredList(insuranceSetting, entry, new ArrayList<>());
            prevInsuredList = entryInsuranceInsuredDao.findByInsuranceId(insuranceId);
        }

        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_INSURE)) {
            assert prevInsuredList != null;
            if (!insuredChanged(thisInsuredList, prevInsuredList)) {
                throw new BusinessException("投被保人无变化，无需批改！");
            }
            List<EndorseInsured> list = new ArrayList<>();
            for (Insured v : thisInsuredList) {
                EndorseInsured insured1 = new EndorseInsured();
                insured1.setIdentifyNumber(v.getIdentifyNumber());
                insured1.setInsuredFlag(v.getInsuredFlag());
                insured1.setInsuredName(v.getInsuredName());
                insured1.setIdentifyType(InsuranceConst.ID_TYPE_BIZ_LICENSE);
                insured1.setAddress(null);
                list.add(insured1);
            }
            req.setInsuredList(list);
            List<Insured> finalInsuredList = thisInsuredList;
            updateHolder.add(ins -> {
                // delete old insured list and recreate
                entryInsuranceInsuredDao.deleteByInsuranceId(insuranceId);
                ins.setInsurant(finalInsuredList.get(0).getInsuredName() + "," + finalInsuredList.get(1).getInsuredName());
                ins.setPolicyholder(finalInsuredList.get(2).getInsuredName());
                createInsuredList(finalInsuredList, ins);
            });
        }
        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_HOUSE_DETAIL)) {
            String address = entry.getProvince() + entry.getCity() + entry.getArea() + entry.getAddress();
            if (entry.getProvince().equals(entry.getCity())){
                address = entry.getProvince() + entry.getArea() + entry.getAddress();
            }
            if (Objects.equals(insurance.getAddress(), address)) {
                throw new BusinessException("标的地址无变化，无需批改！");
            }
            req.setAddressName(address);
            String finalAddress = address;
            updateHolder.add(ins -> ins.setAddress(finalAddress));
        }
        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_SUM_AMOUNT)) {
            if (insurance.getInsuranceMoney().compareTo(new BigDecimal(calcSumAmount(entry))) == 0) {
                throw new BusinessException("保费无变化，无需批改！");
            }
            req.setSumAmount(calcSumAmount(entry));
            updateHolder.add((ins) -> ins.setInsuranceMoney(new BigDecimal(req.getSumAmount())));
        }
        if (modTypes.contains(InsuranceConst.ENDORSE_TYPE_REMARK)) {
            String preInsured1 = prevInsuredList.get(0).getInsuredName();
            String preInsured2 = prevInsuredList.get(1).getInsuredName();
            Set<String> remark = new HashSet<>();
            assert thisInsuredList != null;
            String insured1 = thisInsuredList.get(0).getInsuredName();
            String insured2 = thisInsuredList.get(1).getInsuredName();
            remark.add(insured1);
            remark.add(insured2);
            if (remark.containsAll(Arrays.asList(preInsured1, preInsured2))) {
                throw new BusinessException("特别约定第一受益人无变化，无需批改！");
            }
            req.setRemark(insured1 + "及" + insured2);
            List<Insured> finalInsuredList = thisInsuredList;
            updateHolder.add((ins) -> {
                ins.setInsuranceName(insured1 + "," + insured2);
                ins.setPolicyholder(finalInsuredList.get(2).getInsuredName());
            });
        }

        IRet<EndorseResp> endorseRet = apiService.endorse(req);
        User user = UserUtils.getUser();
        String msg = "操作批改失败";
        if (isRespOk(endorseRet)) {
            EndorseResp data = endorseRet.getData();
            createEndorseRecord(insurance, data.getEndorseNo());
            insurance.setDownUrl2(data.getDownUrl());
            insurance.setDownUrl(data.getDownUrl());
            exchangePolicyUrl(insurance.getEntryId(), insurance.getId(), data.getDownUrl());
            insurance.setProcessDetail(appendOpDetail(user, insurance.getProcessDetail(), "操作批改成功"));
            insurance.setOrderStatus(InsuranceOrderConstants.PUSH_STATUS_MOD_OK);
            applyUpdate(updateHolder, insurance);
            updateSumPremium(config, data.getEndorseNo(), insurance);
            //原保单信息同步
            applicationInsuranceDao.updateEntityInfo(insurance);
        } else {
            insurance.setProcessDetail(appendOpDetail(user, insurance.getProcessDetail(), "操作批改失败"));
            insurance.setOrderStatus(InsuranceOrderConstants.PUSH_STATUS_MOD_FAIL);
            if (endorseRet != null) {
                msg = endorseRet.getMsg();
            }
        }
        return isRespOk(endorseRet) ? Result.ok() : Result.fail(msg);
    }

    private void updateApplicationInsuranceInfoSafe(CrmCustomerEntryInsurance insurance) {
        try {
            applicationInsuranceDao.updateEntityInfo(insurance);
        } catch (Exception ex) {
            log.error("update application insurance error", ex);
        }
    }

    private void updateApplicationNoSysInsuranceInfoSafe(CrmCustomerNoSystemEntryInsurance insurance) {
        try {
            applicationInsuranceDao.updateNoSysEntityInfo(insurance);
        } catch (Exception ex) {
            log.error("update application insurance error", ex);
        }
    }

    private void exchangePolicyUrl(String entryId, String insuranceId, String downUrl) {
        scheduledService.schedule(() -> {
            try {
                String url = uploadInsurancePolicy2OSS(insuranceId, downUrl);
                setDownUrl(insuranceId, url);
            } catch (Exception ex) {
                log.error("exchange insurance policy url error, insurance.id: {}", insuranceId, ex);
            }
        }, 90, TimeUnit.SECONDS);
    }

    @Transactional(rollbackFor = Exception.class)
    public void setDownUrl(String insuranceId, String url) {
        if (StringUtils.isNotBlank(url)) {
            Db.update("update crm_customer_entry_insurance set down_url=? where id=?", url, insuranceId);
            Db.update("update crm_customer_incoming_application_insurance set insurance_policy = ? where insurance_id = ?", url, insuranceId);
        }
    }

    /**
     * 更新保费
     */
    private void updateSumPremium(InsuranceConfig config, String endorseNo, CrmCustomerEntryInsurance insurance) {
        try {
            EndorseQueryReq req = new EndorseQueryReq();
            req.setCurrent(1);
            req.setRequestNo(IdGen.uuid());
            req.setEndorseNo(endorseNo);
            req.setChannelCode(config.getChannelCode());
            req.setSign(HashKit.md5(req.getRequestNo() + config.getChannelCode() + config.getKey()));
            IRet<EndorseQueryResp> ret = apiService.endorseQuery(req);
            if (isRespOk(ret)) {
                if (ret.getData() != null && !ret.getData().getItems().isEmpty()) {
                    String sumPremium = ret.getData().getItems().get(0).getSumPremium();
                    entryInsuranceDao.updatePrice(insurance.getInsuranceId(), sumPremium);
                    entryInsuranceEndorseDao.deleteByEndorseNo(endorseNo);
                }
            }
        } catch (Exception ex) {
            log.error("[保单批改] 更新保费失败", ex);
        }
    }

    private void createEndorseRecord(CrmCustomerEntryInsurance insurance, String endorseNo) {
        CrmCustomerEntryInsuranceEndorse r = new CrmCustomerEntryInsuranceEndorse();
        r.setEndorseNo(endorseNo);
        r.setInsuranceId(insurance.getId());
        r.setCreateTime(new Date());
        r.setStatus("0");
        entryInsuranceEndorseDao.save(r);
    }

    private void applyUpdate(List<Consumer<CrmCustomerEntryInsurance>> updateHolder, CrmCustomerEntryInsurance insurance) {
        if (updateHolder.isEmpty()) {
            return;
        }
        for (Consumer<CrmCustomerEntryInsurance> c : updateHolder) {
            c.accept(insurance);
        }
    }

    private boolean insuredChanged(List<Insured> currentInsuredList, List<CrmCustomerEntryInsuranceInsured> prevInsuredList) {
        Map<String, CrmCustomerEntryInsuranceInsured> prevMap = prevInsuredList.stream()
                .collect(Collectors.toMap(CrmCustomerEntryInsuranceInsured::getTargetId, e -> e, (a, b) -> a));
        for (Insured insured : currentInsuredList) {
            CrmCustomerEntryInsuranceInsured v = prevMap.get(insured.getTargetId());
            if (v == null) {
                return true;
            }
            if (!Objects.equals(v.getInsuredName(), insured.getInsuredName())
                    || !Objects.equals(v.getIdentifyNumber(), insured.getIdentifyNumber())
                    || !Objects.equals(v.getInsuredFlag(), insured.getInsuredFlag())
                    || !Objects.equals(v.getIdentifyType(), insured.getIdentifyType())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 推送保单失败后，重新推送。
     * 整体逻辑是：删除旧的保单并添加新的保单但是保持保单 id 不变
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> repush(CustomerEntry entry,CrmCustomerEntryInsurance insurance) {
        //List<CrmCustomerEntryInsurance> insurances = entryInsuranceDao.findByEntryId2(entry.getId());
        //CrmCustomerEntryInsurance insurance =insurances.get(0);
        if (StringUtils.equalsAny(insurance.getOrderStatus(), InsuranceOrderConstants.PUSH_STATUS_OK, InsuranceOrderConstants.PUSH_STATUS_MOD_OK)) {
            log.error("repush error, insurance exists, entry.id: {}", entry.getId());
            throw new BusinessException("保单已经生效，无需重新推送！");
        }
        InsuranceConfig config = InsuranceConfig.load();
        IRet<OrderQueryResp> ret = getOrder(entry.getId(), entry.getPowerStationCode(), config);
        if (isRespOk(ret) && ret.getData() != null && !ret.getData().getItems().isEmpty() && !"1".equals(insurance.getPolicyType())) {
            String policyStatus = ret.getData().getItems().get(0).getPolicyStatus();
            // 保单状态：0核保中，1生效，2期满，3投保失败，4核保未通过，5退保注销，-1其他
            if (Objects.equals("1", policyStatus) || Objects.equals("2", policyStatus)) {
                log.error("repush error, insurance exists, entry.id: {}", entry.getId());
                throw new BusinessException("保单已经存在，无需重新推送！");
            }
        }
        // delete old insurance and recreate
        String processDetail = insurance.getProcessDetail();
        String insuranceId = insurance.getId();
        entryInsuranceInsuredDao.deleteByInsuranceId(insuranceId);
        return push(entry, processDetail, insurance, UserUtils.getUser(),"0");
    }

    /**
     * force 表示如果无法获取必要信息则抛出异常。
     */
    private List<Insured> getInsuredList(CrmInsuranceList insuranceSetting, CustomerEntry entry, List<CertFileInfo> licenseUrls) {
        CrmPartner crmPartner = partnerDao.get(insuranceSetting.getInsurant()); // 被保人
        Insured insured1 = asInsured(crmPartner, licenseUrls);
        // 隐形被保人项目公司
        ProjectCompanySettings projectCompany = projectCompanyDao.get(entry.getProjectCompanyId());
        Insured insured2 = asInsured(projectCompany, InsuranceConst.INSURE_FLAG_INSURED, licenseUrls);
        ProjectCompanySettings epcCompany = projectCompanyDao.get(insuranceSetting.getPolicyholder()); // 投保人
        Insured policyHolder = asInsured(epcCompany, InsuranceConst.INSURE_FLAG_INSURE_PAYER, licenseUrls);
        return Arrays.asList(insured1, insured2, policyHolder);
    }

    private List<Insured> getInsuredListNoSys(CrmCustomerNoSystemEntryInsurance insurance, List<CertFileInfo> licenseUrls) {
        CrmPartner crmPartner = partnerDao.findByName(insurance.getPartnerName()); // 被保人
        Insured insured1 = asInsured(crmPartner, licenseUrls);
        // 隐形被保人项目公司
        ProjectCompanySettings projectCompany = projectCompanyDao.getByName(insurance.getProjectCompanyName());
        Insured insured2 = asInsured(projectCompany, InsuranceConst.INSURE_FLAG_INSURED, licenseUrls);
        ProjectCompanySettings epcCompany = projectCompanyDao.getByName(insurance.getPolicyholder()); // 投保人
        Insured policyHolder = asInsured(epcCompany, InsuranceConst.INSURE_FLAG_INSURE_PAYER, licenseUrls);
        return Arrays.asList(insured1, insured2, policyHolder);
    }

    /**
     * 推送保单到保险公司
     * 根据 entry 找到产品，根据产品关联的保险设置决定是否自动推送保单信息到保险公司
     *
     * @param old               只有在重新推送的时候需要
     * @param lastProcessDetail 只有在重新推送的时候需要
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> push(CustomerEntry entry, String lastProcessDetail, CrmCustomerEntryInsurance old, User user,String isFlag) {
        CrmInsuranceList insuranceSetting = getInsuranceSetting(entry);
        if (insuranceSetting == null) {
            log.info("[保单推送] 未查到保险设置，跳过推送保单。entry.id: {}", entry.getId());
            throw new BusinessException("未关联保险设置，无法推送保单！");
        }
        List<CrmCustomerEntryInsurance> list = entryInsuranceDao.findByEntryId(entry.getId());
        if(list.size() == 1){
            CrmCustomerEntryInsurance insurance = entryInsuranceDao.findByEntryId(entry.getId(),insuranceSetting.getType());
            if (insurance!= null && old == null) {
                log.info("[保单推送] 电站对应的保单已存在，跳过推送保单。entry.id: {}", entry.getId());
                return Result.fail("保单已存在");
            }
        }

        if (entry.getActualCapacity() == null || entry.getActualCapacity().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("实际装机容量必须大于0");
        }
        if (entry.getEpcPrice() == null || entry.getEpcPrice().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("epc单价必须大于0");
        }
        if (StringUtils.isAnyBlank(entry.getProvince(), entry.getCity(), entry.getArea(), entry.getAddress())) {
            throw new BusinessException("标的地址不能为空");
        }

        IRet<OrderSubmitResp> ret = null;
        InsuranceConfig config = InsuranceConfig.load();
        Insured policyholder = null;
        OrderSubmitReq req = new OrderSubmitReq();
        req.setRequestNo(entry.getId());
        List<Insured> insuredList = new ArrayList<>();
        Insured insured1 = null;
        Insured insured2 = null;
        HouseDetails details = new HouseDetails();
        // house details 取电站的省市区
        details.setProvince(entry.getProvince());
        details.setCity(entry.getCity());
        details.setCounty(entry.getArea());
        details.setAddress(entry.getAddress());
        req.setHouseDetailsList(Collections.singletonList(details));
        Date tomorrow = null;
        Date nextYear = null;

        try {
            List<CertFileInfo> licenseFiles = new ArrayList<>();
            insuredList = getInsuredList(insuranceSetting, entry, licenseFiles);
            validateInsureList(insuredList);
            validateCertInfoList(licenseFiles);
            validateAddress(insuredList);

            insured1 = insuredList.get(0);
            insured2 = insuredList.get(1);
            policyholder = insuredList.get(2);
            BaseInfo baseInfo = new BaseInfo();
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            tomorrow = DateUtils.addDays(new Date(), 1);
            baseInfo.setStartDate(df.format(tomorrow));
            nextYear = DateUtils.addYears(tomorrow, 1);
            baseInfo.setEndDate(df.format(nextYear));
            baseInfo.setSumAmount(calcSumAmount(entry));
            baseInfo.setSumQuantity("1");
            baseInfo.setRemark(insured1.getInsuredName() + "及" + insured2.getInsuredName());
            List<String> codeList = new ArrayList<>();
            codeList.add("3216");
            codeList.add("3132");
            codeList.add("3136");
            baseInfo.setChoose(codeList);
            req.setBaseInfo(baseInfo);
            req.setInsuredList(insuredList);
            req.setFileInfos(getFileInfos(config, licenseFiles));

            String orderNo = config.getChannelCode() + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
            req.setOrderNo(orderNo);
            req.setChannelCode(config.getChannelCode());
            req.setSign(genSign(req.getRequestNo(), config));
            req.setItemNo(entry.getPowerStationCode());
            req.setProductPlan(InsuranceProductPlan.matchProductPlan(entry.getCity(), entry.getProvince(), config));
            ret = apiService.orderSubmit(req);
        } catch (Exception ex) {
            log.error("[保单推送] order submit error, entry.id: {}", entry.getId(), ex);
            if (old != null) {
                throw ex;
            }
        }
        if (ret == null) {
            log.error("[保单推送] order submit error, ret is null, entry.id: {}", entry.getId());
        }

        OrderSubmitResp data = ret == null ? null : ret.getData();
        boolean isRespOk = isRespOk(ret);
        if (!isRespOk && isOrderExists(entry.getId(), entry.getPowerStationCode(), config)) {
            log.info("[保单推送] order submit error, order already exists");
            throw new BusinessException("保单已存在");
        }

        //User user = UserUtils.getUser();
        CrmCustomerEntryInsurance ins;
        if (old == null) {
            ins = new CrmCustomerEntryInsurance();
        } else {
            ins = old;
        }
        ins.setEntryId(entry.getId());
        ins.setRequestNo(entry.getId());
        ins.setItemNo(entry.getPowerStationCode());
        ins.setNumber(isRespOk ? data.getPolicyNo() : null);
        ins.setSource(InsuranceConst.INSURE_SRC_INTERFACE);
        ins.setInsuranceId(insuranceSetting.getId());
        ins.setInsuranceName(insuranceSetting.getName());
        ins.setInsuranceType(insuranceSetting.getType());
        ins.setInsuranceCompanyName(insuranceSetting.getCompanyName());
        if (policyholder != null) {
            ins.setPolicyholder(policyholder.getInsuredName());
        }
        if (insured1 != null && insured2 != null) {
            ins.setInsurant(insured1.getInsuredName() + "," + insured2.getInsuredName());
        }
        ins.setInsuranceMoney(new BigDecimal(calcSumAmount(entry)));
        ins.setNum(1);
        ins.setPrice(isRespOk ? new BigDecimal(data.getSumPremium()) : null);
        ins.setAddress(details.getProvince()+details.getCity()+details.getCounty()+details.getAddress());
        ins.setStartTime(tomorrow);
        ins.setEndTime(nextYear);
        ins.setCreateTime(old == null ? new Date() : old.getCreateTime());
        ins.setCreateBy(old == null ? user.getId() : old.getCreateBy());
        ins.setCreator(user.getUserName());
        ins.setUpdateTime(old == null ? null : new Date());
        ins.setUpdateBy(old == null ? null : user.getId());
        ins.setModifier(old == null ? null : user.getUserName());
        ins.setStatus(isRespOk ? InsuranceOrderConstants.STATUS_SUCCESS : InsuranceOrderConstants.STATUS_FAIL);
        ins.setOrderStatus(isRespOk ? InsuranceOrderConstants.PUSH_STATUS_OK : InsuranceOrderConstants.PUSH_STATUS_FAIL);
        ins.setRemarks(ret == null ? null : ret.getMsg());

        if("0".equals(isFlag)){
            ins.setPolicyType(InsuranceOrderConstants.POLICY_TYPE_FIRST);
        }else if("1".equals(isFlag)){
            ins.setPolicyType(InsuranceOrderConstants.POLICY_TYPE_CONTINUE);
        }

        ins.setProposalNo(isRespOk ? data.getProposalNo() : null);
        ins.setDownUrl2(isRespOk ? data.getDownUrl() : null);
        ins.setDownUrl(ins.getDownUrl2());
        ins.setOrderNo(req.getOrderNo());
        ins.setOrderTime(new Date());
        ins.setProcessDetail(appendOpDetail(user, lastProcessDetail, old == null ? "推送保单" : "重新推送保单"));
        entryInsuranceDao.save(ins);
        if (isRespOk) {
            exchangePolicyUrl(ins.getEntryId(), ins.getId(), ins.getDownUrl2());
        }
        createInsuredList(insuredList, ins);
        //原保单信息同步
        if (isRespOk) {
            updateApplicationInsuranceInfoSafe(ins);
        }
        return isRespOk ? Result.ok() : Result.fail(old == null ? "推送失败" : "重新推送失败");
    }

    @Transactional(rollbackFor = Exception.class)
    public Result<Void> rePushNoSystemEntryInsurance(String lastProcessDetail, CrmCustomerNoSystemEntryInsurance old, User user) {


        IRet<OrderSubmitResp> ret = null;
        InsuranceConfig config = InsuranceConfig.load();
        Insured policyholder = null;
        OrderSubmitReq req = new OrderSubmitReq();
        req.setRequestNo(old.getRequestNo());
        List<Insured> insuredList = new ArrayList<>();
        Insured insured1 = null;
        Insured insured2 = null;
        HouseDetails details = new HouseDetails();
        details.setProvince(old.getProvince());
        details.setCity(old.getCity());
        details.setCounty(old.getArea());
        details.setAddress(old.getAddress());
        req.setHouseDetailsList(Collections.singletonList(details));
        Date tomorrow = null;
        Date nextYear = null;

        try {
            List<CertFileInfo> licenseFiles = new ArrayList<>();
            insuredList = getInsuredListNoSys(old, licenseFiles);
            validateInsureList(insuredList);
            validateCertInfoList(licenseFiles);
            validateAddress(insuredList);

            insured1 = insuredList.get(0);
            insured2 = insuredList.get(1);
            policyholder = insuredList.get(2);
            BaseInfo baseInfo = new BaseInfo();
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            tomorrow = DateUtils.addDays(new Date(), 1);
            baseInfo.setStartDate(df.format(tomorrow));
            nextYear = DateUtils.addYears(tomorrow, 1);
            baseInfo.setEndDate(df.format(nextYear));
            baseInfo.setSumAmount(String.valueOf(old.getPrice()));
            baseInfo.setSumQuantity("1");
            baseInfo.setRemark(insured1.getInsuredName() + "及" + insured2.getInsuredName());
            List<String> codeList = new ArrayList<>();
            codeList.add("3216");
            codeList.add("3132");
            codeList.add("3136");
            baseInfo.setChoose(codeList);
            req.setBaseInfo(baseInfo);
            req.setInsuredList(insuredList);
            req.setFileInfos(getFileInfos(config, licenseFiles));

            String orderNo = config.getChannelCode() + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
            req.setOrderNo(orderNo);
            req.setChannelCode(config.getChannelCode());
            req.setSign(genSign(req.getRequestNo(), config));
            req.setItemNo(old.getItemNo());
            req.setProductPlan(InsuranceProductPlan.matchProductPlan(old.getAddress(), config));
            ret = apiService.orderSubmit(req);
        } catch (Exception ex) {
            log.error("[保单推送] order submit error, entry.id: {}", old.getRequestNo(), ex);
            if (old != null) {
                throw ex;
            }
        }
        if (ret == null) {
            log.error("[保单推送] order submit error, ret is null, entry.id: {}", old.getRequestNo());
        }

        OrderSubmitResp data = ret == null ? null : ret.getData();
        boolean isRespOk = isRespOk(ret);
        if (!isRespOk && isOrderExists(old.getRequestNo(), old.getItemNo(), config)) {
            log.info("[保单推送] order submit error, order already exists");
            throw new BusinessException("保单已存在");
        }

        //User user = UserUtils.getUser();
        CrmCustomerNoSystemEntryInsurance ins;
        if (old == null) {
            ins = new CrmCustomerNoSystemEntryInsurance();
        } else {
            ins = old;
        }
        ins.setRequestNo(old.getId());
        ins.setItemNo(old.getItemNo());
        ins.setNumber(isRespOk ? data.getPolicyNo() : null);
        ins.setSource(InsuranceConst.INSURE_SRC_INTERFACE);
        ins.setInsuranceId(old.getInsuranceId());
        ins.setInsuranceName(old.getInsuranceName());
        ins.setInsuranceType(old.getInsuranceType());
        ins.setInsuranceCompanyName(old.getInsuranceCompanyName());
        if (policyholder != null) {
            ins.setPolicyholder(policyholder.getInsuredName());
        }
        if (insured1 != null && insured2 != null) {
            ins.setInsurant(insured1.getInsuredName() + "," + insured2.getInsuredName());
        }
        ins.setInsuranceMoney(old.getInsuranceMoney());
        ins.setNum(1);
        ins.setPrice(isRespOk ? new BigDecimal(data.getSumPremium()) : null);
        ins.setProvince(details.getProvince());
        ins.setCity(details.getCity());
        ins.setArea(details.getCounty());
        ins.setAddress(details.getAddress());
        ins.setStartTime(tomorrow);
        ins.setEndTime(nextYear);
        ins.setCreateTime(old == null ? new Date() : old.getCreateTime());
        ins.setCreateBy(old == null ? user.getId() : old.getCreateBy());
        ins.setCreator(user.getUserName());
        ins.setUpdateTime(old == null ? null : new Date());
        ins.setUpdateBy(old == null ? null : user.getId());
        ins.setModifier(old == null ? null : user.getUserName());
        ins.setStatus(isRespOk ? InsuranceOrderConstants.STATUS_SUCCESS : InsuranceOrderConstants.STATUS_FAIL);
        ins.setOrderStatus(isRespOk ? InsuranceOrderConstants.PUSH_STATUS_OK : InsuranceOrderConstants.PUSH_STATUS_FAIL);
        ins.setRemarks(ret == null ? null : ret.getMsg());

        ins.setPolicyType(InsuranceOrderConstants.POLICY_TYPE_FIRST);
        ins.setProposalNo(isRespOk ? data.getProposalNo() : null);
        ins.setDownUrl2(isRespOk ? data.getDownUrl() : null);
        ins.setDownUrl(ins.getDownUrl2());
        ins.setOrderNo(req.getOrderNo());
        ins.setOrderTime(new Date());
        ins.setProcessDetail(appendOpDetail(user, lastProcessDetail, old == null ? "推送保单" : "重新推送保单"));
        noSystemEntryInsuranceDao.save(ins);
        if (isRespOk) {
            exchangePolicyUrl(ins.getRequestNo(), ins.getId(), ins.getDownUrl2());
        }
        createInsuredNoSysList(insuredList, ins);
        //原保单信息同步
        if (isRespOk) {
            updateApplicationNoSysInsuranceInfoSafe(ins);
        }
        return isRespOk ? Result.ok() : Result.fail(old == null ? "推送失败" : "重新推送失败");
    }

    public String uploadInsurancePolicy2OSS(String id, String insUrl) {
        if (StringUtils.isBlank(insUrl)) {
            return null;
        }
        long t = System.currentTimeMillis();
        String ret = null;
        try {
            byte[] bytes = IOUtil.urlAsBytes(insUrl, 30 * 1000);
            ret = AliyunOSSClientUtil.uploadImage(bytes, ".pdf", IdGen.uuid());
        } catch (IOException e) {
            log.error("[保单推送] 上传保单到 oss 失败, id: {}", id);
        }
        log.info("[保单推送] 上传保单耗时：{}ms", System.currentTimeMillis() - t);
        return ret;
    }

    private void validateAddress(List<Insured> insuredList) {
        required(insuredList.get(0).getAddress(), "被保人地址信息不完整");
        required(insuredList.get(1).getAddress(), "项目公司地址信息不完整");
        required(insuredList.get(2).getAddress(), "投保人地址信息不完整");
    }

    @NotNull
    private List<FileInfo> getFileInfos(InsuranceConfig config, List<CertFileInfo> licenseFiles) {
        List<FileInfo> fileInfos = new ArrayList<>();
        for (CertFileInfo licenseUrl : licenseFiles) {
            createReq(licenseUrl, config); // rely on side effect
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileId(licenseUrl.getFileId());
            fileInfo.setFileName(licenseUrl.getCertName());
            fileInfos.add(fileInfo);
        }
        return fileInfos;
    }

    private void validateCertInfoList(List<CertFileInfo> licenseFiles) {
        validateCertInfo(licenseFiles.get(0), "被保人");
        validateCertInfo(licenseFiles.get(1), "项目公司");
        validateCertInfo(licenseFiles.get(2), "投保人");
    }

    private void validateCertInfo(CertFileInfo certFileInfo, String msg) {
        required(msg + "营业执照不能为空", certFileInfo.getCertUrl());
        required(msg + "营业执照不能为空", certFileInfo.getCertName());
    }

    private void validateInsureList(List<Insured> insuredList) {
        required("被保人手机号不能为空", insuredList.get(0).getMobile());
        required("项目公司手机号不能为空", insuredList.get(1).getMobile());
        required("投保人手机号不能为空", insuredList.get(2).getMobile());
    }

    private String appendOpDetail(User user, String lastProcessDetail, String msg) {
        Target t = new Target();
        t.setD(com.sifang.modules.sys.common.utils.DateUtils.getDateTime());
        t.setP(user.getUserName());
        t.setC(user.getUserName() + msg);
        return WebPageFunUtils.appendOrderProcessDetail(t, lastProcessDetail);
    }

    private void createInsuredList(List<Insured> insuredList, CrmCustomerEntryInsurance ins) {
        for (Insured v : insuredList) {
            CrmCustomerEntryInsuranceInsured insured = new CrmCustomerEntryInsuranceInsured();
            insured.setInsuredType(v.getInsuredType());
            insured.setMobile(v.getMobile());
            insured.setIdentifyType(v.getIdentifyType());
            insured.setIsInsuredRelation(v.getIsInsuredRelation());
            insured.setInsuredFlag(v.getInsuredFlag());
            insured.setIdentifyNumber(v.getIdentifyNumber());
            insured.setInsuredNature(v.getInsuredNature());
            insured.setInsuredName(v.getInsuredName());
            insured.setInsuranceId(ins.getId());
            insured.setTargetId(v.getTargetId());
            entryInsuranceInsuredDao.save(insured);
        }
    }

    private void createInsuredNoSysList(List<Insured> insuredList, CrmCustomerNoSystemEntryInsurance ins) {
        for (Insured v : insuredList) {
            CrmCustomerEntryInsuranceInsured insured = new CrmCustomerEntryInsuranceInsured();
            insured.setInsuredType(v.getInsuredType());
            insured.setMobile(v.getMobile());
            insured.setIdentifyType(v.getIdentifyType());
            insured.setIsInsuredRelation(v.getIsInsuredRelation());
            insured.setInsuredFlag(v.getInsuredFlag());
            insured.setIdentifyNumber(v.getIdentifyNumber());
            insured.setInsuredNature(v.getInsuredNature());
            insured.setInsuredName(v.getInsuredName());
            insured.setInsuranceId(ins.getId());
            insured.setTargetId(v.getTargetId());
            entryInsuranceInsuredDao.save(insured);
        }
    }

    /**
     * power station code is the item no
     */
    private boolean isOrderExists(String entryId, String powerStationCode, InsuranceConfig config) {
        IRet<OrderQueryResp> ret = getOrder(entryId, powerStationCode, config);
        OrderQueryResp data = ret.getData();
        if (isRespOk(ret) && ret.getData() != null && data.getItems() != null && !data.getItems().isEmpty()) {
            String policyStatus = data.getItems().get(0).getPolicyStatus();
            if (Objects.equals("1", policyStatus) || Objects.equals("2", policyStatus)) { // 1.生效，2.期满
                log.info("[推送报单] order exists, entry.id: {}, status: {}", entryId, policyStatus);
                return true;
            }
        }
        return false;
    }

    private IRet<OrderQueryResp> getOrder(String entryId, String powerStationCode, InsuranceConfig config) {
        OrderQueryReq req = new OrderQueryReq();
        req.setCurrent(1);
        req.setRequestNo(IdGen.uuid());
        req.setChannelCode(config.getChannelCode());
        req.setSign(HashKit.md5(req.getRequestNo() + config.getChannelCode() + config.getKey()));
        req.setItemNo(powerStationCode);
        return apiService.orderQuery(req);
    }

    /**
     * 安装容量*epc单价
     */
    public String calcSumAmount(CustomerEntry entry) {
        BigDecimal epcPrice = entry.getEpcPrice();
        BigDecimal val = epcPrice.multiply(entry.getActualCapacity()).setScale(2, RoundingMode.HALF_UP);
        String ret = val.toPlainString();
        log.info("[保单推送] 电站总价：{}, entry.id: {}, epc: {}, cap: {}", ret, entry.getId(), epcPrice, entry.getActualCapacity());
        return ret;
    }

    private void required(String msg, String val) {
        if (StringUtils.isBlank(val)) {
            throw new BusinessException(msg);
        }
    }

    /**
     * @param company     可能是epc或者项目公司
     * @param insuredFlag insuredFlag
     */
    private Insured asInsured(ProjectCompanySettings company, String insuredFlag, List<CertFileInfo> licenseUrls) {
        Insured insured = new Insured();
        insured.setTargetId(company.getId());
        insured.setInsuredFlag(insuredFlag); // 被保险人
        insured.setIdentifyNumber(company.getEnterpriseQualificationNumber());
        insured.setInsuredName(company.getName());
        insured.setIdentifyType(InsuranceConst.ID_TYPE_BIZ_LICENSE);
        insured.setInsuredType(InsuranceConst.INSURE_TYPE_ENTERPRISE); // 单位
        insured.setMobile(company.getTelephone());
        insured.setAddress(getAddress(company.getProvince(), company.getCity(), company.getArea(), company.getAddress()));
        insured.setInsuredNature(InsuranceConst.INSURE_NATURE_LEGAL_PERSON); // 法人
        insured.setEmail(null);
        if (Objects.equals(insuredFlag, InsuranceConst.INSURE_FLAG_INSURE_PAYER)) {
            insured.setIsInsuredRelation(InsuranceConst.INSURE_REL_SELF); // 其他
        } else if (Objects.equals(insuredFlag, InsuranceConst.INSURE_FLAG_INSURED)) {
            insured.setIsInsuredRelation(InsuranceConst.INSURE_REL_OTHER); // 其他
        } else {
            throw new IllegalArgumentException("invalid insured flag: " + insuredFlag);
        }
        CertFileInfo fileInfo = new CertFileInfo();
        fileInfo.setCertUrl(company.getBusinessLicense());
        fileInfo.setCertName(company.getCertificationImgName());
        licenseUrls.add(fileInfo);
        return insured;
    }

    private Insured asInsured(CrmPartner partner, List<CertFileInfo> licenseUrls) {
        Insured insured = new Insured();
        insured.setTargetId(partner.getId());
        insured.setInsuredFlag(InsuranceConst.INSURE_FLAG_INSURED); // 被保险人
        insured.setIdentifyNumber(partner.getUnifiedCreditCode());
        insured.setInsuredName(partner.getName());
        insured.setIdentifyType(InsuranceConst.ID_TYPE_BIZ_LICENSE);
        insured.setInsuredType(InsuranceConst.INSURE_TYPE_ENTERPRISE); // 单位
        insured.setMobile(partner.getMobile());
        insured.setInsuredNature(InsuranceConst.INSURE_NATURE_LEGAL_PERSON); // 法人
        insured.setEmail(null);
        insured.setAddress(getAddress(partner.getProvince(), partner.getCity(), partner.getArea(), partner.getAddress()));
        insured.setIsInsuredRelation(InsuranceConst.INSURE_REL_OTHER); // 其他
        CertFileInfo fileInfo = new CertFileInfo();
        fileInfo.setCertUrl(partner.getCertificationImg());
        fileInfo.setCertName(partner.getCertificationImgName());
        licenseUrls.add(fileInfo);
        return insured;
    }

    private String getAddress(String province, String city, String area, String address) {
        if (StringUtils.isAnyBlank(province, city, area, address)) {
            return null;
        }
        return province + city + area + address;
    }

    private void createReq(CertFileInfo info, InsuranceConfig config) {
        log.info("[保单推送] 上传文件，url: {}, name: {}", filePrePath + info.getCertUrl(), info.getCertName());
        try {
            FileUploadReq req = new FileUploadReq();
            req.setRequestNo(IdGen.uuid());
            req.setChannelCode(config.getChannelCode());
            byte[] bytes = IOUtil.urlAsBytes(filePrePath + info.getCertUrl(), 30 * 1000);
            req.setFileBase64(Base64.getEncoder().encodeToString(bytes));
            req.setSign(HashKit.md5(req.getRequestNo() + config.getChannelCode() + config.getKey()));
            req.setFileName(info.getCertName());
            IRet<FileUploadResp> fileUploadRespIRet = apiService.fileUpload(req);
            if (isRespOk(fileUploadRespIRet)) {
                FileUploadResp data = fileUploadRespIRet.getData();
                info.setFileId(data.getFileId());
                info.setFileUrl(data.getFileUrl());
            } else {
                throw new BusinessException("上传营业执照失败");
            }
        } catch (IOException ex) {
            throw new BusinessException("上传营业执照失败");
        }
    }

    private boolean isRespOk(IRet<?> data) {
        return data != null && Objects.equals(data.getCode(), "000000");
    }

    private static String genSign(String reqNo, InsuranceConfig config) {
        return HashKit.md5(reqNo + config.getChannelCode() + config.getKey());
    }

    public CrmInsuranceList getInsuranceSetting(CustomerEntry entry) {
        String projectId = entry.getProjectId();
        CrmProjectSetting projectSetting = projectSettingDao.get(projectId);
        String insuranceId = projectSetting.getInsuranceId();
        String buyInsuranceFlag = projectSetting.getBuyInsuranceFlag();
        if ("1".equals(buyInsuranceFlag)) { // whether buy or not, 1. buy 0. not buy
            return insuranceListDao.get(insuranceId);
        }
        return null;
    }

    public CrmInsuranceList getInsuranceSettingList(String projectId) {
        CrmProjectSetting projectSetting = projectSettingDao.get(projectId);
        String insuranceId = projectSetting.getInsuranceId();
        String buyInsuranceFlag = projectSetting.getBuyInsuranceFlag();
        if ("1".equals(buyInsuranceFlag)) { // whether buy or not, 1. buy 0. not buy
            return insuranceListDao.get(insuranceId);
        }
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveInsurance(CustomerEntry entry, User user) {
        if(PartnerConstants.PARTNER_ID_GF.equals(entry.getPartnerId())) {
            ProjectCompanySettings companySettings = projectCompanyDao.get(entry.getProjectCompanyId());
            if (entryInsuranceDao.findByEntryId(entry.getId(),"4") != null) {
                log.info("保单已经存在:{}", entry.getId());
                return;
            }
            if (StringUtil.isAnyBlank(companySettings.getInsuranceId(), companySettings.getInsuranceName(), companySettings.getInsuranceUrl())) {
                throw new BusinessException("项目公司没有保单信息,不允许安装验收！");
            }
//            CrmInsuranceList insuranceSetting = getInsuranceSetting(entry);
//            Insured policyholder = null;
//            List<Insured> insuredList = new ArrayList<>();
//            Insured insured1 = null;
//            Insured insured2 = null;
//            List<CertFileInfo> licenseFiles = new ArrayList<>();
//            insuredList = getInsuredList(insuranceSetting, entry, licenseFiles);
//            insured1 = insuredList.get(0);
//            insured2 = insuredList.get(1);
//            policyholder = insuredList.get(2);
            //User user = UserUtils.getUser();
            CrmCustomerEntryInsurance ins = new CrmCustomerEntryInsurance();
            ins.setNoPush("1"); //不需要推送保司
            ins.setEntryId(entry.getId());
            ins.setRequestNo(entry.getId());
            ins.setItemNo(entry.getPowerStationCode());
            ins.setNumber(companySettings.getInsuranceId());
            ins.setSource(InsuranceConst.INSURE_FLAG_INSURED);
//            ins.setInsuranceId(insuranceSetting.getId());
            ins.setInsuranceName("工程安装一切险");
            ins.setInsuranceType("4");
            ins.setInsuranceCompanyName(companySettings.getInsuranceName());
          /*  if (policyholder != null) {
                ins.setPolicyholder(policyholder.getInsuredName());
            }
            if (insured1 != null && insured2 != null) {
                ins.setInsurant(insured1.getInsuredName() + "," + insured2.getInsuredName());
            }*/
//            ins.setInsuranceMoney(new BigDecimal(calcSumAmount(entry)));
            ins.setNum(1);
            ins.setCreateBy(user.getId());
            ins.setCreator(user.getUserName());
            ins.setUpdateTime(new Date());
            ins.setUpdateBy(user.getId());
            ins.setModifier(user.getUserName());
            ins.setStatus(InsuranceOrderConstants.STATUS_SUCCESS);
            ins.setOrderStatus(InsuranceOrderConstants.PUSH_STATUS_OK);
            ins.setDownUrl(companySettings.getInsuranceUrl());
            ins.setOrderTime(new Date());
            ins.setCreateTime(new Date());
            ins.setPolicyType("0");
            entryInsuranceDao.save(ins);
//            createInsuredList(insuredList, ins);
            updateApplicationInsuranceInfoSafe(ins);
        }
    }

    public static void main(String[] args) {
        if (StringUtil.isAnyBlank("1", "1","12156")) {
            throw new BusinessException("项目公司没有保单信息,不允许安装验收！");
        }
    }
}
