package cn.newfeifan.mall.module.pay.service.fuyouorder;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.newfeifan.mall.framework.common.exception.ErrorCode;
import cn.newfeifan.mall.framework.pay.core.client.PayClient;
import cn.newfeifan.mall.module.pay.dal.dataobject.app.PayAppDO;
import cn.newfeifan.mall.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.newfeifan.mall.module.pay.dal.dataobject.order.PayOrderDO;
import cn.newfeifan.mall.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.newfeifan.mall.module.pay.dal.mysql.order.PayOrderExtensionMapper;
import cn.newfeifan.mall.module.pay.dal.mysql.order.PayOrderMapper;
import cn.newfeifan.mall.module.pay.enums.order.PayOrderStatusEnum;
import cn.newfeifan.mall.module.pay.fuiou.reqdata.CommonQueryDataReq;
import cn.newfeifan.mall.module.pay.fuiou.reqdata.WxPreCreateDataReq;
import cn.newfeifan.mall.module.pay.fuiou.reqvo.SubmitOrderRequestVO;
import cn.newfeifan.mall.module.pay.fuiou.respVO.FuYouPayOrderSubmitRespVO;
import cn.newfeifan.mall.module.pay.fuiou.respVO.FuYouPaymentResponseVO;
import cn.newfeifan.mall.module.pay.fuiou.respVO.FuYouQueryPayOrderResponseVO;
import cn.newfeifan.mall.module.pay.service.app.PayAppService;
import cn.newfeifan.mall.module.pay.service.channel.PayChannelService;
import cn.newfeifan.mall.module.pay.service.order.PayOrderService;
import com.fasterxml.jackson.databind.ObjectMapper;


import cn.newfeifan.mall.module.pay.fuiou.util.DateUtils;
import cn.newfeifan.mall.module.pay.fuiou.util.FuiouHttpPoster;
import cn.newfeifan.mall.module.pay.fuiou.util.MD5;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

import java.lang.reflect.Field;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.newfeifan.mall.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.newfeifan.mall.module.pay.enums.DictTypeConstants.*;
import static cn.newfeifan.mall.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
import static cn.newfeifan.mall.module.pay.enums.ErrorCodeConstants.SUBMIT_ORDER_ERROR;

@Service
@Slf4j
public class FiYouPayOrderServiceImpl implements FuYouPayOrderService {

    @Value("${wx.mp.app-id}")
    private String appid;

    @Resource
    private PayOrderService payOrderService;
    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    private PayOrderExtensionMapper orderExtensionMapper;

    @Resource
    private PayAppService appService;

    @Resource
    private PayChannelService channelService;

