package com.yida.data.device.telephone.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yida.data.common.core.entity.CurrentUser;
import com.yida.data.common.core.entity.apply.EduApplyProductOrder;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.entity.system.EduPayWxConfig;
import com.yida.data.common.core.entity.telephone.EduTelephoneCard;
import com.yida.data.common.core.entity.telephone.EduTelephoneOrder;
import com.yida.data.common.core.entity.user.EduParent;
import com.yida.data.common.core.entity.user.EduStudent;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.ExcelUtil;
import com.yida.data.common.core.utils.WxPayUtil;
import com.yida.data.common.service.CommonService;
import com.yida.data.device.dto.telephone.OrderPageDTO;
import com.yida.data.device.telephone.mapper.EduTelephoneOrderMapper;
import com.yida.data.device.telephone.service.EduTelephoneCardService;
import com.yida.data.device.telephone.service.EduTelephoneOrderService;
import com.yida.data.device.telephone.util.BalanceUtil;
import com.yida.data.device.telephone.util.GuuidUtil;
import com.yida.data.device.vo.telephone.OrderInfoVO;
import com.yida.data.device.vo.telephone.OrderPageVO;
import com.yida.data.school.vo.transcation.PayInfoVO;
import com.yida.data.user.feign.RemoteStudentService;
import com.yida.data.user.vo.ParentInfoVO;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 亲情电话-充值订单 Service实现
 *
 * @author zhanghaijun
 * @date 2021-08-10 17:00:48
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class EduTelephoneOrderServiceImpl extends ServiceImpl
        <EduTelephoneOrderMapper, EduTelephoneOrder> implements EduTelephoneOrderService {

    private static final ExecutorService ORDER_SUCCESS_POOL = new ThreadPoolExecutor(10, 20, 1,
            TimeUnit.MINUTES, new LinkedBlockingQueue<>());
    private final EduTelephoneOrderMapper eduTelephoneOrderMapper;
    @Resource
    private EduTelephoneCardService eduTelephoneCardService;
    @Resource
    private RemoteStudentService remoteStudentService;
    @Resource
    private RedisService redisService;
    @Resource
    private CommonService commonService;
    @Value("${febs.apiUrl}")
    private String apiUrl;

    @Override
    public IPage<OrderPageVO> listOrderPage(OrderPageDTO dto) {
        return baseMapper.listOrderPage(dto.toPage(), dto);
    }

    @Override
    public void exportOrder(OrderPageDTO dto, HttpServletResponse response) {
        List<OrderPageVO> voList = CollUtil.isNotEmpty(dto.getOrderList()) ? dto.getOrderList() :
                baseMapper.listOrder(dto);
        if (CollUtil.isNotEmpty(voList)) {
            for (OrderPageVO vo : voList) {
                vo.setPayPerson(vo.getPayPhone() + (StrUtil.isNotBlank(vo.getPayRelation()) ?
                        "(" + vo.getPayRelation() +
                                ")" : ""));
            }
        }
        ExcelUtil.export("订单记录", OrderPageVO.class, voList, response);
    }

    @Override
    public OrderInfoVO getOrder(Long id) {
        return baseMapper.getOrder(id);
    }

    @Override
    public void wxCall(Map<String, Object> res) {

        //String eventType = res.get("event_type").toString();
        //Map<String, Object> resource = (Map<String, Object>) res.get("resource");
        ////解密结果
        //try {
        //    JSONObject resJson =
        //            JSONUtil.parseObj(WxPayUtil.decodeText(resource.get("associated_data").toString(),
        //                    resource.get("nonce").toString(), resource.get("ciphertext").toString()));
        //    switch (eventType) {
        //        // 支付成功回调
        //        case WxPayConstant.PAY_NOTICE:
        //            paySuccess(resJson);
        //            break;
        //        // 退款回调
        //        case WxPayConstant.REFUND_SUCCESS_NOTICE:
        //        case WxPayConstant.REFUND_ABNORMAL_NOTICE:
        //        case WxPayConstant.REFUND_CLOSED_NOTICE:
        //            break;
        //        default:
        //    }
        //} catch (Exception e) {
        //    log.error("微信支付回调失败,data:[{}],msg:[{}]", res, e.getMessage());
        //}
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayInfoVO createOrder(Double money, Long icCardId, Integer payType, CurrentUser currentUser, String ip) {

        //生成订单未支付状态
        EduTelephoneOrder order = new EduTelephoneOrder();
        order.setOrderNo(String.valueOf(GuuidUtil.getUUID()));
        EduTelephoneCard telephoneCard = eduTelephoneCardService.getOne(Wrappers.lambdaQuery(new EduTelephoneCard())
                .eq(EduTelephoneCard::getId, icCardId));
        order.setCardId(icCardId);
        order.setMoney(money);
        order.setPayPhone(currentUser.getMobile());
        order.setCardNo(telephoneCard.getCardNo());
        EduStudent student = (EduStudent) redisService.hget(CachePrefixConstant.STUDENT_DATA, telephoneCard.getStudentId().toString());
        if (student==null){
            student=remoteStudentService.getStudentNoPermission(telephoneCard.getStudentId()).getData();
        }
        List<EduParent> parents = student.getParents();
        List<EduParent> collect =
                parents.stream().filter(parent -> parent.getMobile().equals(currentUser.getUsername())).collect(Collectors.toList());
        order.setPayRelation(collect.get(0).getParentType());
        order.setOrderStatus(0);
        order.setSchoolId(telephoneCard.getSchoolId());
        order.setAfterBalance(money);
        //生成商品订单
        EduApplyProductOrder eduApplyProductOrder = new EduApplyProductOrder();
        eduApplyProductOrder.setProductName("充值" + money + "元");
        eduApplyProductOrder.setProductTotalPrice(money);
        eduApplyProductOrder.setPraccticalMoney(money);
        eduApplyProductOrder.setOrderCode(order.getOrderNo());

        save(order);
        //发起支付
        String res = null;
        ParentInfoVO parent = remoteStudentService.getParentWithStudent(currentUser.getMobile(),
                null, currentUser.getDeptId()).getData();
        EduPayWxConfig config = commonService.getPayWxConfigBySchool(order.getSchoolId());
        try {
            switch (payType) {
                case 0:
                    res = WxPayUtil.placeOrderJs(config, eduApplyProductOrder, parent.getWxPublicOpenId(), ip,
                            apiUrl + "");
                    break;
                case 1:
                    res = WxPayUtil.placeOrderH5(config, eduApplyProductOrder, ip, apiUrl + "");
                    break;
                default:
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("make order fail,msg:[{}],stack:[{}]", e.getMessage(), e.getStackTrace());
            throw new FebsException("下单失败");
        }
        redisService.set(CachePrefixConstant.ORDER + order.getOrderNo(), order, 900L);
        redisService.set(CachePrefixConstant.ORDER_FLAG + order.getOrderNo(), res, 900L);

        if (order.getMoney() == 0D) {
            paySuccessUpdate(order.getOrderNo(), LocalDateTime.now());
        }
        PayInfoVO vo = new PayInfoVO();
        // 支付方式不同   返回信息也不同
        switch (payType) {
            case 0:
                vo = getWxJsSign(order.getOrderNo(), res);
        }
        vo.setOrderCode(order.getOrderNo());
        return vo;
    }

    @Override
    public void orderSuccess(String orderCode) {

        ORDER_SUCCESS_POOL.submit(() -> {
                    EduTelephoneOrder order = eduTelephoneOrderMapper.selectOne(Wrappers.lambdaQuery(new EduTelephoneOrder())
                            .eq(EduTelephoneOrder::getOrderNo, orderCode));
                    EduTelephoneCard card = eduTelephoneCardService.getById(order.getCardId());
                    if (ObjectUtil.isNotEmpty(order) && (order.getOrderStatus() == 0 || order.getOrderStatus() == 1)) {
                        EduPayWxConfig config = commonService.getPayWxConfigBySchool(order.getSchoolId());
                        while (LocalDateTimeUtil.between(order.getCreateDate(), LocalDateTime.now()).getSeconds() < 900L) {
                            try {
                                JSONObject resJson = JSONUtil.parseObj(WxPayUtil.queryOrder(config,
                                        orderCode));
                                String tradeState = resJson.getStr("trade_state");
                                if ("NOTPAY".equals(tradeState)) {
                                    continue;
                                }
                                switch (tradeState) {
                                    // 支付成功情况
                                    case "SUCCESS":
                                    case "ACCEPT":
                                        order.setOrderStatus(2);
                                        order.setPayDate(LocalDateTime.now());
                                        Double money = order.getMoney();
                                        Double decode = BalanceUtil.decode(card.getBalance());
                                        BigDecimal bigDecimal = new BigDecimal(money);
                                        BigDecimal bigDecimal1 = new BigDecimal(decode);
                                        BigDecimal add = bigDecimal.add(bigDecimal1);
                                        String encode = BalanceUtil.encode(add.doubleValue());
                                        order.setAfterBalance(add.doubleValue());
                                        card.setBalance(encode);
                                        card.setId(order.getCardId());
                                        card.setBalance(encode);
                                        eduTelephoneCardService.updateById(card);

                                        break;
                                    // 未完成支付，订单作废
                                    case "CLOSED":
                                    case "REVOKED":
                                    case "PAYERROR":
                                        order.setOrderStatus(4);
                                        break;
                                    default:
                                }
                                if (order.getOrderStatus() != 0 && order.getOrderStatus() != 1) {
                                    eduTelephoneOrderMapper.updateById(order);
                                    break;
                                }
                            } catch (Exception e) {
                                log.error("order success fail,msg:[{}],stack:[{}]", e.getMessage(), e.getStackTrace());
                            }
                        }
                    }
                }
        );
    }

    @Override
    public PayInfoVO getWxJsSign(String orderCode, String prepayId) {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = RandomUtil.randomString(32);
        String packge = "prepay_id=" + prepayId;
        EduTelephoneOrder order = eduTelephoneOrderMapper.selectOne(Wrappers.<EduTelephoneOrder>lambdaQuery()
                .eq(EduTelephoneOrder::getOrderNo, orderCode));
        EduPayWxConfig config = commonService.getPayWxConfigBySchool(order.getSchoolId());
        String code = config.getWxPublicId() + "\n" + timestamp + "\n" + nonceStr + "\n" + packge + "\n";
        Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, config.getApiCreditPrivateKeyNoHead(),
                null);
        byte[] res = sign.sign(code.getBytes());
        String signRes = Base64.encode(res);
        PayInfoVO infoVO = new PayInfoVO();
        infoVO.setWxAppId(config.getWxPublicId());
        infoVO.setWxSign(signRes);
        infoVO.setWxTimestamp(timestamp);
        infoVO.setWxPackage(packge);
        infoVO.setWxNonceeStr(nonceStr);
        return infoVO;
    }

    private void paySuccess(JSONObject jsonObject) {
        String orderCode = jsonObject.getStr("out_trade_no");
        String tradeState = jsonObject.getStr("trade_state");
        String successTime = jsonObject.getStr("success_time");
        String openid = jsonObject.getJSONObject("payer").getStr("openid");
        paySuccessUpdate(orderCode, DateUtil.parseLocalDateTime(successTime, "yyyy-MM-dd'T'HH:mm:ssXXX"));
    }

    private void paySuccessUpdate(String orderCode, LocalDateTime successTime) {
        // 从缓存中拿订单
        EduTelephoneOrder order = (EduTelephoneOrder) redisService.get(CachePrefixConstant.ORDER + orderCode);
        if (order == null) {
            order = getOne(Wrappers.lambdaQuery(new EduTelephoneOrder()).eq(EduTelephoneOrder::getOrderNo, orderCode));
        }
        order.setPayDate(successTime);
        order.setOrderStatus(2);
        updateById(order);

        redisService.del(CachePrefixConstant.ORDER, orderCode);
    }
}
