package com.ym.mec.biz.service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ym.mec.biz.dal.dao.SysConfigDao;
import com.ym.mec.biz.dal.dto.PaymentParam;
import com.ym.mec.biz.dal.entity.StudentPaymentOrder;
import com.ym.mec.biz.dal.entity.TenantConfig;
import com.ym.mec.biz.dal.entity.TenantOrderRecord;
import com.ym.mec.biz.dal.entity.TenantPaymentOrder;
import com.ym.mec.biz.dal.enums.DealStatusEnum;
import com.ym.mec.biz.service.HfMerchantConfigService;
import com.ym.mec.biz.service.OrderPayOpsService;
import com.ym.mec.biz.service.StudentPaymentOrderService;
import com.ym.mec.biz.service.TenantConfigService;
import com.ym.mec.biz.service.TenantOrderRecordService;
import com.ym.mec.biz.service.TenantPaymentOrderService;
import com.ym.mec.common.exception.BizException;
import com.ym.mec.common.page.WrapperUtil;
import com.ym.mec.thirdparty.adapay.ConfigInit;
import com.ym.mec.thirdparty.adapay.Payment;
import com.ym.mec.thirdparty.adapay.entity.HfMerchantConfig;
import com.ym.mec.util.date.DateUtil;
import com.ym.mec.util.http.HttpUtil;
import com.ym.mec.util.json.JsonUtil;

/**
 * @author hgw
 * Created by 2021-12-22
 */
@Service
public class OrderPayOpsServiceImpl implements OrderPayOpsService {
    private static final Logger log = LoggerFactory.getLogger(OrderPayOpsServiceImpl.class);

    @Autowired
    private StudentPaymentOrderService studentPaymentOrderService;
    @Autowired
    private TenantPaymentOrderService tenantPaymentOrderService;
    @Autowired
    private TenantOrderRecordService tenantOrderRecordService;
    
    @Autowired
    private HfMerchantConfigService hfMerchantConfigService;
    
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private TenantConfigService tenantConfigService;
    @Autowired
    private SysConfigDao sysConfigDao;

    //tenant 机构开通、续费付款、 cloudTeacherOrder 团练宝激活支付、tenantRecharge 机构充值
    private static final String[] tenantPlatform = {"tenant", "cloudTeacherOrder", "tenantRecharge"};
    //校验订单状态
    private static final BiPredicate<Object, DealStatusEnum> predicate = (o, s) -> Objects.nonNull(o) && !DealStatusEnum.ING.equals(s);

    @Override
    public Map<String, Object> executePayment(PaymentParam payParam) throws Exception {
        log.info("executePayment >>>>> {}", JSON.toJSONString(payParam));
        if (payParam.getAmount() == null) {
            throw new Exception("PaymentParam query is null");
        }
        //签名验证
        checkSing(payParam);
        //返回值
        Map<String, Object> payment;

        if (WrapperUtil.checkStr(payParam.getPlatform(), tenantPlatform)) {
            //tenant 机构开通、续费付款、 cloudTeacherOrder 团练宝激活支付、tenantRecharge 机构充值
            payment = tenantPlatformOrder(payParam);
            //主动延迟检查订单
            delayCheckTenant();
        } else {
            //如果以上操作都没有 则应该是这一个，因为老代码就是这样写的。
            payment = student(payParam);
        }

        return payment;
    }

    private void checkSing(PaymentParam param) {
        
        //查询商户支付账户
        HfMerchantConfig hfMerchantConfig = hfMerchantConfigService.queryByTenantId(param.getTenantId());
        if(hfMerchantConfig == null){
        	throw new BizException("请配置机构的汇付商户信息");
        }
        Map<String, Object> signParams = new LinkedHashMap<>();
        signParams.put("appId", hfMerchantConfig.getAppId());
        signParams.put("amount", param.getAmount().setScale(2, RoundingMode.HALF_UP));
        signParams.put("orderNo", param.getOrderNo());
        signParams.put("notifyUrl", param.getNotifyUrl());
        signParams.put("returnUrl", param.getReturnUrl());
        signParams.put("orderSubject", param.getOrderSubject());
        signParams.put("orderBody", param.getOrderBody());
        signParams.put("wxAppId", hfMerchantConfig.getWxAppId());
        String originalStr = JSONObject.toJSONString(signParams);
        String newSign = DigestUtils.md5DigestAsHex(originalStr.getBytes());
        if (!newSign.equals(param.getSign())) {
            log.info("executePayment >>>>>> checkSing : {}", newSign);
            throw new BizException("请勿非法请求");
        }
    }

