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

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.crm.pay.dao.repository.CustomInfoRepository;
import com.crm.pay.dto.finance.withdraw.automatic.FinanceWithdrawHelpDto;
import com.crm.pay.entity.callback.HelpCallbackEntity;
import com.crm.pay.entity.callback.HelpPayoutCallbackEntity;
import com.crm.pay.entity.property.HelpPayPropertyEntity;
import com.crm.pay.entity.property.HelpPayWithdrawPropertyEntity;
import com.crm.pay.service.FinanceDepositService;
import com.crm.pay.service.HelpService;
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.dto.finance.withdraw.FinanceWithdrawDto;
import com.crm.rely.backend.model.entity.custom.info.CustomInfoEntity;
import com.crm.rely.backend.model.entity.finance.deposit.FinanceDepositAddEntity;
import com.crm.pay.entity.FinanceDepositCallbackUpdateEntity;
import com.crm.pay.entity.finance.withdraw.FinanceWithdrawApproveChannelEntity;
import com.crm.pay.entity.finance.withdraw.FinanceWithdrawApproveChannelSubmitEntity;
import com.crm.pay.entity.finance.withdraw.automatic.FinanceWithdrawHelpEntity;
import com.crm.pay.entity.finance.withdraw.automatic.FinanceWithdrawInfoEntity;
import com.crm.rely.backend.core.exception.PayValidatedException;
import com.crm.rely.backend.core.exception.ServiceException;
import com.crm.rely.backend.model.pojo.table.CustomInfoTable;
import com.crm.rely.backend.model.pojo.table.FinanceDepositTable;
import com.crm.rely.backend.model.pojo.table.SysRemitChannelTable;
import com.crm.rely.backend.model.pojo.table.SysRemittanceChannelTable;
import com.crm.rely.backend.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.*;

/**
 * @description:
 * @author: houn
 * @create: 2020-11-06 12:45
 */
@Slf4j
@Service
public class HelpServiceImpl extends BasePayServiceImpl implements HelpService {

    @Autowired
    private FinanceDepositService financeDepositService;

    @Autowired
    private CustomInfoRepository customInfoRepository;