    @Resource
    private PayOrderMapper orderMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String submitOrder(SubmitOrderRequestVO requestVO) {


        // 1.1 获得 PayOrderDO ，并校验其是否存在
        PayOrderDO payOrder = payOrderService.validateOrderCanSubmit(requestVO.getId());

        PayAppDO app = appService.validPayApp(payOrder.getAppId());

        payOrder.setPayOrderNo(generate(FUYOU_TRADE_ORDER_NO_PREFIX));
        payOrder.setNotifyUrl(app.getFuyouOrderNotifyUrl());
        payOrderService.updatePayOrder(payOrder);

        // 1.32 校验支付渠道是否有效
        PayChannelDO channel = validateChannelCanSubmit(payOrder.getAppId(), requestVO.getChannelCode());

        // 2. 插入 PayOrderExtensionDO
        String no = generate(ORDER_NO_PREFIX);
        PayOrderExtensionDO orderExtension = PayOrderExtensionDO.builder()
                .userIp(getClientIP())
                .orderId(payOrder.getId())
                .channelId(channel.getId())
                .channelCode(channel.getCode())
                .no(no)
                .status(PayOrderStatusEnum.WAITING.getStatus())
                .build();

        FuiouHttpPoster http = new FuiouHttpPoster();
        http.setCharset("utf-8");
        http.setUrl("https://aipay.fuioupay.com/aggregatePay/wxPreCreate");
        requestVO.getReq().setTerm_id(term_id);
        requestVO.getReq().setTerm_ip(term_ip);
        requestVO.getReq().setRandom_str(DateUtils.getCurrentDate("yyyyMMddHHmmss")
                + "568974");
        requestVO.getReq().setMchnt_cd(mchnt_cd);
        requestVO.getReq().setOrder_amt(String.valueOf(payOrder.getPrice()));
        requestVO.getReq().setGoods_des(payOrder.getBody());
        requestVO.getReq().setSub_appid(appid);
        requestVO.getReq().setMchnt_order_no(payOrder.getPayOrderNo());//不能重复
        requestVO.getReq().setTxn_begin_ts(DateUtils.getCurrentDate("yyyyMMddHHmmss"));
        requestVO.getReq().setNotify_url(app.getFuyouOrderNotifyUrl());
        requestVO.getReq().setVersion("1.0");
        String sb = requestVO.getReq().getMchnt_cd().trim() + "|" +
                requestVO.getReq().getTrade_type().trim() + "|" +
                requestVO.getReq().getOrder_amt().trim() + "|" +
                requestVO.getReq().getMchnt_order_no().trim() + "|" +
                requestVO.getReq().getTxn_begin_ts() + "|" +
                requestVO.getReq().getGoods_des() + "|" +
                requestVO.getReq().getTerm_id() + "|" +
                requestVO.getReq().getTerm_ip() + "|" +
                requestVO.getReq().getNotify_url() + "|" +
                requestVO.getReq().getRandom_str() + "|" +
                requestVO.getReq().getVersion() + "|" +
                privateKey;
        requestVO.getReq().setSign(MD5.MD5Encode(sb));

        orderExtension.setChannelExtras(convertToMap(requestVO.getReq()));

        String res;
        log.info("向富友发起微信支付请求 == 请求体：{}", JSON.toJSONString(requestVO.getReq()));
        try {
            res = http.newPost(JSON.toJSONString(requestVO.getReq()));

            orderExtension.setChannelResponse(res);
            orderExtensionMapper.insert(orderExtension);
            log.info("向富友发起微信支付请求 == 响应体：{}", res);
            // 解析返回结果,获取参数
            ObjectMapper mapper = new ObjectMapper();
            FuYouPaymentResponseVO resp = mapper.readValue(res, FuYouPaymentResponseVO.class);

            if (!resultCode.equals(resp.getResult_code())) {
                throw exception(SUBMIT_ORDER_ERROR);
            }

            FuYouPayOrderSubmitRespVO vo = FuYouPayOrderSubmitRespVO.builder()
                    .appId(resp.getSdk_appid())
                    .timeStamp(resp.getSdk_timestamp())
                    .nonceStr(resp.getSdk_noncestr())
                    .sdk_package(resp.getSdk_package())
                    .signType(resp.getSdk_signtype())
                    .paySign(resp.getSdk_paysign())
                    .build();
            return JSON.toJSONString(vo);
        } catch (Exception e) {
            log.error("向富友发起微信支付请求 == 异常：{}", e.getMessage());
            throw exception(SUBMIT_ORDER_ERROR);
        }
    }

    /**
     * 将对象转成Map
     *
     * @param req 支付请求对象
     * @return Map
     */
    private static Map<String, String> convertToMap(WxPreCreateDataReq req) {
        Map<String, String> orderMap = new HashMap<>();
        Class<?> orderClass = req.getClass();
        Field[] fields = orderClass.getDeclaredFields();

        try {
            for (Field field : fields) {
                field.setAccessible(true); // 设置可以访问私有字段
                String fieldName = field.getName();
                Object value = field.get(req); // 获取字段的值
                if (value != null) {
                    orderMap.put(fieldName, value.toString());
                }
            }
        } catch (IllegalAccessException e) {
            ErrorCode ERROR = new ErrorCode(1_007_901_006, "存储类型转换失败");
            throw exception(ERROR);
        }

        return orderMap;
    }

