package com.qxgmat.service.extend;

import com.nuliji.tools.AbstractService;
import com.nuliji.tools.Tools;
import com.nuliji.tools.exception.ParameterException;
import com.nuliji.tools.exception.SystemException;
import com.nuliji.tools.pay.PaySource;
import com.nuliji.tools.pay.common.PayInfo;
import com.nuliji.tools.pay.common.ResultInfo;
import com.nuliji.tools.pay.data.PayRequestData;
import com.nuliji.tools.pay.data.PayResponseData;
import com.qxgmat.data.constants.enums.module.PayModule;
import com.qxgmat.data.constants.enums.trade.PayChannel;
import com.qxgmat.data.constants.enums.trade.PayType;
import com.qxgmat.data.constants.enums.trade.TradeStatus;
import com.qxgmat.data.dao.PayMapper;
import com.qxgmat.data.dao.entity.Pay;
import com.qxgmat.data.dao.entity.UserPay;
import com.qxgmat.help.PayHelp;
import com.qxgmat.service.inline.PayService;
import com.qxgmat.service.inline.UserClassService;
import com.qxgmat.service.inline.UserPayService;
import com.qxgmat.service.inline.UserServiceService;
import com.qxgmat.util.annotation.Callback;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

/**
 * Created by gaojie on 2017/11/13.
 */
@Service
public class TradeService extends AbstractService {

    @Resource
    private PayHelp payHelp;

    @Resource
    private PayMapper payMapper;

    @Resource
    private PayService payService;

    @Resource
    private UserServiceService userServiceService;

    @Resource
    private UserClassService userClassService;

    private Map<PayModule, Callback> payCallback = new HashMap<>();;

    @Resource
    private UserPayService userPayService;

    public interface Source {
        String WECHAT = "wechat";
        String ALIPAY = "alipay";
    }
    public static HashMap<PayChannel, String> channelMap = new HashMap<>();

    static{
        channelMap.put(PayChannel.ALIPAY_APP, Source.ALIPAY);
        channelMap.put(PayChannel.ALIPAY_QR, Source.ALIPAY);
        channelMap.put(PayChannel.WECHAT_APP, Source.WECHAT);
        channelMap.put(PayChannel.WECHAT_QR, Source.WECHAT);
    }

    public TradeService(){
        payCallback.put(PayModule.SERVICE, (obj)->{
            Pay pay = (Pay) obj;
            Integer userId = pay.getUserId();
            // 写入用户购买
            userPayService.add(UserPay.builder()
                    .module(pay.getModule())
                    .moduleExtend(pay.getModuleExtend())
                    .isUse(0)
                    .userId(userId).build());
            return true;
        });
        payCallback.put(PayModule.CLASS, (obj)->{
            Pay pay = (Pay) obj;
            Integer userId = pay.getUserId();
            // 写入用户购买
            userPayService.add(UserPay.builder()
                    .module(pay.getModule())
                    .moduleExtend(pay.getModuleExtend())
                    .isUse(0)
                    .userId(userId).build());
            return true;
        });
        payCallback.put(PayModule.DATA, (obj)->{
            Pay pay = (Pay) obj;
            Integer userId = pay.getUserId();
            // 写入用户购买
            userPayService.add(UserPay.builder()
                    .module(pay.getModule())
                    .moduleExtend(pay.getModuleExtend())
                    .isUse(0)
                    .userId(userId).build());
            // 直接绑定数据关系
            return true;
        });
    }

    /**
     * 对于用户的支付方式
     * @param userId
     * @param subject
     * @param body
     * @param module
     * @param moduleExtend
     * @param money
     * @param channel
     * @param request
     * @return
     */
    public PayResponseData pay(Integer userId, String subject, String body, String module, String moduleExtend, BigDecimal money, PayChannel channel, HttpServletRequest request) throws Exception {
        if(!payCallback.containsKey(module))
            throw new ParameterException("不支持的支付模块");
        PayRequestData data = new PayRequestData();
        data.subject = subject;
        data.body = body;
        data.channel = channel.key;
        data.money = money;
        String pid = preprocess(data);
        String clientIp = Tools.getClientIp(request);
        PayType payType = PayType.FromChannel(channel);
        Pay pay = payService.make(userId, payType == null ? "":payType.key, channel.key, money, module, moduleExtend, pid, subject, body, clientIp);

        PayInfo info = process(pay, channel);
        PayResponseData payResponseData = new PayResponseData();
        payResponseData.payNo = pay.getNo();
        payResponseData.request = info.getRequest();
        payResponseData.status = info.getStatus();
        return payResponseData;
    }

