package com.liquidnet.service.account.wallet.service.processor;

import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.account.common.ErrorConstants;
import com.liquidnet.service.account.common.FinConstants;
import com.liquidnet.service.account.exception.FinaccountException;
import com.liquidnet.service.account.funding.dto.base.FundingCallResult;
import com.liquidnet.service.account.funding.dto.base.FundingContextParam;
import com.liquidnet.service.account.funding.service.processor.FundingTransactionProcessor;
import com.liquidnet.service.account.util.FinUtil;
import com.liquidnet.service.account.wallet.dto.WalletTransactionResult;
import com.liquidnet.service.account.wallet.dto.base.WalletContextParam;
import com.liquidnet.service.account.wallet.entity.FinBizTrans;
import com.liquidnet.service.account.wallet.entity.FinWallet;
import com.liquidnet.service.account.wallet.service.FinBizTransService;
import com.liquidnet.service.account.wallet.service.FinWalletService;
import com.liquidnet.service.account.wallet.service.WalletProcessorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * WalletTransactionProcessService.class
 *
 * @author zhanggb
 * Created by IntelliJ IDEA at 2020/10/23
 */
@Service
public class WalletTransactionProcessor extends WalletProcessorService {
    private static final Logger log = LoggerFactory.getLogger(WalletTransactionProcessor.class);
    @Autowired
    private FundingTransactionProcessor fundingTransactionProcessor;
    @Autowired
    private FinWalletService walletService;
    @Autowired
    private FinBizTransService bizTransService;