    //机构开通、续费付款、 团练宝激活支付、机构充值
    private Map<String, Object> tenantPlatformOrder(PaymentParam payParam) {
        //查询订单
        TenantOrderRecord tenantOrderRecord = tenantOrderRecordService.getOne(new WrapperUtil<TenantOrderRecord>()
                .hasEq("order_no_", payParam.getOrderNo()).queryWrapper());
        if (Objects.isNull(tenantOrderRecord) || tenantOrderRecord.getTenantId().intValue() != payParam.getTenantId()) {
            throw new BizException("订单不存在");
        }
        if (tenantOrderRecord.getOrderState() != 0) {
            throw new BizException("订单已处理！");
        }
        //获取支付数据
        Map<String, Object> payment;
        try {
            payment = checkOrderAndGetParam(payParam,
                    tenantOrderRecord,
                    TenantOrderRecord::getEnumOrderState,
                    TenantOrderRecord::getCreatedTime,
                    tenantOrderRecord::setTransNo,
                    tenantOrderRecordService::updateById
            );
        } catch (Exception e) {
            throw new BizException(e.getMessage());
        }
        return payment;
    }

    //teacher
    private Map<String, Object> teacher(PaymentParam payParam) {
        Map<String, Object> payment;
        TenantPaymentOrder tenantPaymentOrder = tenantPaymentOrderService.queryByOrderNo(payParam.getOrderNo());
        if (Objects.isNull(tenantPaymentOrder)) {
            throw new BizException("订单不存在");
        }
        payParam.setTenantId(1);
        try {
            payment = checkOrderAndGetParam(payParam,
                    tenantPaymentOrder,
                    TenantPaymentOrder::getStatus,
                    TenantPaymentOrder::getCreateTime,
                    tenantPaymentOrder::setTransNo,
                    tenantPaymentOrderService::update
            );
        } catch (Exception e) {
            throw new BizException(e.getMessage());
        }
        return payment;
    }

    //platform is null 或者 student
    private Map<String, Object> student(PaymentParam payParam) throws Exception {
        Map<String, Object> payment;
        StudentPaymentOrder studentPaymentOrder = studentPaymentOrderService.findOrderByOrderNo(payParam.getOrderNo());
        if (Objects.isNull(studentPaymentOrder) || studentPaymentOrder.getTenantId().intValue() != payParam.getTenantId()) {
            throw new BizException("订单不存在");
        }
        
        payment = checkOrderAndGetParam(payParam,
                studentPaymentOrder,
                StudentPaymentOrder::getStatus,
                StudentPaymentOrder::getCreateTime,
                studentPaymentOrder::setTransNo,
                studentPaymentOrderService::update
        );
        return payment;
    }

    private <T> Map<String, Object> checkOrderAndGetParam(PaymentParam payParam, T clazz,
                                                          Function<T, DealStatusEnum> enumFunc, Function<T, Date> dateFunc,
                                                          Consumer<String> setOption, Consumer<T> action) throws Exception {
        if (Objects.isNull(clazz)) {
            throw new BizException("订单不存在");
        }
        //校验订单状态
        checkOrderState(clazz, enumFunc);
        //获取订单创建时间
        Date createTime = dateFunc.apply(clazz);
        //校验订单是否超时
        String timeExpire = checkOrderTimeOut(createTime);
        
        //查询商户支付账户
        HfMerchantConfig hfMerchantConfig = hfMerchantConfigService.queryByTenantId(payParam.getTenantId());
        if(hfMerchantConfig == null){
        	throw new BizException("请配置机构的汇付商户信息");
        }
        
        //初始化支付数据
        Map<String, Object> paymentParams = getPaymentParam(payParam, clazz, hfMerchantConfig.getPlatformPayeeMemberId());
        paymentParams.put("time_expire", timeExpire);
        paymentParams.put("expend", getExpend(hfMerchantConfig, payParam));
        paymentParams.put("notify_url", payParam.getNotifyUrl());
        //向第三方发起支付，获取第三方的支付的信息
        Map<String, Object> payment = Payment.executePayment(hfMerchantConfig.getAppId(), hfMerchantConfig.getMerKey(), paymentParams);
        log.info("executePayment create order param >>>>> {}", JSON.toJSONString(payment));
        String transNo = (String) payment.get("id");
        //修改流水
        setTransNoByApply(transNo, clazz, setOption, action);

        return payment;
    }