    private PayInfo prepay(Pay pay){
        // 直接支付完成
        payService.payed(pay,new ResultInfo(), false);
        payed(pay);
        // 构建模拟支付信息
        PayInfo info = new PayInfo();
        info.setStatus("ok");
        return info;
    }

    public Pay payed(Pay pay){
        if(pay == null)
            throw new ParameterException("支付信息错误");

        if(pay.getTradeStatus() == TradeStatus.SUCCESS.index
                || pay.getTradeStatus() == TradeStatus.FINISH.index)
            return pay;

        if(pay.getTradeStatus() == TradeStatus.CANCEL.index
                || pay.getTradeStatus() == TradeStatus.CLOSE.index)
            throw new ParameterException("该支付无效");

        Callback callback = payCallback.get(pay.getModule());
        try {
            boolean r = callback.callback(pay);
            if (!r)
                throw new SystemException("支付回调失败");
        }catch (Exception e){
            throw new SystemException("支付回调失败", e);
        }
        return pay;
    }
    /**
     * 异步通知处理
     * @param channel
     * @param request
     * @return
     * @throws ParameterException
     * @throws SystemException
     */
    public ResultInfo notifyPay(PayChannel channel, HttpServletRequest request) throws ParameterException,SystemException, Exception{
        if(channel == null) throw new ParameterException(104, "第三方支付错误");
        PaySource paySource = getHandler(channel);
        return paySource.notifyTrade(request);
    }

    /**
     * 同步结果处理
     * @param channel
     * @param info
     * @return
     * @throws ParameterException
     * @throws SystemException
     */
    public ResultInfo returnPay(PayChannel channel, ResultInfo info) throws ParameterException,SystemException {
        if(channel == null) throw new ParameterException(104, "第三方支付错误");
        PaySource paySource = getHandler(channel);
        return paySource.returnTrade(info);
    }

    /**
     * 查询支付结果
     * @param pay
     * @return
     * @throws SystemException
     * @throws Exception
     */
    public ResultInfo query(Pay pay) throws SystemException, Exception{
        PaySource paySource = getHandler(PayChannel.ValueOf(pay.getChannel()));
        return paySource.getTrade(pay.getNo(), pay.getTransactionNo(), pay.getPid());
    }

    /**
     * 返回服务端结果
     * @param pay
     * @param info
     * @return
     * @throws SystemException
     */
    public String server(Pay pay, ResultInfo info) throws SystemException{
        PaySource paySource = getHandler(PayChannel.ValueOf(pay.getChannel()));
        if(paySource.validTrade(info, payService.getInfo(pay))){
            throw new SystemException(206, "支付校验错误");
        }
        if(pay.getMoney().equals(info.getMoney())){
            throw new SystemException(206, "支付金额错误");
        }
        analyse(pay, paySource, info);
        if(payService.isPayed(pay)){
            payed(pay);
        }
        return info.getMessage();
    }

    /**
     * 返回客户端处理结果
     * @param pay
     * @param info
     * @return
     * @throws SystemException
     * @throws ParameterException
     */
    public Pay client(Pay pay, ResultInfo info) throws SystemException, ParameterException{
        PaySource paySource = getHandler(PayChannel.ValueOf(pay.getChannel()));
        if(paySource.validTrade(info, payService.getInfo(pay))){
            throw new SystemException(206, "支付校验错误");
        }
        if(pay.getMoney().equals(info.getMoney())){
            throw new SystemException(206, "支付金额错误");
        }
        analyse(pay, paySource, info);
        if(Objects.equals(info.getStatus(), "system")) throw new SystemException(500, info.getMessage());
        if(payService.isPayed(pay)){
            return payed(pay);
        }else if(payService.isPaying(pay)){
            throw new SystemException(210, "支付中");
        }else{
            throw new SystemException(210, info.getMessage());
        }
    }

