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

import com.alibaba.fastjson.JSON;
import com.crm.pay.entity.FinanceDepositCallbackUpdateEntity;
import com.crm.pay.entity.b2bin.*;
import com.crm.pay.entity.callback.B2BinCallbackEntity;
import com.crm.pay.entity.property.B2BinPayPropertyEntity;
import com.crm.pay.service.B2BinService;
import com.crm.pay.service.FinanceDepositService;
import com.crm.pay.service.FinanceRateService;
import com.crm.pay.service.impl.base.BasePayServiceImpl;
import com.crm.rely.backend.core.constant.Constants;
import com.crm.rely.backend.core.exception.ServiceException;
import com.crm.rely.backend.model.constant.PayConstants;
import com.crm.rely.backend.model.entity.finance.deposit.FinanceDepositAddEntity;
import com.crm.rely.backend.model.pojo.table.FinanceDepositTable;
import com.crm.rely.backend.model.pojo.table.SysRemittanceChannelTable;
import com.crm.rely.backend.util.BCrypt;
import com.crm.rely.backend.util.Base64Util;
import com.crm.rely.backend.util.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.springframework.beans.BeanUtils;
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.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

/**
 * @description:
 * @author: houn
 * @create: 2020-05-07 23:45
 */
@Slf4j
@Service
public class B2BinServiceImpl extends BasePayServiceImpl implements B2BinService {

    @Autowired
    private FinanceDepositService balanceService;

    @Autowired
    private FinanceRateService financeRateService;

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

        SysRemittanceChannelTable channel = getPayChannel(PayConstants.B2BIN_PAY_TYPE_KEY);

        B2BinPayPropertyEntity payProperty = getPayProperty(channel, B2BinPayPropertyEntity.class);

        for (B2BinPayCurrencyPropertyEntity propertyEntity : payProperty.getCurrencys()) {
            if (balanceSaveEntity.getBankCode().equals(propertyEntity.getBankCode())) {
                payProperty.setWallet(propertyEntity.getWallet());
                payProperty.setBaseUrl(propertyEntity.getBaseUrl());
                payProperty.setCurrency(propertyEntity.getCurrency());
                break;
            }
        }
        B2BinPayTokenEntity b2BinPayTokenEntity = getB2BinPayToken(payProperty);
        B2BinPayRateListDataEntity b2BinPayRateListDataEntity = getB2BinPayRateListData(b2BinPayTokenEntity, payProperty);

        BigDecimal rate = null;

        B2BinPayRateEntity b2BinPayRateEntity = getRate(b2BinPayRateListDataEntity, payProperty);
        if (b2BinPayRateEntity == null) {
            log.error("Get rate error");
            throw ServiceException.exception(Constants.SYSTEM_ERROR);
        }
        if (("USD".equals(channel.getCurrency()) && "USDT".equals(payProperty.getCurrency()))) {
            rate = financeRateService.getRate("USD", "USDT", 0);
            log.info("Get system exchange rate");
        } else {
            rate = new BigDecimal(b2BinPayRateEntity.getRate()).divide(new BigDecimal(10).pow(b2BinPayRateEntity.getPow()), 10, 1);
        }

        balanceSaveEntity.setCurrency(channel.getCurrency());

        balanceSaveEntity.setRate(rate);

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

        B2BinPayUrlDataEntity b2BinPayUrlDataEntity = null;
        if ("USDT(ETH)".equals(balanceSaveEntity.getBankCode())) {
            B2BinPayPropertyEntity b2BinPayUrlDataProperty = new B2BinPayPropertyEntity();
            BeanUtils.copyProperties(payProperty, b2BinPayUrlDataProperty);

            b2BinPayUrlDataEntity = getB2BinPayUrlData(balanceTable, b2BinPayUrlDataProperty, b2BinPayTokenEntity,
                    b2BinPayRateListDataEntity);
        }