    private <T> void checkOrderState(T obj, Function<T, DealStatusEnum> func) {
        DealStatusEnum em = func.apply(obj);
        if (predicate.test(obj, em)) {
            String msg = em.equals(DealStatusEnum.SUCCESS) ? "订单已支付，请勿重复支付" : "订单已关闭，不能支付";
            throw new BizException(msg);
        }
    }

    private String checkOrderTimeOut(Date createTime) {
        Calendar beforeTime = Calendar.getInstance();
        beforeTime.add(Calendar.MINUTE, -28);// 28 分钟之前的时间
        Date beforeDate = beforeTime.getTime();
        if (createTime.before(beforeDate)) {
            throw new BizException("订单已超时,请重新下单");
        }
        Date expireDate = DateUtil.addMinutes(createTime, 30);
        return new SimpleDateFormat("yyyyMMddHHmmss").format(expireDate);
    }

    private <T> void setTransNoByApply(String TransNo, T clazz, Consumer<String> setOption, Consumer<T> action) {
        setOption.accept(TransNo);
        action.accept(clazz);
    }

    private Map<String, Object> getExpend(HfMerchantConfig hfMerchantConfig, PaymentParam payParam) throws Exception {
        Map<String, Object> expendParams = new HashMap<>(5);
        String openId = "";
        if (payParam.getPayChannel().equals("wx_pub")) {
            //微信支付则获取openId
            openId = getOpenId(hfMerchantConfig, payParam.getCode());
        }
        expendParams.put("open_id", openId);
        expendParams.put("is_raw", "1");
        //expendParams.put("callback_url", payParam.getReturnUrl());
        expendParams.put("limit_pay", "1");
        return expendParams;
    }

    private String getOpenId(HfMerchantConfig hfMerchantConfig, String code) throws Exception {
        String openId;
        if (code == null || code.isEmpty()) {
            throw new BizException("微信支付请先授权");
        }
        String wxMpOAuth2AccessTokenUrl = String.format(ConfigInit.wxMpOAuth2AccessTokenUrl, hfMerchantConfig.getWxAppId(), hfMerchantConfig.getWxAppSecret(), code);
        Map<String, String> weChatRes = JSON.parseObject(HttpUtil.get(wxMpOAuth2AccessTokenUrl, new HashMap<>()), Map.class);
        if (!weChatRes.containsKey("openid")) {
            throw new BizException("授权失败,请重新授权");
        }
        openId = weChatRes.get("openid");
        return openId;
    }