    /**
     * 支付预处理，校验支付请求
     * @param payRequest
     * @return
     * @throws ParameterException
     */
    public String preprocess(PayRequestData payRequest) throws ParameterException {
        if(payRequest.channel.isEmpty()) throw new ParameterException(103, "支付渠道为空");
        String source = getSource(PayChannel.ValueOf(payRequest.channel));
        if(source.isEmpty()) throw new ParameterException(104, "支付渠道错误");
        if(source.equals(Source.ALIPAY)) return payHelp.getAlipayPid();
        if(source.equals(Source.WECHAT)) return payHelp.getWechatPid();
        throw new ParameterException(104, "支付渠道错误");
    }

    /**
     * 发起支付
     * @param pay
     * @return
     * @throws SystemException
     * @throws Exception
     */
    public PayInfo process(Pay pay, PayChannel channel) throws SystemException, Exception {
        String notifyUrl = getNotifyUrl(channel);
        String subject = pay.getSubject();
        String payNo = pay.getNo();
        String body = pay.getBody();
        BigDecimal money = pay.getMoney();
        String pid = pay.getPid();
        PayInfo info = null;
        switch(channel){
            case ALIPAY_APP:
                info = payHelp.getAlipay().appTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                break;
            case ALIPAY_QR:
                info = payHelp.getAlipay().preTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                break;
            case WECHAT_APP:
                info = payHelp.getWechatPay().appTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                break;
            case WECHAT_QR:
                info = payHelp.getWechatPay().preTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                break;
            case WECHAT_JS:
                info = payHelp.getWechatPay().jsTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                break;
            case OFFLINE:
                // 直接完成支付
                info = prepay(pay);
            default:
                throw new SystemException(204, "支付渠道错误方式:"+channel);
        }
        if(Objects.equals(info.getStatus(), "ok")) {
            payService.start(pay, info);
        } else {
            payService.system(pay, info);
            throw new SystemException(205, "支付调用错误");
        }
        return info;
    }

    /**
     * 同步处理支付记录
     * @param pay
     * @param paySource
     * @param info
     */
    private void analyse(Pay pay, PaySource paySource, ResultInfo info) throws SystemException {
        String status = info.getStatus();
        if(payService.isPayed(pay)){
            if(status.equals("error") || status.equals("close") || status.equals("cancel")){
                throw new SystemException(301, "支付异常：已支付，第三方未支付");

            }
            return ;
        }else if(payService.isInvalid(pay)){
            if(status.equals("success") || status.equals("finish")){
                throw new SystemException(301, "支付异常：已撤销或者关闭，第三方支付成功");
            }
            return ;
        }
        switch (status) {
            case "success":
                // 支付成功
                payService.payed(pay, info, false);
                break;
            case "finish":
                // 完成，无法撤销或者关闭
                payService.payed(pay, info, true);
                break;
            case "error":
                // 支付错误，需要关闭支付交易
                payService.error(pay, info);
                break;
            case "cancel":
                // 对方已撤销
                payService.cancel(pay, info);
                break;
            case "close":
                // 对方已经关闭
                payService.close(pay, info);
                break;
            case "system":
                // system 开发接口错误
                payService.system(pay, info);
                break;
            case "exception":
                // exception 支付流程异常
                payService.exception(pay, info);
                break;
        }
    }

    /**
     * 获取支付
     * @param channel
     * @return
     */
    private String getSource(PayChannel channel){
        return channelMap.get(channel);
    }

    /**
     * 获取渠道异步通知
     * @param channel
     * @return
     */
    private String getNotifyUrl(PayChannel channel){
        return payHelp.getNotifyUrl() + channel.key;
    }

    /**
     * 获取支付实例
     * @param channel
     * @return PaySource
     * @throws SystemException
     */
    private PaySource getHandler(PayChannel channel) throws SystemException {
        String source = getSource(channel);
        if(source.equals(Source.ALIPAY)) return payHelp.getAlipay();
        if(source.equals(Source.WECHAT)) return payHelp.getWechatPay();
        throw new SystemException(204, "支付支付渠道错误方式");
    }
}