    @Override
    public WalletTransactionResult checkInputParams(WalletContextParam context) {
        if (StringUtils.isEmpty(context.getWalletNo()) || context.getWalletNo().length() > 11) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid walletNo");
        }
        if (null == context.getTransType()) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid transType");
        }
        if (BigDecimal.ZERO.compareTo(context.getAmount()) >= 0) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid amount");
        }
        if (StringUtils.isEmpty(context.getTracingNo()) || context.getTracingNo().length() > 64) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid tracingNo");
        }
        if (null == context.getTracingTime()) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid tracingTime");
        }
        if (StringUtils.isEmpty(context.getTracingTitle()) || context.getTracingTitle().length() > 100) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid tracingTitle");
        }
        if (!StringUtils.isEmpty(context.getTracingDesc()) && context.getTracingDesc().length() > 100) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Toolong tracingDesc");
        }
        return new WalletTransactionResult();
    }

    @Override
    public WalletTransactionResult checkBussinessLogic(WalletContextParam context) {
        return new WalletTransactionResult();
    }

    @Override
    public WalletTransactionResult doBussiness(WalletContextParam context) {
        FinWallet wallet = walletService.query(context.getWalletNo());
        if (null == wallet) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Invalid walletNo");
        }
        if (FinConstants.Status.NORMAL.getVal() != wallet.getStatus()) {
            return new WalletTransactionResult(ErrorConstants.PARAMS_VALUE_ERROR_CODE, "Disabled wallet");
        }

        context.setTransNo(FinUtil.getTransNo());
        context.setFinId(wallet.getFinId());

        WalletTransactionResult result = null;
        boolean recordTransSucc = false;
        try {
            recordTransSucc = this.transactionRecordGeneration(context);

            result = this.transactionProcessing(context);
        } catch (Exception e) {
            log.error("Abnormal operation of wallet transaction:[transNo={}]", context.getTransNo());
            if (context.getTransType().isForce()) {
                FinBizTrans transInfo = this.transactionRecordInitialization(context);

                log.warn("Compensation Mechanism of wallet transaction:[transInfo={},billNo={}|{}]",
                        JsonUtils.toJson(transInfo), FinUtil.getBillNo(), FinUtil.getBillNo());

                // TODO: 2020/12/8 Compensation Mechanism


                result = new WalletTransactionResult(context.getTransNo());
            } else {
                result = new WalletTransactionResult(ErrorConstants.WALLET_TRANS_ERROR_CODE, ErrorConstants.WALLET_TRANS_ERROR_DESC);
            }
        }

        if (null != result && result.isFailed() && recordTransSucc) {
            try {
                FinBizTrans updateObj = FinBizTrans.getNew();
                updateObj.setTransNo(context.getTransNo());
                updateObj.setTransStatus(FinConstants.TxStatus.FAILED.getVal());
                updateObj.setReasons(result.getCode().concat(":").concat(result.getMessage()));
                updateObj.setUpdateTime(LocalDateTime.now());
                bizTransService.update(updateObj);
            } catch (Exception e) {
                log.error("Abnormal operation of wallet transaction update:[transNo={}]", context.getTransNo());
            }
        }
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public WalletTransactionResult transactionProcessing(WalletContextParam context) {
        switch (context.getTransType().getVal()) {
            case "C":
                return this.creditProcessing(context);
            case "D":
                return this.debitProcessing(context);
            default:
                return new WalletTransactionResult(ErrorConstants.WALLET_TRANS_ERROR_CODE, "Invalid transType");
        }
    }

    private WalletTransactionResult creditProcessing(WalletContextParam context) {
        String walletNo = context.getWalletNo();
        String availableAccNo = FinUtil.getAccNo(FinConstants.AccType.AVAILABLE.getVal(), walletNo);
        String pendingAccNo = FinUtil.getAccNo(FinConstants.AccType.PENDING.getVal(), walletNo);

        FundingContextParam fundingTransactionParam = FundingContextParam.getNew();
        fundingTransactionParam.setAmount(context.getAmount());
        fundingTransactionParam.setFinId(context.getFinId());
        fundingTransactionParam.setWalletNo(walletNo);
        fundingTransactionParam.setTransNo(context.getTransNo());
        fundingTransactionParam.setTracingNo(context.getTracingNo());
        fundingTransactionParam.setTracingTime(context.getTracingTime());

        FundingCallResult<?> fundingTransactionResult = null;
        switch (context.getTransType()) {
            case CREDIT_ONWAY:
                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.CREDIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            case CREDIT_CONFIRM:
                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.DEBIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }

                fundingTransactionParam.setAccNo(availableAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.CREDIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            case CREDIT_CANCEL:
                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.DEBIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            default:
                return new WalletTransactionResult(ErrorConstants.BUSINESS_ERROR_CODE,
                        String.format("Unknown transType[%s]", context.getTransType().getVal()));
        }
        return new WalletTransactionResult(context.getTransNo());
    }

    private WalletTransactionResult debitProcessing(WalletContextParam context) {
        String walletNo = context.getWalletNo();
        String availableAccNo = FinUtil.getAccNo(FinConstants.AccType.AVAILABLE.getVal(), walletNo);
        String pendingAccNo = FinUtil.getAccNo(FinConstants.AccType.PENDING.getVal(), walletNo);

        FundingContextParam fundingTransactionParam = FundingContextParam.getNew();
        fundingTransactionParam.setAmount(context.getAmount());
        fundingTransactionParam.setFinId(context.getFinId());
        fundingTransactionParam.setWalletNo(walletNo);
        fundingTransactionParam.setTransNo(context.getTransNo());
        fundingTransactionParam.setTracingNo(context.getTracingNo());
        fundingTransactionParam.setTracingTime(context.getTracingTime());

        FundingCallResult<?> fundingTransactionResult = null;
        switch (context.getTransType()) {
            case DEBIT_ONWAY:
                fundingTransactionParam.setAccNo(availableAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.DEBIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }

                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.CREDIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            case DEBIT_CONFIRM:
                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.DEBIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            case DEBIT_CANCEL:
                fundingTransactionParam.setAccNo(availableAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.CREDIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }

                fundingTransactionParam.setAccNo(pendingAccNo);
                fundingTransactionParam.setCategory(FinConstants.CDType.DEBIT);
                fundingTransactionResult = fundingTransactionProcessor.service(fundingTransactionParam);
                if (null == fundingTransactionResult || !fundingTransactionResult.isSuccess()) {
                    log.warn("Abnormal operation of funding account:params=[{}]", JsonUtils.toJson(fundingTransactionParam));
                    log.warn("Abnormal operation of funding account:result=[{}]", JsonUtils.toJson(fundingTransactionResult));
                    throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Abnormal fund operation");
                }
                break;
            default:
                return new WalletTransactionResult(ErrorConstants.BUSINESS_ERROR_CODE,
                        String.format("Unknown transType[%s]", context.getTransType().getVal()));
        }
        return new WalletTransactionResult(context.getTransNo());
    }

    private FinBizTrans transactionRecordInitialization(WalletContextParam context) {
        FinBizTrans transInfo = FinBizTrans.getNew();
        BeanUtils.copyProperties(context, transInfo);
        transInfo.setTransType(context.getTransType().name());
        transInfo.setTransNo(context.getTransNo());
        transInfo.setFinId(context.getFinId());
        transInfo.setTransStatus(FinConstants.TxStatus.SUCCESS.getVal());
        transInfo.setTradeNo(context.getTradeNo());
        transInfo.setCreateTime(context.getCreateTime());
        return transInfo;
    }

    private boolean transactionRecordGeneration(WalletContextParam context) {
        FinBizTrans transInfo = this.transactionRecordInitialization(context);
        if (bizTransService.add(transInfo) <= 0) {
            log.warn("Add data failed[biz_trans]:[{}]", JsonUtils.toJson(transInfo));
            throw new FinaccountException(ErrorConstants.BUSINESS_ERROR_CODE, "Failed to persist data");
        }
        return true;
    }

//    private void transactionRecordUpdation(String transNo, WalletTransactionResult result) {
//        try {
//            if (!result.isProcessing()) {
//                FinConstants.TxStatus rst = result.isSuccess() ? FinConstants.TxStatus.SUCCESS : FinConstants.TxStatus.FAILED;
//
//                FinBizTrans updateInfo = FinBizTrans.getNew();
//                updateInfo.setTransNo(transNo);
//                updateInfo.setUpdateTime(Calendar.getInstance().getTime());
//                updateInfo.setTransStatus(rst.getVal());
//                updateInfo.setReasons(result.isSuccess() ? "" : result.getMessage());
//                if (bizTransService.update(updateInfo) <= 0) {
//                    log.warn("Update data failed[biz_trans.status]:[{} -> {}]", transNo, rst.name());
//                }
//            }
//        } catch (Exception e) {
//            log.error("Update data abnormal[biz_trans]:{},[{}]", transNo, JsonUtils.toJson(result), e);
//        }
//    }
}
