package com.crm.pay.service.impl.pay;

import com.alibaba.fastjson.JSON;
import com.crm.pay.dto.uniotc.UniotcRequestDto;
import com.crm.pay.entity.callback.UniotcCallbackEntity;
import com.crm.pay.entity.property.UniotcPropertyEntity;
import com.crm.pay.service.FinanceDepositService;
import com.crm.pay.service.UniotcService;
import com.crm.pay.service.impl.base.BasePayServiceImpl;
import com.crm.rely.backend.model.constant.PayConstants;
import com.crm.rely.backend.model.entity.finance.deposit.FinanceDepositAddEntity;
import com.crm.pay.entity.FinanceDepositCallbackUpdateEntity;
import com.crm.rely.backend.model.pojo.table.FinanceDepositTable;
import com.crm.rely.backend.model.pojo.table.SysRemittanceChannelTable;
import com.crm.rely.backend.core.exception.PayValidatedException;
import com.crm.rely.backend.core.exception.ServiceException;
import com.crm.rely.backend.util.HttpUtil;
import com.crm.rely.backend.util.MD5Util;
import com.crm.rely.backend.util.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static com.alibaba.fastjson.JSON.parseObject;

/**
 * @program: cwg-my-base
 * @description:
 * @author: houn
 * @create: 2021-03-11 11:45
 */
@Slf4j
@Service
public class UniotcServiceImpl extends BasePayServiceImpl implements UniotcService {
    @Autowired
    private FinanceDepositService financeDepositService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String pay(FinanceDepositAddEntity balanceSaveEntity) throws Exception {

        //通道配置
        SysRemittanceChannelTable channel = getPayChannel(PayConstants.UNIOTC_TYPE_KEY);
        //通道配置实体
        UniotcPropertyEntity payProperty = getPayProperty(channel, UniotcPropertyEntity.class);

        balanceSaveEntity.setCurrency(channel.getCurrency());
        balanceSaveEntity.setTransformCurrency(channel.getTransformCurrency());

        FinanceDepositTable balanceTable = financeDepositService.save(balanceSaveEntity, channel);

        String url = getUrl(payProperty, balanceTable);
        balanceTable.setFormUrl(url);
        
        return url;
    }