    private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) {
        // 校验 App
        appService.validPayApp(appId);
        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
        PayClient client = channelService.getPayClient(channel.getId());
        if (client == null) {
            log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw exception(CHANNEL_NOT_FOUND);
        }
        return channel;
    }

    private String generate(String prefix) {
        // 递增序号
        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
        String key = TRADE_NO + noPrefix;
        Long no = stringRedisTemplate.opsForValue().increment(key);
        // 设置过期时间
        stringRedisTemplate.expire(key, Duration.ofMinutes(1L));
        return noPrefix + no;
    }

    @Override
    public void afterSale(Long afterSaleId) {

    }

    @Override
    public PayOrderDO getPayOrderByStatus(PayOrderDO payOrderDO) {
        // 如果订单状态为待支付，则向富友查询订单状态
        if (payOrderDO.getStatus().equals(PayOrderStatusEnum.WAITING.getStatus())) {
            //修改程序,当微信支付回调的时候再次向富友请求查询pay订单的支付状态
            log.info("============支付完成查询:支付订单ID号{}", payOrderDO.getId());
            log.info("============支付完成查询:支付订单详情{}", payOrderDO);
            PayOrderExtensionDO payOrderExtensionDO = orderExtensionMapper.selectByPayId(payOrderDO.getId());

            // 修改订单状态
            updateOrderStatus(payOrderExtensionDO, payOrderService.getOrder(payOrderDO.getId()));
        }


        return orderMapper.selectById(payOrderDO.getId());
    }

    /**
     * 向富友查询支付订单状态
     *
     * @param payOrderExtensionDO 支付订单扩展信息
     * @param payOrderDO          支付订单信息
     */
    private void updateOrderStatus(PayOrderExtensionDO payOrderExtensionDO, PayOrderDO payOrderDO) {

        FuiouHttpPoster http = new FuiouHttpPoster();
        http.setCharset("utf-8");
        http.setUrl("https://aipay.fuioupay.com/aggregatePay/commonQuery");
        CommonQueryDataReq req = new CommonQueryDataReq();
        req.setVersion("1.0");
        req.setMchnt_cd(mchnt_cd);
        req.setRandom_str(DateUtils.getCurrentDate("yyyyMMddHHmmss")
                + "568974");
        req.setOrder_type(payOrderExtensionDO.getChannelCode());
        req.setMchnt_order_no(payOrderDO.getPayOrderNo());
        req.setTerm_id(term_id);
        String data = MD5.MD5Encode(req.getMchnt_cd() + "|" + req.getOrder_type() + "|" +
                req.getMchnt_order_no() + "|" + req.getTerm_id() + "|" + req.getRandom_str() + "|" +
                req.getVersion() + "|" + privateKey);
        req.setSign(data);

        String res;
        try {
            res = http.newPost(JSON.toJSONString(req));

            ObjectMapper mapper = new ObjectMapper();
            FuYouQueryPayOrderResponseVO resp = mapper.readValue(res, FuYouQueryPayOrderResponseVO.class);

            // 当响应结果为000000且交易状态为SUCCESS时才是支付成功
            if (resp.getResult_code().equals(resultCode) && resp.getTrans_stat().equals(transStatus)) {
                // 修改订单扩展信息
                payOrderExtensionDO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());

                // 修改订单信息
                payOrderDO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
                payOrderDO.setExtensionId(payOrderExtensionDO.getId());
                payOrderDO.setNo(payOrderExtensionDO.getNo());
                payOrderDO.setChannelOrderNo(resp.getTransaction_id());
                payOrderDO.setChannelUserId(resp.getBuyer_id());
                payOrderDO.setChannelId(payOrderExtensionDO.getChannelId());
                payOrderDO.setChannelCode(payOrderExtensionDO.getChannelCode());

                payOrderService.updatePayOrder(payOrderDO);
            } else {
                // 否则支付失败, 记录错误信息
                payOrderExtensionDO.setChannelErrorCode(resp.getResult_code());
                payOrderExtensionDO.setChannelErrorMsg(resp.getResult_msg());

            }
            payOrderExtensionDO.setChannelNotifyData(res);
            orderExtensionMapper.updateById(payOrderExtensionDO);

        } catch (Exception e) {
            ErrorCode ERROR = new ErrorCode(1_007_901_006, "查询支付订单失败");
            throw exception(ERROR);
        }
    }

}