    private <T> Map<String, Object> getPaymentParam(PaymentParam payParam, T clazz, String platformAccount) {
        Map<String, Object> paymentParams = new HashMap<>();
        paymentParams.put("order_no", payParam.getOrderNo());
        paymentParams.put("pay_channel", payParam.getPayChannel());
        paymentParams.put("pay_amt", payParam.getAmount().setScale(2, RoundingMode.HALF_UP));
        paymentParams.put("goods_title", payParam.getOrderSubject());
        paymentParams.put("goods_desc", payParam.getOrderBody());

        TenantConfig tenantConfig = tenantConfigService.queryByTenantId(payParam.getTenantId());
        if (tenantConfig == null) {
            throw new BizException("机构[{}]信息找不到", payParam.getTenantId());
        }

        if (clazz instanceof StudentPaymentOrder || clazz instanceof TenantPaymentOrder) {

            if (payParam.getTenantId() == 1) {
                // 延时分账
                paymentParams.put("pay_mode", "delay");
            } else {
                Function<BigDecimal, BigDecimal> amountTo = (a) -> a
                        .multiply(tenantConfig.getChargeRate())
                        .divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP);

                StudentPaymentOrder st = (StudentPaymentOrder) clazz;
                BigDecimal amount = amountTo.apply(st.getActualAmount());

                List<Map<String, Object>> divMemberList = new ArrayList<>();
                // 实时分账
                if (amount.doubleValue() > 0) {

    				/*SysConfig config = sysConfigService.findByParamName("platform_collection_organ");
    		        if (Objects.isNull(config)) {
    		            throw new BizException("平台收款账户没有设置[platform_collection_organ]");
    		        }

    	        	SysPaymentConfig sysPaymentConfig = sysPaymentConfigService.findPaymentConfigByOrganId(Integer.parseInt(config.getParanValue()));

    	        	if(sysPaymentConfig == null || StringUtils.isBlank(sysPaymentConfig.getHfMerNo())){
    	        		throw new BizException("分部[{}]没有设置收款账号", config.getParanValue());
    	        	}*/

                    //String platformAccount = sysConfigDao.findConfigValue(SysConfigService.PLATFORM_PAYEE_ACCOUNT);

                    if (StringUtils.isBlank(platformAccount)) {
                        throw new BizException("平台收款账户查询失败");
                    }

                    Map<String, Object> divMember = new HashMap<>();
                    divMember.put("member_id", platformAccount);
                    divMember.put("amount", amount);
                    divMember.put("fee_flag", "Y");
                    divMemberList.add(divMember);
                }

                Map<String, Object> divMember1 = new HashMap<>();
                divMember1.put("member_id", st.getMerNos());
                divMember1.put("amount", payParam.getAmount().subtract(amount));
                if (divMemberList.size() > 0) {
                    divMember1.put("fee_flag", "N");
                } else {
                    divMember1.put("fee_flag", "Y");
                }
                divMemberList.add(divMember1);

                paymentParams.put("div_members", JsonUtil.toJSONString(divMemberList));
            }

        } else if (clazz instanceof TenantOrderRecord) {//向平台支付，不分账
            TenantOrderRecord tor = (TenantOrderRecord) clazz;

            List<Map<String, Object>> divMemberList = new ArrayList<>();
            Map<String, Object> divMember = new HashMap<>();
            divMember.put("member_id", tor.getMerNos());
            divMember.put("amount", tor.getActualAmount());
            divMember.put("fee_flag", "Y");
            divMemberList.add(divMember);

            paymentParams.put("div_members", JsonUtil.toJSONString(divMemberList));
        } else {
            throw new BizException("订单[{}]找不到", payParam.getOrderNo());
        }

        //手续费收取模式：O-商户手续费账户扣取手续费，I-交易金额中扣取手续费；值为空时，默认值为I；若为O时，分账对象列表中不支持传入手续费承担方
        //paymentParams.put("fee_mode", "I");
        return paymentParams;
    }

    //检查次数
    private static final int times = 50;
    //检查间隔 毫秒
    private static final long sleepTime = 3000L;
    //锁的时间
    private static final int lockTime = (int) (times * sleepTime / 1000 - 1);

    //主动检查机构像平台支付的订单的状态
    private void delayCheckTenant() {
        RBucket<Object> bucket = redissonClient.getBucket("delay_check_tenant");
        if (!bucket.trySet(1, lockTime, TimeUnit.SECONDS)) {
            return;
        }
        //异步
        CompletableFuture.runAsync(() -> {
            for (int i = 0; i <= times; i++) {
                try {
                    tenantOrderRecordService.checkTenantOrder();
                    Thread.sleep(sleepTime);//每3秒触发一次
                    log.info("delayCheckTenant >>>>>  start {}", i);
                } catch (InterruptedException e) {
                    log.error("delayCheckTenant >>>>", e.getCause());
                    return;
                }
            }
            bucket.delete();
        });
    }

}

