package com.izouma.nineth.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.anji.captcha.util.StringUtils;
import com.izouma.nineth.converter.CustomBeanResultTransformer;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.Order;
import com.izouma.nineth.dto.DivMoneyDTO;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.enums.CollectionSource;
import com.izouma.nineth.enums.DivStatus;
import com.izouma.nineth.enums.OrderStatus;
import com.izouma.nineth.enums.PayStatus;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.utils.DateTimeUtils;
import com.izouma.nineth.utils.JpaUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.*;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;

@Service
@AllArgsConstructor
@Slf4j
public class DivMoneyService {

    private EntityManager entityManager;

    private DivMoneyRepo divMoneyRepo;

    private AssetRepo assetRepo;
    private UserRepo userRepo;

    private UserBankCardRepo userBankCardRepo;

    private OrderRepo orderRepo;

    private PaymentInfoRepo paymentInfoRepo;

    /**
     * 查询条件构建
     * @param pageQuery
     * @return
     */
    private Specification<DivMoney>  generateSpecification(PageQuery pageQuery){
        String showUserData = Objects.toString(pageQuery.getQuery().get("showUserData"),"");
        Specification specification = JpaUtils.toSpecification(pageQuery, DivMoney.class);
        if("true".equals(showUserData)){
            specification = specification.and((root, query, criteriaBuilder) -> {
                return criteriaBuilder.isNotNull(root.get("userId"));
            });
        }

        Object payTime = pageQuery.getQuery().get("payTime");
        if(ObjectUtil.isNotEmpty(payTime)){
            List valList = (List)payTime;
            if(valList.size() != 2){
                throw new BusinessException("支付日期范围格式不正确");
            }
            LocalDateTime startTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(0), "yyyy-MM-dd HH:mm:ss");
            LocalDateTime endTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(1), "yyyy-MM-dd HH:mm:ss");
            specification = specification.and((root, query, criteriaBuilder) -> {
                Subquery subquery = query.subquery(PaymentInfo.class);
                Root subRoot = subquery.from(PaymentInfo.class);
                subquery.select(subRoot.get("id"));
                Predicate predicate = criteriaBuilder.between(subRoot.get("payTime"), startTime, endTime);
                predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("orderId"), subRoot.get("orderId")));
                subquery.where(predicate);
                return criteriaBuilder.exists(subquery);
            });
        }

        return specification;
    }
    /**
     * 列表数据
     * @param pageQuery
     * @return
     */
    public Page<DivMoney> all(PageQuery pageQuery) {

        Specification<DivMoney> specification = generateSpecification(pageQuery);
        Page<DivMoney> page = divMoneyRepo.findAll(specification, JpaUtils.toPageRequest(pageQuery));
        //检查，设置分账提示
        page.forEach((divMoney) -> {
            //订单检查
            Order order = orderRepo.findById(divMoney.getOrderId()).orElse(null);
            if(order == null){
                divMoney.setExportTips("订单记录未找到");
                return;
            }
            if(order.getStatus() != OrderStatus.FINISH){
                divMoney.setExportTips("订单状态不正确" + order.getStatus());
                return;
            }
            //支付信息检查
            PaymentInfo paymentInfo = paymentInfoRepo.findByOrderNo(order.getOrderNo()).orElse(null);
            if(paymentInfo == null){
                divMoney.setExportTips("支付记录不存在");
                return;
            }
            //设置第三方支付时间
            divMoney.setExportPayTime(paymentInfo.getPayTime());
            if(paymentInfo.getStatus() != PayStatus.HAVE_PAID){
                divMoney.setExportTips("支付状态不正确" + paymentInfo.getStatus());
                return;
            }

            if(divMoney.getUserId() != null){
                //只判断有用户分账的
                List<UserBankCard> userBankCardList = userBankCardRepo.findByUserId(divMoney.getUserId());
                if(userBankCardList.size() == 0){
                    divMoney.setExportTips("用户未绑定银行卡");
                    return;
                }

                if(userBankCardList.size() > 1){
                    divMoney.setExportTips("用户存在多张银行卡");
                    return;
                }

                UserBankCard userBankCard = userBankCardList.get(0);
                divMoney.setTransferBankNo(userBankCard.getBankNo());
                divMoney.setTransferBankName(userBankCard.getRealName());
            }

        });
        return page;
    }

    /**
     * excel分账数据
     * @param pageQuery
     * @return
     */
    public List<DivMoneyDTO>  getCheckData(PageQuery pageQuery){
        Object createdAt = pageQuery.getQuery().get("createdAt");
        Object payTime = pageQuery.getQuery().get("payTime");
        if (ObjectUtil.isEmpty(createdAt) && ObjectUtil.isEmpty(payTime)){
            throw new BusinessException("需要指定一个“创建时间范围”或”支付时间范围“");
        }


        String divStatus = Objects.toString(pageQuery.getQuery().get("status"), "");
        String userId = Objects.toString(pageQuery.getQuery().get("userId"), "");
        String orderId = Objects.toString(pageQuery.getQuery().get("orderId"), "");
        String payPlatform = Objects.toString(pageQuery.getQuery().get("payPlatform"), "");

        String sqlString = "select dm.id,dm.order_id,dm.user_id,dm.member_id,dm.total_amount,dm.amount,dm.ratio,dm.rest_amount," +
                "dm.fee_flag, dm.status,dm.created_at,dm.process_time," +
                " oi.order_no, oi.transaction_id, oi.name order_name, oi.total_price, oi.status order_status, oi.pay_method," +
                " pi.pay_platform, ubc.bank_name, ubc.bank_no, ubc.phone, ubc.real_name  " +
                "   from div_money dm join order_info oi on oi.id = dm.order_id " +
                "       join payment_info pi on pi.order_id = oi.id and pi.del = 0" +
                "       join user_bank_card ubc  on ubc.user_id = dm.user_id and ubc.del = 0" +
                " where oi.status in ('PROCESSING','FINISH') and pi.status = 'HAVE_PAID' " +
                "   and dm.del = 0 and dm.user_id is not null ";
        Map<String,Object> paramsMap = new LinkedHashMap<>();
        //创建时间范围
        if(ObjectUtil.isNotEmpty(createdAt)){
            List valList = (List)createdAt;
            if(valList.size() != 2){
                throw new BusinessException("创建日期范围格式不正确");
            }

            LocalDateTime startTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(0), "yyyy-MM-dd HH:mm:ss");
            LocalDateTime endTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(1), "yyyy-MM-dd HH:mm:ss");
            sqlString += " and dm.created_at between :createdStart and :createdEnd ";
            paramsMap.put("createdStart", startTime);
            paramsMap.put("createdEnd", endTime);
        }

        //支付方支付时间范围
        if(ObjectUtil.isNotEmpty(payTime)){
            List valList = (List)payTime;
            if(valList.size() != 2){
                throw new BusinessException("支付日期范围格式不正确");
            }
            LocalDateTime startTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(0), "yyyy-MM-dd HH:mm:ss");
            LocalDateTime endTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(1), "yyyy-MM-dd HH:mm:ss");
            sqlString += " and pi.pay_time between :payStart and :payEnd ";
            paramsMap.put("payStart", startTime);
            paramsMap.put("payEnd", endTime);
        }


        if(StringUtils.isNotBlank(divStatus)){
            sqlString += " and dm.status = :divStatus ";
            paramsMap.put("divStatus",divStatus);
        }
        if(StringUtils.isNotBlank(userId)){
            sqlString += " and dm.user_id = :userId ";
            paramsMap.put("userId",userId);
        }
        if(StringUtils.isNotBlank(orderId)){
            sqlString += " and dm.order_id = :orderId ";
            paramsMap.put("orderId",orderId);
        }
        if(StringUtils.isNotBlank(payPlatform)){
            sqlString += " and dm.pay_platform = :payPlatform ";
            paramsMap.put("payPlatform",payPlatform);
        }

        sqlString += " limit 5000 ";

        Query query = entityManager.createNativeQuery(sqlString);
        query.unwrap(org.hibernate.query.Query.class).setResultTransformer(CustomBeanResultTransformer.create(DivMoneyDTO.class));

        //设置参数
        paramsMap.forEach((pkey, pval) -> {
            query.setParameter(pkey, pval);
        });

        List<DivMoneyDTO> resultList = query.getResultList();
        return resultList;
    }

    /**
     * 记录分账操作
     * @param order 订单
     * @return
     */
    @Transactional
    public List<DivMoney> saveDivMoney(Order order, Collection collection, String payPlatform){
        //删除残留的分账记录
        divMoneyRepo.deleteByOrderId(order.getId());

        List<DivMoney> divMoneyList = new ArrayList<>();
        //减去GAS费开始分账
        BigDecimal orderAmount = order.getTotalPrice();
        BigDecimal totalAmount = order.getTotalPrice().subtract(order.getGasPrice());
        BigDecimal restAmount = order.getTotalPrice().multiply(BigDecimal.valueOf(1));
        User invitor = null;
        if (order.getInvitor() != null) {
            invitor = userRepo.findById(order.getInvitor()).orElse(null);
        }
        if(collection.getSource().equals(CollectionSource.TRANSFER)) {
            //转让分账
            Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("转让交易无资产记录"));
            User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("拥有者用户不存在"));
            //获取支付通道绑定的账户id
            UserBankCard bankCard = userBankCardRepo.findByUserIdWithPlatform(owner.getId(), payPlatform)
                    .stream().findAny().orElse(null);
            String thirdAccountId = null;
            if(bankCard != null){
                thirdAccountId = bankCard.getThirdAccountId();
            }

            int totalRatio = collection.getServiceCharge() + collection.getRoyalties();
            if (totalRatio >= 0) {
                // 扣除手续费、服务费
                int transferRatio = 100 - totalRatio;
                DivMoney divMoney = buildDivMoney(order.getId(), orderAmount, totalAmount, restAmount, owner,
                        thirdAccountId, transferRatio, false, payPlatform);
                divMoneyList.add(divMoney);
                restAmount = divMoney.getRestAmount();
            }
            DivMoney divMoney = buildRestMoney(order.getId(), orderAmount, totalAmount,restAmount, "0",
                    restAmount, true, payPlatform);
            if(divMoney != null){
                divMoneyList.add(divMoney);
                restAmount = divMoney.getRestAmount();
            }

        } else {
            //首发分账
            if (invitor != null && invitor.getShareRatio() != null
                    && invitor.getShareRatio().compareTo(BigDecimal.ZERO) > 0) {
                //获取支付通道绑定的账户id
                UserBankCard bankCard = userBankCardRepo.findByUserIdWithPlatform(invitor.getId(), payPlatform)
                        .stream().findAny().orElse(null);
                String thirdAccountId = null;
                if(bankCard != null){
                    thirdAccountId = bankCard.getThirdAccountId();
                }

                DivMoney divMoney = buildDivMoney(order.getId(), orderAmount, totalAmount, restAmount, invitor,
                        thirdAccountId,
                        invitor.getShareRatio().intValue(), false, payPlatform);
                divMoneyList.add(divMoney);
                restAmount = divMoney.getRestAmount();
            }

            DivMoney divMoney = buildRestMoney(order.getId(), orderAmount,  totalAmount, restAmount,
                    "0", restAmount, true, payPlatform);
            if(divMoney != null){
                divMoneyList.add(divMoney);
                restAmount = divMoney.getRestAmount();
            }

        }

        if (restAmount.compareTo(BigDecimal.ZERO) != 0) {
            log.error("分账出错 {}", JSON.toJSONString(divMoneyList, SerializerFeature.PrettyFormat));
            throw new BusinessException("分账出错");
        }

        //保存分账记录
        divMoneyRepo.saveAll(divMoneyList);

        return divMoneyList;

    }


    /**
     * 完成分账状态
     * @param orderId
     */
    @Transactional
    public void completeDivMoney(Long orderId){
        List<DivMoney> divMoneyList = divMoneyRepo.findByOrderId(orderId);
        divMoneyList.forEach(divMoney ->  {
            divMoney.setStatus(DivStatus.PROCESSED);
            divMoney.setProcessTime(LocalDateTime.now());
        });
    }


    private DivMoney buildDivMoney(Long orderId,BigDecimal orderAmount,
                                   BigDecimal totalAmount, BigDecimal restAmount,
                                   User divUser, String thirdAccountId, int ratio, boolean feeFlag,
                                   String payPlatform) {
        if (ratio < 0 || ratio > 100) {
            throw new BusinessException("分账比例错误");
        }

        BigDecimal divAmount = totalAmount.multiply(BigDecimal.valueOf(ratio))
                    .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_DOWN);

        DivMoney.DivMoneyBuilder builder = DivMoney.builder();
        builder.orderId(orderId).userId(divUser.getId())
                .memberId(divUser.getMemberId())
                .thirdAccountId(thirdAccountId)
                .orderAmount(orderAmount)
                .totalAmount(totalAmount).amount(divAmount)
                .restAmount(restAmount.subtract(divAmount))
                .ratio(ratio)
                .feeFlag(feeFlag)
                .status(DivStatus.PENDING)
                .payPlatform(payPlatform);
        return builder.build();

    }

    private DivMoney buildRestMoney(Long orderId,BigDecimal orderAmount,
                                    BigDecimal totalAmount, BigDecimal restAmount, String memberId, BigDecimal divAmount, boolean feeFlag,
                                    String payPlatform) {
        if (divAmount.compareTo(BigDecimal.ZERO) > 0) {
            DivMoney.DivMoneyBuilder builder = DivMoney.builder();
            builder.orderId(orderId).userId(null).memberId(memberId)
                    .orderAmount(orderAmount)
                    .totalAmount(totalAmount)
                    .amount(divAmount)
                    .restAmount(restAmount.subtract(divAmount))
                    .feeFlag(feeFlag).status(DivStatus.PENDING)
                    .payPlatform(payPlatform);
            return builder.build();
        }

        return null;
    }


    /**
     * 导出杉德分账表格
     *
     */
    @Transactional(rollbackOn = Exception.class)
    public void exportSandpayDiv(PageQuery pageQuery, HttpServletResponse response) throws Exception{
        InputStream sandpayTplStream  = DivMoneyService.class.getResourceAsStream("/xls/sandpayToUserTpl.xlsx");
        if(sandpayTplStream == null){
            throw new BusinessException("模板文件不存在");
        }

        //获取待导出数据
        List<DivMoneyDTO> dataList = getCheckData(pageQuery);
        //防止重复检查
        Set<String> divMoneyIds  = new HashSet<>();
        dataList.forEach(dto -> {
            if(divMoneyIds.contains(dto.getId())){
                throw new BusinessException("出现重复分账记录，请检查用户支付记录和银行卡,分账记录ID：" + dto.getId());
            }
            divMoneyIds.add(dto.getId());
            //标记已导出
            if(DivStatus.PENDING == dto.getStatus()){
                divMoneyRepo.updateDivStatus(Long.valueOf(dto.getId()), DivStatus.EXPORTED);
            }
        });



        String fileName = "杉德批量付款.xlsx";
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        response.setHeader("File-Name", URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));

        WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(sandpayTplStream).build();
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
        excelWriter.fill(dataList, fillConfig, writeSheet);
        excelWriter.finish();

    }


    /**
     * 更新分账记录状态
     * @param divMoneyId
     * @param divStatus
     */
    @Transactional
    public void updateDivStatus(Long divMoneyId, DivStatus divStatus){
        DivMoney divMoney = divMoneyRepo.findById(divMoneyId).orElseThrow(new BusinessException("记录不存在"));
        if(divMoney.getStatus() == DivStatus.PROCESSED || divMoney.getStatus() == DivStatus.ERROR){
            throw new BusinessException("记录'"+divMoneyId+"'的状态已经是最终状态，不可再变更");
        }

        divMoneyRepo.updateDivStatus(divMoneyId, divStatus);
    }

    /**
     * 更新分账记录结果
     * @param divMoneyId
     * @param transferResult
     */
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void updateDivResult(Long divMoneyId, String transferResult){
        divMoneyRepo.updateDivResult(divMoneyId, transferResult);
    }


    /**
     * 批量更新分账记录状态
     * @param pageQuery
     * @param divStatus
     */
    @Transactional
    public Integer batchUpdateDivResult(PageQuery pageQuery, DivStatus divStatus){
        Object createdAt = pageQuery.getQuery().get("createdAt");
        Object payTime = pageQuery.getQuery().get("payTime");
        if (ObjectUtil.isEmpty(createdAt) && ObjectUtil.isEmpty(payTime)){
            throw new BusinessException("需要指定一个“创建时间范围”或”支付时间范围“");
        }

        Specification<DivMoney> specification = generateSpecification(pageQuery);
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaUpdate criteriaUpdate = builder.createCriteriaUpdate(DivMoney.class);
        Root<DivMoney> root = criteriaUpdate.from(DivMoney.class);
        criteriaUpdate.set(root.get("status"), divStatus);
        criteriaUpdate.where(specification.toPredicate(root, builder.createQuery(), builder));
        Query query = entityManager.createQuery(criteriaUpdate);
        return query.executeUpdate();

    }

    /**
     * 获取待更新数量
     * @param pageQuery
     * @return
     */
    public Long countBatchMark(PageQuery pageQuery){
        Object createdAt = pageQuery.getQuery().get("createdAt");
        Object payTime = pageQuery.getQuery().get("payTime");
        if (ObjectUtil.isEmpty(createdAt) && ObjectUtil.isEmpty(payTime)){
            throw new BusinessException("需要指定一个“创建时间范围”或”支付时间范围“");
        }
        Specification<DivMoney> specification = generateSpecification(pageQuery);
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery  = builder.createQuery(Long.class);
        Root<DivMoney> root = criteriaQuery.from(DivMoney.class);
        criteriaQuery.select(builder.count(root).alias("num"));
        criteriaQuery.where(specification.toPredicate(root,criteriaQuery, builder));
        Query query = entityManager.createQuery(criteriaQuery);
        return (Long)query.getSingleResult();

    }

    /**
     * 统计金额信息
     * @param pageQuery
     * @return
     */
    public Map<String,Object> calculateMoneyInfo(PageQuery pageQuery){
        Specification specification = generateSpecification(pageQuery);

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
        Root<DivMoney> root = criteriaQuery.from(DivMoney.class);

        //查询退款金额
        Subquery refundQuery = criteriaQuery.subquery(BigDecimal.class);
        Root<PaymentInfo> refundRoot = refundQuery.from(PaymentInfo.class);
        Predicate subPredicate = criteriaBuilder.conjunction();
        subPredicate.getExpressions().add(criteriaBuilder.equal(refundRoot.get("status"), PayStatus.REFUNDED));
        Object payTime = pageQuery.getQuery().get("payTime");
        Object createdAt = pageQuery.getQuery().get("createdAt");
        if(ObjectUtil.isNotEmpty(createdAt) || ObjectUtil.isNotEmpty(payTime)){
            List valList = (List)(ObjectUtil.isNotEmpty(createdAt) ? createdAt : payTime);
            if(valList.size() != 2){
                throw new BusinessException("支付日期范围格式不正确");
            }
            LocalDateTime startTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(0), "yyyy-MM-dd HH:mm:ss");
            LocalDateTime endTime = DateTimeUtils
                    .toLocalDateTime((String) valList.get(1), "yyyy-MM-dd HH:mm:ss");
            subPredicate.getExpressions().add(criteriaBuilder.between(refundRoot.get("refundTime"), startTime, endTime));

        }
        refundQuery.select(criteriaBuilder.sum(refundRoot.get("payAmt")));
        refundQuery.where(subPredicate);


        criteriaQuery.multiselect(
                criteriaBuilder.sum(root.get("orderAmount")),
                criteriaBuilder.sum(root.get("totalAmount")),
                criteriaBuilder.sum(root.get("amount")),
                refundQuery
        );
        Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder);
        criteriaQuery.where(predicate);

        Query query = entityManager.createQuery(criteriaQuery);
        query.unwrap(org.hibernate.query.Query.class)
                .setResultTransformer(Transformers.TO_LIST);
        List list = (List) query.getSingleResult();
        Map<String,Object> resultMap = new LinkedHashMap<>();
        resultMap.put("orderAmount", list.get(0));
        resultMap.put("totalAmount", list.get(1));
        resultMap.put("amount", list.get(2));
        resultMap.put("refundAmount", list.get(3));

        //实际金额
        BigDecimal realAmount = BigDecimal.ZERO;
        if(list.get(0) != null){
            realAmount = new BigDecimal(list.get(0).toString()).multiply(new BigDecimal("0.992"));
        }
        resultMap.put("realAmount", realAmount);
        return resultMap;
    }

}
