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

import com.alibaba.fastjson.JSON;
import com.crm.pay.dto.korapay.*;
import com.crm.pay.entity.callback.korapay.KoraCallbackEntity;
import com.crm.pay.entity.korapay.*;
import com.crm.pay.entity.property.PorapayPropertyEntity;
import com.crm.pay.service.CustomInfoService;
import com.crm.pay.service.FinanceDepositService;
import com.crm.pay.service.KorapayService;
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.CustomInfoTable;
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.AESUtil;
import com.crm.rely.backend.util.HAMCSHAUtil;
import com.crm.rely.backend.util.HttpUtil;
import com.google.common.base.Strings;
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.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: cwg-my-base
 * @description:
 * @author: houn
 * @create: 2023-05-12 11:45
 */
@Slf4j
@Service
public class KorapayServiceImpl extends BasePayServiceImpl implements KorapayService {

    @Autowired
    private FinanceDepositService financeDepositService;

    @Autowired
    private CustomInfoService customInfoService;

    public static void main(String[] args) throws UnsupportedEncodingException {
//        System.out.println(AESUtil.encrypt("", "bfa5559109f94c78af615bcf00d52060"));
//
//        System.out.println(AESUtil.decrypt("", "bfa5559109f94c78af615bcf00d52060"));
    }

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

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

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

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

        KorapayRequestPayDataDto korapayRequestPayDataDto = requestPay(payProperty, balanceTable, balanceSaveEntity);

        balanceTable.setChannelSerial(korapayRequestPayDataDto.getTransactionReference());
        if (PayConstants.KORAPAY_PAY_3DS.equals(korapayRequestPayDataDto.getAuthModel())) {
            balanceTable.setFormUrl(korapayRequestPayDataDto.getRedirectUrl());
        }

        korapayRequestPayDataDto.setSerial(balanceTable.getSerial());
        KorapayDto dto = new KorapayDto();
        BeanUtils.copyProperties(korapayRequestPayDataDto, dto);
        