    /**
     * help通道支付业务逻辑 包括加密 汇率等
     *
     * @param balanceSaveEntity 支付实体
     * @return
     * @throws Exception
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String pay(FinanceDepositAddEntity balanceSaveEntity, CustomInfoEntity infoEntity) throws Exception {
        SysRemittanceChannelTable channel = getPayChannel(balanceSaveEntity.getPayType());

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


        balanceSaveEntity.setCurrency(channel.getCurrency());

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

        //获取form表单数据
        String formString = getForm(payProperty, financeDepositTable, balanceSaveEntity, infoEntity);

        //将form字符串加入到实体
        financeDepositTable.setFormUrl(formString);
        
        return financeDepositTable.getSerial();
    }

    /**
     * 回调处理数据
     *
     * @param callbackEntity
     * @throws ServiceException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callback(HelpCallbackEntity callbackEntity) throws Exception {
        log.info(String.format("callback orderNo:%s,callback data:%s", callbackEntity.getReference(), JSON.toJSONString(callbackEntity)));
        HelpPayPropertyEntity payProperty = getPayProperty(PayConstants.HELP_PAY_TYPE, false, HelpPayPropertyEntity.class);
        boolean flag = validate(callbackEntity, payProperty);
        if (!flag) {
            throw new ServiceException("fail");
        }

        List<String> success = new ArrayList<>(2);
        success.add(PayConstants.HELP_SUCCESS_STATUS000);
        success.add(PayConstants.HELP_SUCCESS_STATUS006);
        FinanceDepositCallbackUpdateEntity balanceCallbackUpdateEntity = getFinanceDepositCallbackUpdateEntity(success,
                callbackEntity.getReference(),
                callbackEntity.getID(),
                callbackEntity.getStatus(),
                callbackEntity);
        balanceCallbackUpdateEntity.setTransformAmount(new BigDecimal(callbackEntity.getAmount()));
        String statusCode = getQuery(callbackEntity, payProperty);
        if (success.contains(callbackEntity.getStatus()) && !success.contains(statusCode)) {
            balanceCallbackUpdateEntity.setCallbackStatus(PayConstants.CALLBACE_PROCESSING_STATUS);
        }
        /**
         * 更新数据
         */
        financeDepositService.updateCallback(balanceCallbackUpdateEntity);
    }

    /**
     * 获取支付form
     *
     * @param financeDepositTable
     * @return
     * @throws ServiceException
     */
    private String getForm(HelpPayPropertyEntity propertyEntity, FinanceDepositTable financeDepositTable, FinanceDepositAddEntity balanceSaveEntity, CustomInfoEntity infoEntity) throws ServiceException {

        Map<String, String> map = new HashMap<>(10);
        String language = getLanguage(infoEntity.getCountry());

        String datetime = DateUtil.convertDateBetweenTimeZone("GMT+8", financeDepositTable.getAddTime());
        map.put("Merchant", propertyEntity.getShopNo());
        map.put("Currency", financeDepositTable.getTransformCurrency());
        map.put("Customer", String.valueOf(financeDepositTable.getCId()));
        map.put("Reference", financeDepositTable.getSerial() + "_" + financeDepositTable.getLogin());
//        map.put("Amount", String.format("%.2f", financeDepositTable.getTransformAmount().doubleValue()));
        map.put("Amount", String.format("%.2f", financeDepositTable.getTransformAmount().setScale(0,
                BigDecimal.ROUND_UP).doubleValue()));
        map.put("Note", "");
        map.put("Datetime", datetime);
        map.put("FrontURI", propertyEntity.getReceiveUrl());
        map.put("BackURI", propertyEntity.getReceiveUrl());
        map.put("Language", language);
        map.put("Bank", financeDepositTable.getBankCode());
        map.put("ClientIP", balanceSaveEntity.getIp());

        String sign = getSign(propertyEntity, map);

        map.put("Key", sign);
        String formString = PayFormUtil.getForm(propertyEntity.getBaseUrl(), map);

        log.info(String.format("支付请求,请求orderNo:%s,请求form:%s", financeDepositTable.getSerial(), formString));

        return formString;
    }

    @Override
    public FinanceWithdrawHelpDto withdrawInfo(FinanceWithdrawInfoEntity entity) throws Exception {
        FinanceWithdrawDto withdrawDto = null;
        if (entity.getWithdrawInfoType() == null || entity.getWithdrawInfoType().equals(1)) {
            withdrawDto = financeWithdrawService.searchSingle(entity.getId());
        } else {
            //withdrawDto = financeAgentWithdrawService.searchSingle(entity.getId());
        }
        entity.setPayType(PayConstants.HELP_PAY_TYPE_REMIT);

        CustomInfoTable customInfoTable = customInfoRepository.getFirstById(withdrawDto.getCustomId());
        SysRemitChannelTable withdrawChannel = getWithdrawChannel(entity.getPayType());

        FinanceWithdrawHelpDto dto = new FinanceWithdrawHelpDto();
        dto.setCurrencyCode(withdrawDto.getBankCode());
        dto.setAmount(withdrawDto.getTransformAmount() == null ? null :
                String.valueOf(withdrawDto.getTransformAmount().setScale(2, BigDecimal.ROUND_DOWN)));
        dto.setBankCode(withdrawDto.getSwiftCode());
        dto.setToBankAccountName(withdrawDto.getBankUname());
        dto.setToBankAccountNumber(withdrawDto.getBankCardNum());
        dto.setToProvince("");
        dto.setToCity("");
        dto.setCustomerName(withdrawDto.getName());
        dto.setCustomerEmail(customInfoTable.getEmail());
        dto.setCustomerMobile(customInfoTable.getPhone());

        dto.setId(withdrawDto.getId());
        dto.setSerial(withdrawDto.getSerial());
        dto.setWithdrawUrl(withdrawChannel.getWithdrawUrl());
        dto.setPayType(entity.getPayType());
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdraw(FinanceWithdrawHelpEntity entity) throws Exception {
        FinanceWithdrawDto withdrawDto = null;
        if (entity.getWithdrawInfoType() == null || entity.getWithdrawInfoType().equals(1)) {
            withdrawDto = financeWithdrawService.searchSingle(entity.getId());
        } else {
            //withdrawDto = financeAgentWithdrawService.searchSingle(entity.getId());
        }
        entity.setCId(withdrawDto.getCId());
        entity.setSerial(withdrawDto.getSerial());
        entity.setLogin(withdrawDto.getLogin());

        HelpPayWithdrawPropertyEntity helpPayWithdrawProperty = getWithdrawProperty(PayConstants.HELP_PAY_TYPE_REMIT, HelpPayWithdrawPropertyEntity.class);

        payout(entity, helpPayWithdrawProperty);

        FinanceWithdrawApproveChannelSubmitEntity financeWithdrawApproveChannelSubmitEntity =
                new FinanceWithdrawApproveChannelSubmitEntity();
        financeWithdrawApproveChannelSubmitEntity.setSubmitStatus(PayConstants.PASSED_STATUS);
        financeWithdrawApproveChannelSubmitEntity.setId(entity.getId());
        financeWithdrawApproveChannelSubmitEntity.setActualPayType(entity.getPayType());
        if (entity.getWithdrawInfoType() == null || entity.getWithdrawInfoType().equals(1)) {
            financeWithdrawService.approve(financeWithdrawApproveChannelSubmitEntity);
        } else {
            //financeAgentWithdrawService.approve(financeWithdrawApproveChannelSubmitEntity);
        }
    }

    private void payout(FinanceWithdrawHelpEntity entity, HelpPayWithdrawPropertyEntity propertyEntity) throws Exception {
        log.info(String.format("payout json:%s",JSON.toJSON(entity)));

        Map<String, String> map = new HashMap<>();
        map.put("ClientIP",propertyEntity.getClientIp());
        if (entity.getWithdrawInfoType() == null || entity.getWithdrawInfoType().equals(1)) {
            map.put("ReturnURI", propertyEntity.getNotifyUrl());
        } else {
            map.put("ReturnURI", propertyEntity.getAgentNotifyUrl());
        }
        map.put("MerchantCode",propertyEntity.getMerchantCode());
        map.put("TransactionID",entity.getSerial());
        map.put("CurrencyCode",entity.getCurrencyCode());
        map.put("MemberCode", String.valueOf(entity.getCId()));
        // 如果货币类型是TET-ETHE (USDT - ERC20)，Amount需要保留6位小数
        if (entity.getCurrencyCode().toLowerCase().contains("tet")){
            map.put("Amount", String.valueOf(new BigDecimal(entity.getAmount()).setScale(6, BigDecimal.ROUND_UP)));
        } else if (entity.getCurrencyCode().toLowerCase().contains("thb")|| entity.getCurrencyCode().toLowerCase().contains("vnd")
                || entity.getCurrencyCode().toLowerCase().contains("idr")|| entity.getCurrencyCode().toLowerCase().contains("cny")){
            // 如果货币类型是THB VND IDR CNY Amount保留两位小数，小数点只有一位有效，例如：12.30可以，12.31不可以
            map.put("Amount", String.valueOf(new BigDecimal(entity.getAmount()).setScale(1, BigDecimal.ROUND_UP).setScale(2, BigDecimal.ROUND_UP)));
        } else {
            map.put("Amount", entity.getAmount());
        }
        // 转为 am/pm 格式
        map.put("TransactionDateTime",DateUtil.formatToAmPm(new Date()));
        map.put("BankCode",entity.getBankCode());
        map.put("toBankAccountName",entity.getToBankAccountName());
        map.put("toBankAccountNumber",entity.getToBankAccountNumber());
        map.put("toProvince",entity.getToProvince());
        map.put("toCity",entity.getToCity());
        map.put("CustomerName",entity.getCustomerName());
        map.put("CustomerEmail",entity.getCustomerEmail());
        map.put("CustomerMobile",entity.getCustomerMobile());
        map.put("IFSC",entity.getIfsc());

        String key = getSignKey(map, propertyEntity.getSecretCode());
        map.put("Key",key);

        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json; charset=UTF-8");

        Connection.Response response = HttpUtil.post(propertyEntity.getBaseUrl(),headers, map);
        log.info(String.format("url:%s,json:%s,body:%s", propertyEntity.getBaseUrl(), map, response.body()));

        JSONObject jsonObject = XmlUtils.xml2Json(response.body());

        if (!"000".equals(jsonObject.getString("statusCode"))){
            if (StringUtils.isNotEmpty(jsonObject.getString("message"))){
                throw new PayValidatedException(jsonObject.getString("message"));
            } else {
                throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer service.");
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String withdrawCallback(Integer withdrawType, HelpPayoutCallbackEntity callbackEntity) throws Exception {
        log.info(String.format("callback data:%s", callbackEntity));

        HelpPayWithdrawPropertyEntity withdrawProperty = getWithdrawProperty(PayConstants.HELP_PAY_TYPE_REMIT, HelpPayWithdrawPropertyEntity.class);
        // 验证签名
        boolean flag = this.withdrawCallbackValidate(callbackEntity, withdrawProperty);
        if (!flag){
            log.error("help withdraw callback verification signature error");
            return Constants.FAIL;
        }
        String status = callbackEntity.getStatus();
        String transactionId = callbackEntity.getTransactionId();
        String id = callbackEntity.getId();

        log.info("help withdraw callback--Status:"+status+", transactionId:"+transactionId+", id:"+id);

        FinanceWithdrawApproveChannelEntity financeWithdrawApproveChannelEntity =
                new FinanceWithdrawApproveChannelEntity();
        financeWithdrawApproveChannelEntity.setChannelSerial(id);

        // 状态 Success Approved Failed Rejected
        if (!"Success".equalsIgnoreCase(status) && !"Approved".equalsIgnoreCase(status)) {
            financeWithdrawApproveChannelEntity.setCallbackStatus(PayConstants.CALLBACE_FAIL_STATUS);
        } else {
            financeWithdrawApproveChannelEntity.setCallbackStatus(PayConstants.CALLBACE_SUCCESS_STATUS);
        }
        financeWithdrawApproveChannelEntity.setSerial(transactionId);

        /**
         * 更新数据
         */
        if (withdrawType == null || withdrawType.equals(1)) {
            financeWithdrawService.approve(financeWithdrawApproveChannelEntity);
        } else {
            //financeAgentWithdrawService.channel(financeWithdrawApproveChannelEntity);
        }

        return Constants.SUCCESS;

    }

    public static void main(String[] args) throws IOException {
        String decrypt = AESUtil.decrypt("FZMa8QQDy/CSlUQP/XI66bg8jmBRyWvsiWCstTpxhpiiLGQDCEJrBhWqfsl8Bg0hDhs17rJVj+ipuV2lNMwtINV1n1mu8bA4tJwvYuI8lhY+2S8V4U6OxFlSiSePiUWpFNlIHvYNQbXikc6c6vrnw6WdjSmRILXWoKTljDAUdAu0xqUqGAQRzgtU3CTd32IRgp3nUu7AVoA2wZEgArcZWVkoLBVh6F4ZEcvZosLNaU72uY0XPGUteylE78fMEMKQkJSEkbzAKxWqHsPQDkVd/DaVwvGGp5V8tL72ncMGIGqrxMocrkoiT3YGueWW3dmd", "bfa5559109f94c78af615bcf00d52060");
        System.out.println(decrypt);

        String content = "{" +
                "    \"merchantCode\":\"T0144\"," +
                "    \"ClientIP\":\"103.158.191.66\"," +
                "    \"secretCode\":\"c0hFo8R0tDUNMEq0tEf1\"," +
                "    \"notifyUrl\":\"https://testpay.cwgvu.com/help/withdraw/callback\"," +
                "    \"agentNotifyUrl\":\"https://testpay.cwgvu.com/help/agent/withdraw/callback\"," +
                "    \"baseUrl\":\"\"\n" +
                "}";
        System.out.println("encrypt :"+ AESUtil.encrypt(content, "bfa5559109f94c78af615bcf00d52060"));

//        System.out.println(DateUtil.convertDateBetweenTimeZone("GMT+8", new Date()));

//        System.out.println(DateUtil.formatToAmPm(new Date()));
        String secretKey = "c0hFo8R0tDUNMEq0tEf1";

        System.out.println();
        String clientIp = "127.0.0.1";
        String returnUrl = "https://testpay.cwgvu.com/help/withdraw/callback";
        String merchantCode = "T0144";
        String transactionId = "MD120324475668";
        String currencyCode = "MYR";
        String memberCode = "1700010221";
        String amount = "12.00";
        String transactionDateTime = DateUtil.formatToAmPm(new Date());
        String bankCode = "AFF";
        String toBankAccountName = "张三";
        String toBankAccountNumber = "123500202351402";
        String toProvince = "CHINA";
        String toCity = "SHANGHAI";
        String customerName = "张三";
        String customerEmail = "www.1354564615@163.com";
        String customerMobile = "15000000000";
        String ifsc =  "AINM";
        Map<String, String> map = new HashMap<>();
        map.put("ClientIP",clientIp);
        map.put("ReturnURI",returnUrl);
        map.put("MerchantCode",merchantCode);
        map.put("TransactionID",transactionId);
        map.put("CurrencyCode",currencyCode);
        map.put("MemberCode",memberCode);
        map.put("Amount",amount);
        map.put("TransactionDateTime",transactionDateTime);
        map.put("BankCode",bankCode);
        map.put("toBankAccountName",toBankAccountName);
        map.put("toBankAccountNumber",toBankAccountNumber);
        map.put("toProvince",toProvince);
        map.put("toCity",toCity);
        map.put("CustomerName",customerName);
        map.put("CustomerEmail",customerEmail);
        map.put("CustomerMobile",customerMobile);
        map.put("IFSC",ifsc);

        String key = getSignKey(map, secretKey);
        map.put("Key",key);

        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json; charset=UTF-8");

        /*Connection.Response response = HttpUtil.post("",headers, map);
        log.info(String.format("url:%s,json:%s,body:%s", "url", map, response.body()));

        String payString = response.body();
        log.info(" payout response body:" + payString);*/


    }

    public static String getSignKey(Map<String, String> map,String securityCode){
        String transactionDatetime = DateUtil.dateAmPmFormatToTimestamp(map.get("TransactionDateTime"));
        String signString = String.format("%s%s%s%s%s%s%s%s",
                map.get("MerchantCode"),
                map.get("TransactionID"),
                map.get("MemberCode"),
                map.get("Amount"),
                map.get("CurrencyCode"),
                transactionDatetime,
                map.get("toBankAccountNumber"),
                securityCode
        );

        String sign = MD5Util.getMD5(signString);
        log.info(String.format("签名:%s,sign string:%s,sign:%s", map.get("TransactionID"), signString, sign));
        return sign;
    }

    /**
     * 验证回调参数是否被篡改
     *
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private boolean validate(HelpCallbackEntity callbackEntity, HelpPayPropertyEntity payProperty) throws Exception {

        if (payProperty.getWhitelist() != null && payProperty.getWhitelist().size() > 0) {
            //传输过来的ip不在白名单中 则返回失败
            if (!payProperty.getWhitelist().contains(callbackEntity.getIp())) {
                log.info(String.format("支付回调白名单验证失败--回调orderNo:%s,回调ip:%s,白名单ips:%s",
                        callbackEntity.getReference(),
                        callbackEntity.getIp(),
                        String.join(",", payProperty.getWhitelist())));
                return false;
            }
        }

        String signString = String.format("%s%s%s%s%s%s%s",
                payProperty.getShopNo(),
                callbackEntity.getReference(),
                callbackEntity.getCustomer(),
                callbackEntity.getAmount(),
                callbackEntity.getCurrency(),
                callbackEntity.getStatus(),
                payProperty.getShopKey()
        );
        String sign = MD5Util.getMD5(signString);

        boolean verify = sign.toLowerCase().equals(callbackEntity.getKey().toLowerCase());
        log.info(String.format("支付回调加密处理--回调orderNo:%s,回调sign string:%s,sign:%s,callback sign:%s",
                callbackEntity.getReference(),
                signString,
                sign,
                callbackEntity.getKey()));
        return verify;
    }

    /**
     * 验证出金回调参数是否被篡改
     *
     * @param callbackEntity
     * @return
     * @throws ServiceException
     */
    private boolean withdrawCallbackValidate(HelpPayoutCallbackEntity callbackEntity, HelpPayWithdrawPropertyEntity payProperty) throws Exception {

        String signString = String.format("%s%s%s%s%s%s%s",
                callbackEntity.getMerchantCode(),
                callbackEntity.getTransactionId(),
                callbackEntity.getMemberCode(),
                callbackEntity.getAmount(),
                callbackEntity.getCurrencyCode(),
                callbackEntity.getStatus(),
                payProperty.getSecretCode()
        );
        String sign = MD5Util.getMD5(signString);

        boolean verify = sign.equalsIgnoreCase(callbackEntity.getKey());
        log.info(String.format("支付回调加密处理--回调orderNo:%s,回调sign string:%s,sign:%s,callback sign:%s",
                callbackEntity.getTransactionId(),
                signString,
                sign,
                callbackEntity.getKey()));
        return verify;
    }

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

        String dateTime = DateUtil.formatDateTime(map.get("Datetime"));
        String signString = String.format("%s%s%s%s%s%s%s%s",
                propertyEntity.getShopNo(),
                map.get("Reference"),
                map.get("Customer"),
                map.get("Amount"),
                map.get("Currency"),
                dateTime,
                propertyEntity.getShopKey(),
                map.get("ClientIP")
        );

        String sign = MD5Util.getMD5(signString);

        log.info(String.format("orderNo:%s,sign string:%s,sign:%s", map.get("Reference"), signString,
                sign));

        return sign;
    }

    private String getLanguage(String country) {

        if (CountryUtil.isCN(country)) {
            return "zh-cn";
        } else {
            return "en-us";
        }
    }

    private String getQuery(HelpCallbackEntity callbackEntity, HelpPayPropertyEntity payProperty) throws IOException {
        String url = String.format("https://query.safepaymentapp" +
                        ".com/Services/Merchants/%s/TransferStatus/%s",
                payProperty.getShopNo(), callbackEntity.getReference());
        Connection.Response response = HttpUtil.post(url);
        String bodyString = response.body();
        log.info("help query order statuscode response body:" + bodyString);

        JSONObject jsonObject = XmlUtils.xml2Json(bodyString);
        log.info("help query order json:" + JSON.toJSONString(jsonObject));
        return jsonObject.getString("StatusCode");
    }
}
