package com.bonus.canteen.core.account.business;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bonus.canteen.core.account.constants.AccTradeStateEnum;
import com.bonus.canteen.core.account.constants.AccTradeTypeEnum;
import com.bonus.canteen.core.account.constants.WalletBalanceOperationEnum;
import com.bonus.canteen.core.account.domain.AccountTrade;
import com.bonus.canteen.core.account.domain.AccTradeWalletDetail;
import com.bonus.canteen.core.account.domain.AccWalletInfo;
import com.bonus.canteen.core.account.domain.WalletUpdateDTO;
import com.bonus.canteen.core.account.domain.bo.WalletBalanceOperation;
import com.bonus.canteen.core.account.mapper.AccWalletInfoMapper;
import com.bonus.canteen.core.account.service.IAccTradeService;
import com.bonus.canteen.core.account.service.IAccTradeWalletDetailService;
import com.bonus.canteen.core.account.utils.AccRedisUtils;
import com.bonus.canteen.core.pay.constants.PayStateEnum;
import com.bonus.common.core.constant.SecurityConstants;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.core.utils.DateUtils;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.common.houqin.utils.JacksonUtil;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.system.api.RemoteUserService;
import com.bonus.system.api.domain.SysUser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Component
public class AccWalletInfoBusiness {
    private static final Logger log = LoggerFactory.getLogger(AccWalletInfoBusiness.class);
    @Autowired
    private AccWalletInfoMapper accWalletInfoMapper;
    @Resource
    private RemoteUserService remoteUserService;

    @Autowired
    IAccTradeService accTradeService;
    @Autowired
    IAccTradeWalletDetailService accTradeWalletDetailService;

    //此方法不可以直接调用，须通过service中的方法调用
    @Transactional(rollbackFor = Exception.class)
    public void addAccWalletBalance(WalletUpdateDTO walletUpdateDTO) {
        log.info("新增钱包入参: {}", JacksonUtil.writeValueAsString(walletUpdateDTO));
        WalletBalanceOperation operation = operationBuilder(
                walletUpdateDTO.getAmount(),
                walletUpdateDTO.getUserId(),
                walletUpdateDTO.getAccWalletTypeEnum().getKey(),
                walletUpdateDTO.getAccTradeTypeEnum().getKey(),
                walletUpdateDTO.getPayChannelEnum().getKey(),
                walletUpdateDTO.getPayTypeEnum().getKey(),
                walletUpdateDTO.getDeviceSn(),
                walletUpdateDTO.getOperationUser());
        operation.setOperationType(WalletBalanceOperationEnum.ADD_BAL.getKey());
        operation.setOrderNo(walletUpdateDTO.getOrderNo());
        acWalletBalanceOperation(operation);
        log.info("新增钱包结束");
    }

    //此方法不可以直接调用，须通过service中的方法调用
    @Transactional(rollbackFor = Exception.class)
    public void clearAccWalletBalance(WalletUpdateDTO walletUpdateDTO) {
        log.info("扣减钱包入参: {}", JacksonUtil.writeValueAsString(walletUpdateDTO));
        try {
            WalletBalanceOperation operation = operationBuilder(
                    walletUpdateDTO.getAmount(),
                    walletUpdateDTO.getUserId(),
                    walletUpdateDTO.getAccWalletTypeEnum().getKey(),
                    walletUpdateDTO.getAccTradeTypeEnum().getKey(),
                    walletUpdateDTO.getPayChannelEnum().getKey(),
                    walletUpdateDTO.getPayTypeEnum().getKey(),
                    walletUpdateDTO.getDeviceSn(),
                    walletUpdateDTO.getOperationUser());
            operation.setOperationType(WalletBalanceOperationEnum.REDUCE_BAL.getKey());
            operation.setOrderNo(walletUpdateDTO.getOrderNo());
            acWalletBalanceOperation(operation);
            log.info("扣减钱包结束");
        } finally {
            if(!walletUpdateDTO.isBatch()) {
                AccRedisUtils.unlockUpdateAccWalletBalance(walletUpdateDTO.getUserId());
            }
        }
    }