        String url = getUrl(balanceTable, payProperty, b2BinPayTokenEntity, b2BinPayRateEntity, rate,
                channel.getExpire(), b2BinPayUrlDataEntity);
        balanceTable.setFormUrl(url);
        return url;
    }

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

        boolean flag = validate(callbackEntity);
        if (!flag) {
            throw new ServiceException(Constants.FAIL);
        }

        int status;
        switch (callbackEntity.getStatus()) {
            case "1":
                status = 0;
                break;
            case "2":
                status = 1;
                break;
            default:
                status = -1;
                break;
        }

        FinanceDepositCallbackUpdateEntity balanceCallbackUpdateEntity =
                getFinanceDepositCallbackUpdateEntity(callbackEntity.getTracking_id(), callbackEntity.getId() + "", status,callbackEntity);


        if (callbackEntity.getTransactions() == null || callbackEntity.getTransactions().size() < 1) {
            throw new ServiceException(Constants.SYSTEM_ERROR);
        }
        //计算实际金额
        B2BinPayTransactionEntity b2BinPayTransactionEntity = callbackEntity.getTransactions().get(0);
        BigDecimal autulAmount = new BigDecimal(b2BinPayTransactionEntity.getAmount());
        BigDecimal transferRate = new BigDecimal(b2BinPayTransactionEntity.getTransfer_rate());

        BigDecimal autulAmountPow = new BigDecimal(10).pow(new Integer(b2BinPayTransactionEntity.getPow()));
        BigDecimal transferRatePow = new BigDecimal(10).pow(new Integer(b2BinPayTransactionEntity.getTransfer_rate_pow()));
        autulAmount = autulAmount.divide(autulAmountPow);
        transferRate = transferRate.divide(transferRatePow);
        BigDecimal aount = autulAmount.multiply(transferRate);
        aount = aount.setScale(2, RoundingMode.FLOOR);

        if ("USDT".equals(b2BinPayTransactionEntity.getTransfer_currency().getAlpha())) {
            BigDecimal rate = financeRateService.getRate("USD", "USDT", 0);
            aount = aount.divide(rate, 10, BigDecimal.ROUND_HALF_DOWN);
        }
        balanceCallbackUpdateEntity.setAmount(aount);
        /**
         * 更新数据
         */
        balanceService.updateCallback(balanceCallbackUpdateEntity);
    }

    /**
     * 获取支付url
     *
     * @param balanceTable
     * @return
     * @throws ServiceException
     */
    private String getUrl(FinanceDepositTable balanceTable, B2BinPayPropertyEntity payProperty,
                          B2BinPayTokenEntity b2BinPayTokenEntity, B2BinPayRateEntity b2BinPayRateEntity,
                          BigDecimal rate, Integer expire, B2BinPayUrlDataEntity ethB2BinPayUrlDataEntity) throws ServiceException, IOException {


        Map<String, String> header = new HashMap<>(2);

        header.put("Content-Type", "application/x-www-form-urlencoded");
        header.put("Authorization", b2BinPayTokenEntity.getToken_type() + " " + b2BinPayTokenEntity.getAccess_token());
        if (rate == null) {
            rate = new BigDecimal(b2BinPayRateEntity.getRate());
        } else {
            rate = rate.multiply(new BigDecimal(10).pow(b2BinPayRateEntity.getPow()));
        }
        BigDecimal amount = balanceTable.getAmount().multiply(rate);
        Map<String, String> params = new HashMap<>(20);
        params.put("wallet", payProperty.getWallet());
        params.put("amount", amount.toString() + "");
//        params.put("lifetime", expire == null ? "0" :
//                String.valueOf(expire * 60));
        params.put("lifetime", "0");
        params.put("pow", b2BinPayRateEntity.getPow() + "");
        if (ethB2BinPayUrlDataEntity != null) {
            params.put("tracking_id", ethB2BinPayUrlDataEntity.getData().getTracking_id());
        } else {
            params.put("tracking_id", balanceTable.getSerial() + "_" + balanceTable.getLogin());
        }
        if (ethB2BinPayUrlDataEntity != null) {
            params.put("callback_url", ethB2BinPayUrlDataEntity.getData().getCallback_url());
        } else {
            params.put("callback_url", payProperty.getReceiveUrl());
        }

        params.put("success_url", payProperty.getSuccessUrl());
        params.put("error_url", payProperty.getErrorUrl());
        if (ethB2BinPayUrlDataEntity != null) {
            params.put("address", ethB2BinPayUrlDataEntity.getData().getAddress());
        } else {
            params.put("address", payProperty.getReceiveUrl());
        }


        log.info(String.format("b2binPay发送地址%s/api/v1/pay/bills,请求数据:%s", payProperty.getBaseUrl(), JSON.toJSONString(params)));

        Connection.Response response = HttpUtil.post(header, payProperty.getBaseUrl() + "/api/v1/pay/bills", params);
        String payString = response.body();

        log.info("b2binPay接收发送地址返回数据:" + payString);

        B2BinPayUrlDataEntity b2BinPayUrlDataEntity = JSON.parseObject(payString, B2BinPayUrlDataEntity.class);
        B2BinPayUrlEntity b2BinPayUrlEntity = b2BinPayUrlDataEntity.getData();
        if (b2BinPayUrlEntity.getStatus() != 1) {
            throw new ServiceException(b2BinPayUrlEntity.getMessage());
        }
        return b2BinPayUrlEntity.getUrl();

    }

    private B2BinPayUrlDataEntity getB2BinPayUrlData(FinanceDepositTable balanceTable,
                                                     B2BinPayPropertyEntity payProperty,
                                                     B2BinPayTokenEntity b2BinPayTokenEntity,
                                                     B2BinPayRateListDataEntity b2BinPayRateListDataEntity) throws IOException {

        for (B2BinPayCurrencyPropertyEntity propertyEntity : payProperty.getCurrencys()) {
            if ("ETH".equals(propertyEntity.getBankCode())) {
                payProperty.setWallet(propertyEntity.getWallet());
                payProperty.setBaseUrl(propertyEntity.getBaseUrl());
                payProperty.setCurrency(propertyEntity.getCurrency());
                break;
            }
        }
        B2BinPayRateEntity b2BinPayRateEntity = getRate(b2BinPayRateListDataEntity, payProperty);

        BigDecimal rate = new BigDecimal(b2BinPayRateEntity.getRate()).divide(new BigDecimal(10).pow(b2BinPayRateEntity.getPow()), 10, 1);

        Map<String, String> header = new HashMap<>(2);

        header.put("Content-Type", "application/x-www-form-urlencoded");
        header.put("Authorization", b2BinPayTokenEntity.getToken_type() + " " + b2BinPayTokenEntity.getAccess_token());
        if (rate == null) {
            rate = new BigDecimal(b2BinPayRateEntity.getRate());
        } else {
            rate = rate.multiply(new BigDecimal(10).pow(b2BinPayRateEntity.getPow()));
        }
        BigDecimal amount = balanceTable.getAmount().multiply(rate);
        Map<String, String> params = new HashMap<>(20);
        params.put("wallet", payProperty.getWallet());
        params.put("amount", amount.toString() + "");
        params.put("lifetime", "0");
        params.put("pow", b2BinPayRateEntity.getPow() + "");
        params.put("tracking_id", balanceTable.getSerial() + "_" + balanceTable.getLogin());
        params.put("callback_url", payProperty.getReceiveUrl());
        params.put("success_url", payProperty.getSuccessUrl());
        params.put("error_url", payProperty.getErrorUrl());
        params.put("address", payProperty.getReceiveUrl());

        log.info(String.format("b2binPay发送地址%s/api/v1/pay/bills,请求数据:%s", payProperty.getBaseUrl(), JSON.toJSONString(params)));

        Connection.Response response = HttpUtil.post(header, payProperty.getBaseUrl() + "/api/v1/pay/bills", params);
        String payString = response.body();

        log.info("b2binPay接收发送地址返回数据:" + payString);

        B2BinPayUrlDataEntity b2BinPayUrlDataEntity = JSON.parseObject(payString, B2BinPayUrlDataEntity.class);
        return b2BinPayUrlDataEntity;
    }

    private B2BinPayTokenEntity getB2BinPayToken(B2BinPayPropertyEntity payProperty) throws IOException, ServiceException {
        Map<String, String> header = new HashMap<>(1);

        header.put("Authorization", payProperty.getTokenHead() + " " + Base64Util.toBase64String(payProperty.getKey() + ":" + payProperty.getSecret()));

        Connection.Response response = HttpUtil.get(payProperty.getBaseUrl() + "/api/login", header);

        String tokenString = response.body();

        log.info("b2binPay获取token:" + tokenString);
        B2BinPayTokenEntity b2BinPayTokenEntity = JSON.parseObject(tokenString, B2BinPayTokenEntity.class);

        if (b2BinPayTokenEntity.getCode() != null) {
            throw new ServiceException(b2BinPayTokenEntity.getError());
        }
        return b2BinPayTokenEntity;
    }

    private B2BinPayRateListDataEntity getB2BinPayRateListData(B2BinPayTokenEntity b2BinPayTokenEntity, B2BinPayPropertyEntity payProperty) throws IOException {
        Map<String, String> header = new HashMap<>(1);

        header.put("Authorization", b2BinPayTokenEntity.getToken_type() + " " + b2BinPayTokenEntity.getAccess_token());

        Connection.Response response = HttpUtil.get(payProperty.getBaseUrl() + "/api/v1/rates/deposit/", header);

        String rateString = response.body();

        log.info("获取汇率数据:" + rateString);

        B2BinPayRateListDataEntity b2BinPayRateListEntity = JSON.parseObject(rateString, B2BinPayRateListDataEntity.class);
        return b2BinPayRateListEntity;
    }

    /**
     * 验证回调参数是否被篡改
     *
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private boolean validate(B2BinCallbackEntity callbackEntity) throws Exception {
        B2BinPayPropertyEntity payProperty = getPayProperty(PayConstants.B2BIN_PAY_TYPE_KEY, false, B2BinPayPropertyEntity.class);
        String sign = getPlaintext(payProperty, callbackEntity);

        log.info(String.format("order no:%scallback result sign compare,加密方式:BCrypt--callback sign:%s,plaintext结果:%s", callbackEntity.getId(), callbackEntity.getSign(), sign));
        return BCrypt.checkpw(sign, callbackEntity.getSign().getHash());

    }

    public static void main(String[] args) {
        System.out.println(BCrypt.checkpw("85b05e4bfa:5e4dc8ad41601a6:Tue Jun 08 2021 06:53:05 GMT+0000", "$2y$10$zj0ZFsxLkwRPebMLR1UJ2OSYCAogebv9qvZAVUnhCY1zKeQJ2skia"));
    }

    /**
     * 回调请求加密plaintext
     *
     * @param b2BinPayPropertyEntity
     * @param safeCallbackEntity
     * @return
     * @throws ServiceException
     */
    private String getPlaintext(B2BinPayPropertyEntity b2BinPayPropertyEntity, B2BinCallbackEntity safeCallbackEntity) {

        String signString = b2BinPayPropertyEntity.getKey() + ":" + b2BinPayPropertyEntity.getSecret() + ":" + safeCallbackEntity.getSign().getTime();


        return signString;
    }

    private B2BinPayRateEntity getRate(B2BinPayRateListDataEntity b2BinPayRateListEntity,
                                       B2BinPayPropertyEntity payProperty) throws IOException {

        B2BinPayRateEntity result = null;
        for (B2BinPayRateEntity b2BinPayRateEntity : b2BinPayRateListEntity.getData()) {
            if (payProperty.getCurrency().equals(b2BinPayRateEntity.getTo().getAlpha())) {
                result = b2BinPayRateEntity;
                break;
            }
        }
        log.info("使用汇率数据:" + JSON.toJSONString(result));
        return result;
    }
}
