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

import com.alibaba.fastjson.JSON;
import com.crm.pay.entity.b2bin.v2.*;
import com.crm.pay.entity.callback.b2b.v2.B2BinV2CallbackDataIncludedEntity;
import com.crm.pay.entity.callback.b2b.v2.B2BinV2CallbackEntity;
import com.crm.pay.entity.property.B2BinV2PayPropertyEntity;
import com.crm.pay.service.B2BinV2Service;
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.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.SysChannelBankTable;
import com.crm.rely.backend.model.pojo.table.SysRemittanceChannelTable;
import com.crm.rely.backend.core.exception.ServiceException;
import com.crm.rely.backend.util.BCrypt;
import com.crm.rely.backend.util.HAMCSHAUtil;
import com.crm.rely.backend.util.HttpUtil;
import com.crm.rely.backend.util.ShaUtil;
import com.google.common.base.Strings;
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.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description:
 * @author: houn
 * @create: 2021-12-07 23:45
 */
@Slf4j
@Service
public class B2BinV2ServiceImpl extends BasePayServiceImpl implements B2BinV2Service {

    @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_V2_PAY_TYPE_KEY);
        B2BinV2PayPropertyEntity payProperty = getPayProperty(channel, B2BinV2PayPropertyEntity.class);
        SysChannelBankTable channelBankTable = balanceService.getChannelBank(balanceSaveEntity, channel);

        //获取token
        String token = getB2BinPayToken(payProperty);

        //获取汇率
        B2BinV2PayRateListDataEntity rateListData = getB2BinPayRateListData(token, payProperty);


        //处理汇率及wallet
        B2BinV2PayRateEntity b2BinPayRateEntity = getCurrency(rateListData, payProperty, channel.getCurrency(),
                channelBankTable);

        //处理汇率
        BigDecimal rate = getRate(b2BinPayRateEntity, channel, channelBankTable);

        //设置对象参数
        balanceSaveEntity.setCurrency(channel.getCurrency());
        balanceSaveEntity.setRate(rate);

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

        //获取支付地址
        String url = getUrl(balanceTable, payProperty, token, b2BinPayRateEntity.getCurrencyId(), channelBankTable);
        balanceTable.setFormUrl(url);
        
        return url;
    }

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

        B2BinV2CallbackDataIncludedEntity includedTransferEntity = null;

        B2BinV2CallbackDataIncludedEntity includedCurrencyEntity = null;
        for (B2BinV2CallbackDataIncludedEntity includedForEntity :
                callbackEntity.getIncluded()) {
            if ("transfer".equals(includedForEntity.getType())) {
                includedTransferEntity = includedForEntity;
            }
            if ("currency".equals(includedForEntity.getType())) {
                includedCurrencyEntity = includedForEntity;
            }
        }
        if (includedTransferEntity == null || includedCurrencyEntity == null) {
            throw ServiceException.exception(Constants.NOT_PERMIT);
        }

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

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

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

        //计算实际金额
        BigDecimal amount = new BigDecimal(includedTransferEntity.getAttributes().getAmount());

        balanceCallbackUpdateEntity.setTransformAmount(amount);
        /**
         * 更新数据
         */
        balanceService.updateCallback(balanceCallbackUpdateEntity);
    }

    /**
     * 获取支付url
     *
     * @param balanceTable
     * @return
     * @throws ServiceException
     */
    private String getUrl(FinanceDepositTable balanceTable, B2BinV2PayPropertyEntity payProperty,
                          String token, int currencyId,SysChannelBankTable channelBankTable) throws ServiceException, IOException {

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

        header.put(payProperty.getAuthorizationHeader(), payProperty.getBearer() + " " + token);
        header.put("Content-Type", "application/vnd.api+json");

        Map<String, Object> dataMap = new HashMap<>(3);
        dataMap.put("type", "deposit");

        //构建attributes
        Map<String, Object> attributesMap = new HashMap<>(4);
        attributesMap.put("label", balanceTable.getLogin());
        attributesMap.put("tracking_id", balanceTable.getSerial() + "_" + balanceTable.getLogin());
        attributesMap.put("confirmations_needed", 2);
        attributesMap.put("callback_url", payProperty.getCallbackUrl());

        //构建relationships
        Map<String, Object> relationshipsMap = new HashMap<>(2);
        Map<String, Object> walletMap = new HashMap<>(2);
        Map<String, Object> walletDataMap = new HashMap<>(2);
        walletDataMap.put("type", "wallet");
        walletDataMap.put("id", payProperty.getWalletId());
        walletMap.put("data", walletDataMap);
        relationshipsMap.put("wallet", walletMap);


        Map<String, Object> currencyMap = new HashMap<>(2);
        Map<String, Object> currencyDataMap = new HashMap<>(2);
        currencyDataMap.put("type", "currency");
        currencyDataMap.put("id", currencyId);
        currencyMap.put("data", currencyDataMap);
        relationshipsMap.put("currency", currencyMap);

        dataMap.put("attributes", attributesMap);
        dataMap.put("relationships", relationshipsMap);


        Map<String, Object> params = new HashMap<>(1);
        params.put("data", dataMap);

        String data = JSON.toJSONString(params);
        String payUrl = payProperty.getBaseUrl() + payProperty.getDepositUrl();
        log.info(String.format("b2b v2 get url:%s,data:%s", payUrl, data));

        Connection.Response response = HttpUtil.post(payUrl, header, data);

        String payString = response.body();

        log.info("b2bin v2 pay response body:" + payString);

        B2BinV2PayDataEntity b2BinPayUrlDataEntity = JSON.parseObject(payString, B2BinV2PayDataEntity.class);
        B2BinV2PayDepositEntity b2BinPayUrlEntity = b2BinPayUrlDataEntity.getData();

        return String.format(payProperty.getPayUrl(), b2BinPayUrlEntity.getAttributes().getAddress(),
                payProperty.getSuccessUrl(),
                balanceTable.getTransformAmount().stripTrailingZeros().toPlainString(), channelBankTable.getEnName());
//        return b2BinPayUrlEntity.getAttributes().getPayment_page();

    }

    private String getB2BinPayToken(B2BinV2PayPropertyEntity payProperty) throws Exception {
        Map<String, String> header = new HashMap<>(1);

        header.put("Content-Type", "application/vnd.api+json");

        B2BinV2PayTokenDataEntity dataEntity = new B2BinV2PayTokenDataEntity();
        B2BinV2PayTokenEntity tokenEntity = new B2BinV2PayTokenEntity();
        tokenEntity.setType("auth-token");
        B2BinV2PayTokenAttributesEntity attributes = new B2BinV2PayTokenAttributesEntity();
        attributes.setLogin(payProperty.getKey());
        attributes.setPassword(payProperty.getSecret());
        tokenEntity.setAttributes(attributes);
        dataEntity.setData(tokenEntity);

        String data = JSON.toJSONString(dataEntity);

        log.info(String.format("b2b v2 get token url:%s,data:%s", payProperty.getBaseUrl() + payProperty.getTokenUrl(), data));
        Connection.Response response = HttpUtil.post(payProperty.getBaseUrl() + payProperty.getTokenUrl(), header, data);

        String tokenString = response.body();

        log.info("b2bin v2 get token:" + tokenString);
        B2BinV2PayTokenDataEntity entity = JSON.parseObject(tokenString, B2BinV2PayTokenDataEntity.class);

        if (!Strings.isNullOrEmpty(entity.getErrors())) {
            throw ServiceException.exception(entity.getErrors());
        }
        String sign = getSign(entity, payProperty);
        if (sign.equals(entity.getMeta().getSign())) {
            return entity.getData().getAttributes().getAccess();
        } else {
            throw ServiceException.exception(Constants.SYSTEM_ERROR);
        }
    }

    private B2BinV2PayRateListDataEntity getB2BinPayRateListData(String token,
                                                                 B2BinV2PayPropertyEntity payProperty) throws IOException {
        Map<String, String> header = new HashMap<>(2);

        header.put(payProperty.getAuthorizationHeader(), payProperty.getBearer() + " " + token);
        header.put("Content-Type", "application/vnd.api+json");

        log.info(String.format("b2b v2 get rate url:%s", payProperty.getBaseUrl() + payProperty.getTokenUrl()));

        Connection.Response response = HttpUtil.get(payProperty.getBaseUrl() + payProperty.getRateUrl(), header);

        String tokenString = response.body();

        log.info("b2bin v2 get rate:" + tokenString);

        String rateString = response.body();

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

        B2BinV2PayRateListDataEntity b2BinV2PayRateListDataEntity = JSON.parseObject(rateString, B2BinV2PayRateListDataEntity.class);
        return b2BinV2PayRateListDataEntity;
    }

    /**
     * 验证回调参数是否被篡改
     *
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private boolean validate(B2BinV2CallbackEntity callbackEntity, B2BinV2CallbackDataIncludedEntity includedEntity) throws Exception {
        B2BinV2PayPropertyEntity payProperty = getPayProperty(PayConstants.B2BIN_V2_PAY_TYPE_KEY, B2BinV2PayPropertyEntity.class);
        String sign = getSign(callbackEntity, includedEntity, payProperty);

        log.info(String.format("order no:%scallback result sign compare,加密方式:sha256_HMAC--callback sign:%s,sign结果:%s",
                callbackEntity.getData().getAttributes().getTracking_id(),
                callbackEntity.getMeta().getSign(), sign));
        return callbackEntity.getMeta().getSign().equals(sign);

    }

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


    private BigDecimal getRate(B2BinV2PayRateEntity b2BinPayRateEntity,
                               SysRemittanceChannelTable channel,
                               SysChannelBankTable channelBankTable) throws IOException, ServiceException {
        BigDecimal rate;
        if (b2BinPayRateEntity == null) {
            log.error("b2b v2 get rate error");
            throw ServiceException.exception(Constants.SYSTEM_ERROR);
        }
        //处理特殊情况汇率
        if (("USD".equals(channel.getCurrency()) && "USDT".equals(channelBankTable.getCurrency())) || ("USDT".equals(channel.getCurrency()) && "USD".equals(channelBankTable.getCurrency()))) {
            rate = financeRateService.getRate("USD", "USDT", 0);
            log.info("Get system exchange rate");
        } else {
            rate = new BigDecimal(b2BinPayRateEntity.getAttributes().getBid());
        }
        return rate;
    }

    private B2BinV2PayRateEntity getCurrency(B2BinV2PayRateListDataEntity b2BinPayRateListEntity,
                                             B2BinV2PayPropertyEntity payProperty,
                                             String currency, SysChannelBankTable channelBankTable) throws IOException, ServiceException {

        B2BinV2PayRateEntity result = null;
        for (B2BinV2PayRateEntity b2BinPayRateEntity : b2BinPayRateListEntity.getData()) {
            if (currency.equals(b2BinPayRateEntity.getAttributes().getLeft()) && channelBankTable.getCurrency().equals(b2BinPayRateEntity.getAttributes().getRight())) {
                result = b2BinPayRateEntity;
                break;
            }
        }
        AtomicInteger walletId = new AtomicInteger();
        if (payProperty.getCurrencys() != null) {
            payProperty.getCurrencys().forEach(v -> {
                if (v.getCurrency().equals(channelBankTable.getCode())) {
                    walletId.set(v.getCurrencyId());
                }
            });
        }
        if (walletId.get() <= 0) {
            log.error("b2b v2 currencyId is null");
            throw ServiceException.exception(Constants.SYSTEM_ERROR);
        }
        if (result != null) {
            result.setCurrencyId(walletId.get());
        }

        log.info("使用汇率数据:" + JSON.toJSONString(result));
        return result;
    }

    private String getSign(B2BinV2CallbackEntity entity, B2BinV2CallbackDataIncludedEntity includedEntity, B2BinV2PayPropertyEntity payProperty) throws Exception {
        String secret = payProperty.getKey() + payProperty.getSecret();
        String message =
                includedEntity.getAttributes().getStatus() + includedEntity.getAttributes().getAmount() + entity.getData().getAttributes().getTracking_id() + entity.getMeta().getTime();
        return HAMCSHAUtil.sha256HMAC(message, ShaUtil.SHAByte("SHA-256", secret));
    }

    private String getSign(B2BinV2PayTokenDataEntity entity, B2BinV2PayPropertyEntity payProperty) throws Exception {
        String secret = payProperty.getKey() + payProperty.getSecret();
        String message = entity.getMeta().getTime() + entity.getData().getAttributes().getRefresh();
        return  HAMCSHAUtil.sha256HMAC(message, ShaUtil.SHAByte("SHA-256", secret));
    }
}