    private WalletBalanceOperation operationBuilder(BigDecimal amount, Long userId, Integer walletType,
                                                    Integer tradeType, Integer payChannel, Integer payType,
                                                    String deviceSn, String operationUser) {
        WalletBalanceOperation operation = new WalletBalanceOperation();
        operation.setUserId(userId);
        operation.setAmount(amount);
        operation.setWalletType(walletType);
        operation.setTradeType(tradeType);
        operation.setPayChannel(payChannel);
        operation.setPayType(payType);
        operation.setDeviceSn(deviceSn);
        operation.setOperationUser(StringUtils.isBlank(operationUser) ? SecurityUtils.getUsername() : operationUser);
        return operation;
    }


    private void acWalletBalanceOperation(WalletBalanceOperation operation) {
        Long tradeId = null;
        try{
            List<AccWalletInfo> walletInfoList = selectAccWalletInfoByUserId(operation.getUserId());
            BigDecimal accBalTotal = walletInfoList.stream().map(AccWalletInfo::getWalletBal)
                    .filter(ObjectUtil::isNotNull).reduce(BigDecimal.ZERO, BigDecimal::add);
            tradeId = insertAccTradeRecode(operation, accBalTotal);
            if(WalletBalanceOperationEnum.getEnum(operation.getOperationType()) == null) {
                throw new ServiceException("钱包操作类型不存在");
            }
            if(operation.getAmount().compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("钱包操作金额不能小于0");
            }

            switch (WalletBalanceOperationEnum.getEnum(operation.getOperationType())) {
                case ADD_BAL:
                    addAccWalletInfo(operation);
                    break;
                case REDUCE_BAL:
                    reduceAccWalletInfo(operation);
                    break;
                default:
                    throw new ServiceException("钱包操作类型错误");
            }
            walletInfoList = selectAccWalletInfoByUserId(operation.getUserId());
            AccTradeWalletDetail accTradeWalletDetail = new AccTradeWalletDetail();
            accTradeWalletDetail.setTradeId(tradeId);
            accTradeWalletDetail.setUserId(operation.getUserId());
            accTradeWalletDetail.setTradeType(operation.getTradeType());
            accTradeWalletDetail.setAmount(operation.getAmount());
            accTradeWalletDetail.setWalletType(operation.getWalletType());
            BigDecimal walletBalByWalletType = walletInfoList.stream().filter((item) -> {
                return item.getWalletType().equals(operation.getWalletType());
            }).map(AccWalletInfo::getWalletBal).filter(ObjectUtil::isNotNull).reduce(BigDecimal.ZERO, BigDecimal::add);
            accTradeWalletDetail.setWalletBal(walletBalByWalletType);
            accTradeWalletDetail.setTradeTime(DateUtils.getNowDate());
            accTradeWalletDetail.setCreateBy(StringUtils.isBlank(operation.getOperationUser()) ?
                    SecurityUtils.getUsername() : operation.getOperationUser());
            this.accTradeWalletDetailService.insertAccTradeWalletDetail(accTradeWalletDetail);
            AccountTrade accountTrade = new AccountTrade();
            accountTrade.setTradeId(tradeId);
            accountTrade.setPayState(PayStateEnum.PAY_SUCC.getKey());
            accountTrade.setTradeState(AccTradeStateEnum.TAKE_EFFECT.getKey());
            accountTrade.setUpdateBy(StringUtils.isBlank(operation.getOperationUser()) ?
                    SecurityUtils.getUsername() : operation.getOperationUser());
            accountTrade.setWalletBalTotal(walletBalByWalletType);
            accountTrade.setAccountAllBal(walletBalByWalletType);
            accountTrade.setMachineSn(operation.getDeviceSn());
            this.accTradeService.updateAccTrade(accountTrade);
        }catch (Exception ex) {
            if(Objects.nonNull(tradeId)) {
                AccountTrade accountTrade = new AccountTrade();
                accountTrade.setTradeId(tradeId);
                accountTrade.setPayState(PayStateEnum.PAY_FAIL.getKey());
                accountTrade.setTradeState(AccTradeStateEnum.CANCELED.getKey());
                accountTrade.setUpdateBy(SecurityUtils.getUsername());
                accountTrade.setFailReason(StringUtils.substring(ex.getMessage(), 0, 100));
                this.accTradeService.updateAccTrade(accountTrade);
            }
            log.error("修改钱包失败", ex);
            throw new ServiceException(ex.getMessage());
        }
    }