        return dto;
    }

    /**
     * 回调处理数据
     *
     * @param wehbookData
     * @throws ServiceException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callback(String wehbookData, String signature) throws Exception {

        KoraCallbackEntity callbackEntity = JSON.parseObject(wehbookData, KoraCallbackEntity.class);
        if (callbackEntity == null || Strings.isNullOrEmpty(callbackEntity.getEvent()) || callbackEntity.getData() == null) {
            throw new ServiceException(Constants.FAIL);
        }
        log.info(String.format("callback ticket:%s,callback data:%s", callbackEntity.getData().getReference(),
                JSON.toJSONString(callbackEntity)));

        //获取配置
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, false,
                PorapayPropertyEntity.class);

        boolean flag = validate(wehbookData, signature, payProperty);

        if (!flag) {
            throw new ServiceException(Constants.FAIL);
        }

        int status;
        switch (callbackEntity.getData().getStatus()) {
            case PayConstants.KORAPAY_SUCCESS_STATUS:
                status = PayConstants.CALLBACE_SUCCESS_STATUS;
                break;
            case PayConstants.KORAPAY_FAILED_STATUS:
                status = PayConstants.CALLBACE_FAIL_STATUS;
                break;
            default:
                status = 0;
                break;
        }

        FinanceDepositCallbackUpdateEntity balanceCallbackUpdateEntity = new FinanceDepositCallbackUpdateEntity();
        balanceCallbackUpdateEntity.setChannelSerial(callbackEntity.getData().getReference());
        balanceCallbackUpdateEntity.setTicket(callbackEntity.getData().getReference());
        balanceCallbackUpdateEntity.setCallbackStatus(status);
        balanceCallbackUpdateEntity.setCallbackTime(new Date());
        balanceCallbackUpdateEntity.setCallbackData(JSON.toJSONString(callbackEntity));

        balanceCallbackUpdateEntity.setTransformAmount(new BigDecimal(callbackEntity.getData().getAmount()));

        /**
         * 更新数据
         */
        financeDepositService.updateCallback(balanceCallbackUpdateEntity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callbackSerial(String serial) throws Exception {
        //获取配置
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, false,
                PorapayPropertyEntity.class);

        FinanceDepositTable table = financeDepositService.getBySerial(serial);

        validateAndUpdateCallback(payProperty, table);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public KorapayDto pin(KorapayPinEntity entity) throws Exception {
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, PorapayPropertyEntity.class);
        FinanceDepositTable table = financeDepositService.getBySerial(entity.getSerial());

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

        header.put("Authorization",
                payProperty.getAuthorizationBasic() + payProperty.getSecretKey());

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

        params.put("transaction_reference", table.getChannelSerial());

        Map<String, Object> authorizationMap = new HashMap<>(1);
        authorizationMap.put("pin", entity.getPin());

        params.put("authorization", authorizationMap);

        Connection.Response response = HttpUtil.post(
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(), header, params);

        log.info(String.format("url:%s,header:%s,params:%s,body:%s",
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(),
                JSON.toJSON(header), JSON.toJSON(params), response.body()));


        KorapayRequestAuthorizeDto requestEntity = JSON.parseObject(response.body(), KorapayRequestAuthorizeDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }
        if (!requestEntity.isStatus()) {
            throw new PayValidatedException(requestEntity.getMessage());
        }
        if (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus()) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            throw new PayValidatedException(requestEntity.getData().getResponseMessage());
        }
        if (PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            KorapayDto dto = new KorapayDto();
            BeanUtils.copyProperties(requestEntity.getData(), dto);
            dto.setSerial(entity.getSerial());
            return dto;
        }

        validateAndUpdateCallback(payProperty, table);
        KorapayDto dto = new KorapayDto();
        dto.setStatus(PayConstants.KORAPAY_SUCCESS_STATUS);
        dto.setSerial(entity.getSerial());
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public KorapayDto otp(KorapayOtpEntity entity) throws Exception {
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, PorapayPropertyEntity.class);
        FinanceDepositTable table = financeDepositService.getBySerial(entity.getSerial());

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

        header.put("Authorization",
                payProperty.getAuthorizationBasic() + payProperty.getSecretKey());

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

        params.put("transaction_reference", table.getChannelSerial());

        Map<String, Object> authorizationMap = new HashMap<>(1);
        authorizationMap.put("otp", entity.getOtp());

        params.put("authorization", authorizationMap);

        Connection.Response response = HttpUtil.post(
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(), header, params);

        log.info(String.format("url:%s,header:%s,params:%s,body:%s",
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(),
                JSON.toJSON(header), JSON.toJSON(params), response.body()));


        KorapayRequestAuthorizeDto requestEntity = JSON.parseObject(response.body(), KorapayRequestAuthorizeDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }
        if (!requestEntity.isStatus()) {
            throw new PayValidatedException(requestEntity.getMessage());
        }
        if (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus()) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            throw new PayValidatedException(requestEntity.getData().getResponseMessage());
        }
        if (PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            KorapayDto dto = new KorapayDto();
            BeanUtils.copyProperties(requestEntity.getData(), dto);
            dto.setSerial(entity.getSerial());
            return dto;
        }

        validateAndUpdateCallback(payProperty, table);
        KorapayDto dto = new KorapayDto();
        dto.setStatus(PayConstants.KORAPAY_SUCCESS_STATUS);
        dto.setSerial(entity.getSerial());
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public KorapayDto avs(KorapayAvsEntity entity) throws Exception {
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, PorapayPropertyEntity.class);
        FinanceDepositTable table = financeDepositService.getBySerial(entity.getSerial());

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

        header.put("Authorization",
                payProperty.getAuthorizationBasic() + payProperty.getSecretKey());

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

        params.put("transaction_reference", table.getChannelSerial());


        Map<String, Object> avsMap = new HashMap<>(5);
        avsMap.put("state", entity.getState());
        avsMap.put("city", entity.getCity());
        avsMap.put("country", entity.getCountry());
        avsMap.put("address", entity.getAddress());
        avsMap.put("zip_code", entity.getZipCode());

        Map<String, Object> authorizationMap = new HashMap<>(1);
        authorizationMap.put("avs", avsMap);
        params.put("authorization", authorizationMap);

        Connection.Response response = HttpUtil.post(
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(), header, params);

        log.info(String.format("url:%s,header:%s,params:%s,body:%s",
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(),
                JSON.toJSON(header), JSON.toJSON(params), response.body()));


        KorapayRequestAuthorizeDto requestEntity = JSON.parseObject(response.body(), KorapayRequestAuthorizeDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }
        if (!requestEntity.isStatus()) {
            throw new PayValidatedException(requestEntity.getMessage());
        }
        if (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus()) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            throw new PayValidatedException(requestEntity.getData().getResponseMessage());
        }
        if (PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            KorapayDto dto = new KorapayDto();
            BeanUtils.copyProperties(requestEntity.getData(), dto);
            dto.setSerial(entity.getSerial());
            return dto;
        }

        validateAndUpdateCallback(payProperty, table);
        KorapayDto dto = new KorapayDto();
        dto.setStatus(PayConstants.KORAPAY_SUCCESS_STATUS);
        dto.setSerial(entity.getSerial());
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public KorapayDto phone(KorapayPhoneEntity entity) throws Exception {
        PorapayPropertyEntity payProperty = getPayProperty(PayConstants.KORAPAY_PAY, PorapayPropertyEntity.class);
        FinanceDepositTable table = financeDepositService.getBySerial(entity.getSerial());

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

        header.put("Authorization",
                payProperty.getAuthorizationBasic() + payProperty.getSecretKey());

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

        params.put("transaction_reference", table.getChannelSerial());

        Map<String, Object> authorizationMap = new HashMap<>(1);
        authorizationMap.put("phone", entity.getPhone());

        params.put("authorization", authorizationMap);

        Connection.Response response = HttpUtil.post(
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(), header, params);

        log.info(String.format("url:%s,header:%s,params:%s,body:%s",
                payProperty.getBaseUrl() + payProperty.getAuthorizeUrl(),
                JSON.toJSON(header), JSON.toJSON(params), response.body()));


        KorapayRequestAuthorizeDto requestEntity = JSON.parseObject(response.body(), KorapayRequestAuthorizeDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }
        if (!requestEntity.isStatus()) {
            throw new PayValidatedException(requestEntity.getMessage());
        }
        if (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus()) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            throw new PayValidatedException(requestEntity.getData().getResponseMessage());
        }
        if (PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            KorapayDto dto = new KorapayDto();
            BeanUtils.copyProperties(requestEntity.getData(), dto);
            dto.setSerial(entity.getSerial());
            return dto;
        }

        validateAndUpdateCallback(payProperty, table);
        KorapayDto dto = new KorapayDto();
        dto.setStatus(PayConstants.KORAPAY_SUCCESS_STATUS);
        dto.setSerial(entity.getSerial());
        return dto;
    }

    private void validateAndUpdateCallback(PorapayPropertyEntity payProperty, FinanceDepositTable table) throws ServiceException, IOException, PayValidatedException {
        KorapayRequestReferenceDto korapayRequestReferenceDto = getTicketData(payProperty,
                table.getSerial() + "_" + table.getLogin());
        int status;
        switch (korapayRequestReferenceDto.getData().getStatus()) {
            case PayConstants.KORAPAY_SUCCESS_STATUS:
                status = PayConstants.CALLBACE_SUCCESS_STATUS;
                break;
            case PayConstants.KORAPAY_FAILED_STATUS:
                status = PayConstants.CALLBACE_FAIL_STATUS;
                break;
            default:
                status = 0;
                break;
        }

        FinanceDepositCallbackUpdateEntity balanceCallbackUpdateEntity = new FinanceDepositCallbackUpdateEntity();
        balanceCallbackUpdateEntity.setChannelSerial(table.getChannelSerial());
        balanceCallbackUpdateEntity.setTicket(table.getChannelSerial());
        balanceCallbackUpdateEntity.setCallbackStatus(status);
        balanceCallbackUpdateEntity.setCallbackTime(new Date());
        balanceCallbackUpdateEntity.setCallbackData(JSON.toJSONString(korapayRequestReferenceDto));

        balanceCallbackUpdateEntity.setTransformAmount(new BigDecimal(korapayRequestReferenceDto.getData().getAmount()));

        /**
         * 更新数据
         */
        financeDepositService.updateCallback(balanceCallbackUpdateEntity);
    }

    private boolean validate(String wehbookData, String signature, PorapayPropertyEntity propertyEntity) throws Exception {

        String sign = HAMCSHAUtil.sha256HMAC(wehbookData, propertyEntity.getSecretKey());
        log.info(String.format("callback result sign sha256HMAC--callback sign:%s,sign:%s",
                signature, sign));
        return signature.equals(sign);

    }

    /**
     * 获取支付url
     *
     * @param balanceTable
     * @return
     * @throws ServiceException
     */
    private KorapayRequestPayDataDto requestPay(PorapayPropertyEntity propertyEntity, FinanceDepositTable balanceTable,
                                                FinanceDepositAddEntity balanceSaveEntity) throws Exception {

        String orderNo = balanceTable.getSerial() + "_" + balanceTable.getLogin();

        CustomInfoTable customInfoTable = customInfoService.getById(balanceTable.getCustomId());

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

        header.put("Authorization",
                propertyEntity.getAuthorizationBasic() + propertyEntity.getSecretKey());

        KorapayRequestEntity entity = new KorapayRequestEntity();
        entity.setReference(orderNo);
        entity.setCurrency(balanceTable.getTransformCurrency());
        entity.setAmount(balanceTable.getTransformAmount().divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP).floatValue());
        entity.setRedirect_url(propertyEntity.getPickupUrl() + balanceTable.getSerial());

        KorapayRequestCustomerEntity customer = new KorapayRequestCustomerEntity();
        customer.setEmail(customInfoTable.getEmail());
        entity.setCustomer(customer);

        KorapayRequestCardEntity card = new KorapayRequestCardEntity();
        card.setName(balanceSaveEntity.getBankUname());
        card.setCvv(balanceSaveEntity.getCvv());
        card.setExpiry_month(balanceSaveEntity.getExpiryMonth());
        card.setNumber(balanceSaveEntity.getBankCardNum().replace(" ", ""));
        card.setPin(balanceSaveEntity.getPin());
        card.setExpiry_year(balanceSaveEntity.getExpiryYear());
        entity.setCard(card);

        String chargeData = getChargeData(propertyEntity, entity);

        Map<String, String> params = new HashMap<>(1);
        params.put("charge_data", chargeData);
        Connection.Response response = HttpUtil.post(
                propertyEntity.getBaseUrl() + propertyEntity.getChargesCardUrl(), header, params);

        log.info(String.format("url:%s,header:%s,params:%s,body:%s",
                propertyEntity.getBaseUrl() + propertyEntity.getChargesCardUrl(),
                JSON.toJSON(header), JSON.toJSON(entity), response.body()));
        KorapayRequestPayDto requestEntity = JSON.parseObject(response.body(), KorapayRequestPayDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }

        if (!requestEntity.isStatus()) {
            if (Strings.isNullOrEmpty(requestEntity.getMessage())) {
                throw new PayValidatedException("Payment platform is abnormal, please try again later or contact " +
                        "customer" +
                        " service.");
            } else {
                throw new PayValidatedException(requestEntity.getMessage());
            }
        }

        if (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus()) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            if (Strings.isNullOrEmpty(requestEntity.getData().getResponseMessage())) {
                throw new PayValidatedException("Payment platform is abnormal, please try again later or contact " +
                        "customer" +
                        " service.");
            } else {
                throw new PayValidatedException(requestEntity.getData().getResponseMessage());
            }
        }
        if (requestEntity.isStatus() && PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus())) {
            validateAndUpdateCallback(propertyEntity, balanceTable);
        }
        return requestEntity.getData();
    }

    private String getChargeData(PorapayPropertyEntity propertyEntity, KorapayRequestEntity entity) throws Exception {
        String chargeDataString = JSON.toJSONString(entity);

        String chargeData = AESUtil.encryptPayload(chargeDataString, propertyEntity.getEncryptionKey());
        log.info(String.format("kora sign,chargeDataString:%s,chargeData:%s", chargeDataString, chargeData));
        return chargeData;
    }

    private KorapayRequestReferenceDto getTicketData(PorapayPropertyEntity propertyEntity, String ticket) throws ServiceException,
            IOException,
            PayValidatedException {

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

        header.put("Authorization",
                propertyEntity.getAuthorizationBasic() + propertyEntity.getSecretKey());
        String url = propertyEntity.getBaseUrl() + propertyEntity.getChargesReferenceUrl() + "/" + ticket;
        Connection.Response response = HttpUtil.get(url, header);

        log.info(String.format("get ticket data, url:%s,header:%s,body:%s",
                url, JSON.toJSONString(header), response.body()));
        KorapayRequestReferenceDto requestEntity = JSON.parseObject(response.body(), KorapayRequestReferenceDto.class);

        if (requestEntity == null) {
            throw new PayValidatedException("Payment platform is abnormal, please try again later or contact customer" +
                    " service.");
        }

        if (!requestEntity.isStatus() || (!PayConstants.KORAPAY_SUCCESS_STATUS.equals(requestEntity.getData().getStatus())) && !PayConstants.KORAPAY_PROCESSING_STATUS.equals(requestEntity.getData().getStatus())) {
            if (Strings.isNullOrEmpty(requestEntity.getMessage())) {
                throw new PayValidatedException("Payment platform is abnormal, please try again later or contact " +
                        "customer" +
                        " service.");
            } else {
                throw new PayValidatedException(requestEntity.getMessage());
            }
        }

        return requestEntity;
    }
}