    /**
     * 回调处理数据
     *
     * @param callbackEntity
     * @throws ServiceException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callback(UniotcCallbackEntity callbackEntity) throws Exception {
        log.info(String.format("callback orderNo:%s,callback data:%s", callbackEntity.getOut_trade_no(), JSON.toJSONString(callbackEntity)));

        boolean flag = validate(callbackEntity);
        if (!flag) {
            throw new ServiceException("fail");
        }

        FinanceDepositCallbackUpdateEntity balanceCallbackUpdateEntity = getFinanceDepositCallbackUpdateEntity(PayConstants.UNIOTC_SUCCESS, callbackEntity.getOut_trade_no(), null, Integer.parseInt(callbackEntity.getCallbacks()),callbackEntity);

        balanceCallbackUpdateEntity.setTransformAmount(new BigDecimal(callbackEntity.getAmount_true()));
        /**
         * 更新数据
         */
        financeDepositService.updateCallback(balanceCallbackUpdateEntity);
    }

    /**
     * 获取支付url
     *
     * @param balanceTable
     * @return
     * @throws ServiceException
     */
    private String getUrl(UniotcPropertyEntity propertyEntity, FinanceDepositTable balanceTable) throws ServiceException, IOException, PayValidatedException {

        Map<String, String> map = new LinkedHashMap<>(13);

        map.put("appid", propertyEntity.getAppId());
        map.put("pay_id", propertyEntity.getPayId());
        map.put("amount", String.format("%.2f", balanceTable.getTransformAmount().doubleValue()));
        map.put("success_url", propertyEntity.getSuccessUrl());
        map.put("error_url", propertyEntity.getErrorUrl());
        map.put("out_uid", String.valueOf(balanceTable.getCId()));
        map.put("out_trade_no", balanceTable.getSerial() + "_" + balanceTable.getLogin());
        map.put("version", propertyEntity.getVersion());
        map.put("sign_type", propertyEntity.getSignType());
        String sign = getSign(propertyEntity, map);

        map.put("sign", sign);


        Map<String, String> headers = new HashMap<>(2);
        headers.put("Content-Type", "application/json");
        Connection.Response response = HttpUtil.post(propertyEntity.getBaseUrl() + propertyEntity.getPaymentUrl(), headers, map);

        UniotcRequestDto uniotcRequestDataDto = parseObject(response.body(), UniotcRequestDto.class);
        if (uniotcRequestDataDto == null || !uniotcRequestDataDto.getCode().equals("0") || uniotcRequestDataDto.getData()==null ) {

            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer service.");
        }
        String url = uniotcRequestDataDto.getData().getUrl();
        balanceTable.setChannelSerial(uniotcRequestDataDto.getData().getOut_trade_no());
        log.info(String.format("支付请求,请求orderNo:%s,请求url:%s", map.get("out_trade_no"), url));
        return url;
    }

    /**
     * 验证回调参数是否被篡改
     *
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private boolean validate(UniotcCallbackEntity callbackEntity) throws Exception {
        UniotcPropertyEntity propertyEntity = getPayProperty(PayConstants.UNIOTC_TYPE_KEY, false, UniotcPropertyEntity.class);
        Map<String, String> map = new LinkedHashMap<>(4);

        map.put("appid", propertyEntity.getAppId());
        map.put("amount", callbackEntity.getAmount());
        map.put("out_trade_no", callbackEntity.getOut_trade_no());
        String sign = getSign(propertyEntity, map);

        map.put("sign", sign);

        Map<String, String> headers = new HashMap<>(2);
        headers.put("Content-Type", "application/json");
        Connection.Response response = HttpUtil.post(propertyEntity.getBaseUrl() + propertyEntity.getCheckUrl(), headers, map);

        UniotcRequestDto uniotcRequestDto = parseObject(response.body(), UniotcRequestDto.class);
        if (uniotcRequestDto == null || !uniotcRequestDto.getCode().equals("200") || !uniotcRequestDto.getMsg().equals(PayConstants.UNIOTC_CHECK_SUCCESS)) {

            log.info(String.format("order no:%s回调验证错误:%s", callbackEntity.getOut_trade_no(), uniotcRequestDto));

            return false;
        }

        String _sign = getSign(propertyEntity, callbackEntity);
        log.info(String.format("order no:%scallback result sign compare--callback sign:%s, encryption sign result:%s", callbackEntity.getOut_trade_no(), callbackEntity.getSign(), _sign));
        return sign.equals(_sign);
    }

    /**
     * 支付请求加密sign
     *
     * @param propertyEntity
     * @return
     * @throws ServiceException
     */
    private String getSign(UniotcPropertyEntity propertyEntity, Map<String, String> map) {


        String signString = MapUtil.getString(MapUtil.sortByKey(map, false)) + "&key=" + propertyEntity.getShopKey();

        String sign = MD5Util.getMD5(signString).toUpperCase();

        log.info(String.format("请求加密处理--请求orderNo:%s,sign string:%s,sign:%s", map.get("out_trade_no"), signString,
                sign));

        return sign;
    }

    /**
     * 回调请求加密sign
     *
     * @param propertyEntity
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private String getSign(UniotcPropertyEntity propertyEntity, UniotcCallbackEntity callbackEntity) {
        Map<String, String> map = new LinkedHashMap<>(13);
        map.put("callbacks", callbackEntity.getCallbacks());
        map.put("appid", callbackEntity.getAppid());
        map.put("pay_id", callbackEntity.getPay_id());
        map.put("success_url", callbackEntity.getSuccess_url());
        map.put("error_url", callbackEntity.getError_url());
        map.put("out_trade_no", callbackEntity.getOut_trade_no());
        map.put("amount", callbackEntity.getAmount());

        map.put("amount_true", callbackEntity.getAmount_true());
        map.put("out_uid", callbackEntity.getOut_uid());

        String signString = MapUtil.getString(MapUtil.sortByKey(map, false)) + "&key=" + propertyEntity.getCallbackKey();
        String sign = MD5Util.getMD5(signString).toUpperCase();

        log.info(String.format("回调结果加密处理--请求orderNo:%s,sign string:%s,sign:%s", callbackEntity.getOut_trade_no(), signString, sign));

        return sign;
    }

}