    private void addAccWalletInfo(WalletBalanceOperation operation) {
        accWalletInfoMapper.addAccWalletInfo(operation.getAmount(), operation.getUserId(), operation.getWalletType(),
                SecurityUtils.getUserId().toString(), DateUtils.toLocalDateTime(new Date()));
    }

    private void reduceAccWalletInfo(WalletBalanceOperation operation) {
        AccWalletInfo walletInfo = selectAccWalletInfoByUserIdAndWalletType(operation.getUserId(),
                operation.getWalletType());
        if(Objects.isNull(walletInfo)
                || walletInfo.getWalletBal().compareTo(BigDecimal.ZERO) < 0
                || (walletInfo.getWalletBal().compareTo(operation.getAmount()) < 0)) {
            throw new ServiceException("钱包余额不足");
        }
        if(AccTradeTypeEnum.CLEAR.getKey().equals(operation.getOperationType())) {
            operation.setAmount(walletInfo.getWalletBal());
        }
        accWalletInfoMapper.reduceAccWalletInfo(operation.getAmount(), operation.getUserId(), operation.getWalletType(),
                SecurityUtils.getUserId().toString(), DateUtils.toLocalDateTime(new Date()));
    }

    private long insertAccTradeRecode(WalletBalanceOperation operation, BigDecimal accBalTotal) {
        AjaxResult userResult = remoteUserService.getInfo(operation.getUserId() , SecurityConstants.INNER);
        SysUser sysUser = null;
        if(Objects.nonNull(userResult)) {
            sysUser = JSONObject.parseObject(JSON.toJSONString(userResult.get(AjaxResult.DATA_TAG)), SysUser.class);
        }
        LocalDateTime tradeTime = LocalDateTime.now();
        AccountTrade accountTrade = new AccountTrade();
        accountTrade.setTradeTime(tradeTime);
        accountTrade.setTradeType(operation.getTradeType());
        accountTrade.setAmount(operation.getAmount());
        accountTrade.setActualAmount(operation.getAmount());
        accountTrade.setWalletBalTotal(accBalTotal);
        accountTrade.setAccountAllBal(accBalTotal);
        accountTrade.setPayState(PayStateEnum.PAY_INPROCESS.getKey());
        accountTrade.setTradeState(AccTradeStateEnum.CREATE.getKey());
        accountTrade.setUserId(operation.getUserId());
        accountTrade.setPayChannel(operation.getPayChannel());
        accountTrade.setPayType(operation.getPayType());
        accountTrade.setCreateBy(StringUtils.isBlank(operation.getOperationUser()) ?
                SecurityUtils.getUsername() : operation.getOperationUser());
        accountTrade.setUpdateBy(StringUtils.isBlank(operation.getOperationUser()) ?
                SecurityUtils.getUsername() : operation.getOperationUser());
        if(Objects.nonNull(sysUser)) {
            accountTrade.setDeptId(sysUser.getDeptId());
        }
        if(Objects.nonNull(operation.getOrderNo())) {
            accountTrade.setOrderNo(operation.getOrderNo());
        }
        accountTrade.setMachineSn(operation.getDeviceSn());
        this.accTradeService.insertAccTrade(accountTrade);
        return accountTrade.getTradeId();
    }

    public List<AccWalletInfo> selectAccWalletInfoByUserId(Long userId) {
        return accWalletInfoMapper.selectAccWalletInfoByUserId(userId);
    }

    public AccWalletInfo selectAccWalletInfoByUserIdAndWalletType(Long userId, Integer walletType) {
        return accWalletInfoMapper.selectAccWalletInfoByUserIdAndWalletType(userId, walletType);
    }
}
