package com.yn.bftl.thirdparty.modules.unionpay.service.impl;

import com.yn.bftl.common.common.exception.YnceError;
import com.yn.bftl.common.modules.account.dto.AccountUpdateDTO;
import com.yn.bftl.common.modules.account.dto.PaymentDTO;
import com.yn.bftl.common.modules.account.dto.RechargeDTO;
import com.yn.bftl.common.modules.account.entity.*;
import com.yn.bftl.common.modules.account.enums.*;
import com.yn.bftl.common.modules.account.vo.AccountPaymentsVO;
import com.yn.bftl.common.modules.account.vo.WithdrawalsVO;
import com.yn.bftl.common.modules.company.entity.Company;
import com.yn.bftl.common.modules.company.entity.ErpAccountSet;
import com.yn.bftl.common.modules.subaccount.entity.AllinpayMutual;
import com.yn.bftl.common.modules.subaccount.enums.DealStatus;
import com.yn.bftl.thirdparty.common.exception.YnceErrorException;
import com.yn.bftl.thirdparty.common.repository.*;
import com.yn.bftl.thirdparty.common.service.BaseMemberService;
import com.yn.bftl.thirdparty.common.util.DateTimeUtils;
import com.yn.bftl.thirdparty.common.util.SequenceUtil;
import com.yn.bftl.thirdparty.modules.unionpay.service.UnionpayAccountService;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 电子账户服务实现类
 *
 * @author huabiao
 * @create 2022/9/7  9:54
 **/
@Service(value = "unionpayAccountServiceImpl")
public class UnionpayAccountServiceImpl extends BaseMemberService implements UnionpayAccountService {

    @Resource
    private CompanyRepository companyRepository;
    @Resource
    private AccountRepository accountRepository;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private SequenceUtil sequenceUtil;
    @Resource
    private AccountChangeListRepository accountChangeListRepository;
    @Resource
    private TopUpListRepository topUpListRepository;
    @Resource
    private TransferListRepository transferListRepository;
    @Resource
    private AllinpayMutualRepository allinpayMutualRepository;
    @Resource
    private ReimbursementRecordRepository reimbursementRecordRepository;

    public final static Map<String, TransferState> TRANSFER_STATE_MAP = new HashMap<String, TransferState>() {{
        put("succeeded", TransferState.SUCCESS);
        put("processing", TransferState.PROCESSING);
        put("failed", TransferState.FAIL);
    }};

    private final static Map<String, DealStatus> DEAL_STATUS_MAP = new HashMap<String, DealStatus>() {{
        put("processing", DealStatus.DEALSTATUS_ING);
        put("succeeded", DealStatus.DEALSTATUS_SUCC);
        put("failed", DealStatus.DEALSTATUS_FAIL);
    }};

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean rechargeMoney(RechargeDTO rechargeDTO) {
        if (rechargeDTO.getCompanyId() == null) {
            rechargeDTO.setCompanyId(getUser().getCompany().getId());
        }
        Account account = accountRepository.findDepositAccount(rechargeDTO.getCompanyId());
        if (account == null) {
            account = this.createDepositAccount(companyRepository.findById(rechargeDTO.getCompanyId()).orElse(null));
        }
        AccountUpdateDTO accountUpdateDTO = new AccountUpdateDTO();
        accountUpdateDTO.setId(account.getId());
        accountUpdateDTO.setMoveAmount(rechargeDTO.getRechargeAmount());
        accountUpdateDTO.setChangeDirection(ChangeDirection.ADD);
        accountUpdateDTO.setAccountChangeType(AccountChangeType.TOPUP);
        accountUpdateDTO.setRemark(rechargeDTO.getRemarks());
        accountUpdateDTO.setSourceNo(rechargeDTO.getSourceNo());
        accountUpdateDTO.setSourceCompanyId(rechargeDTO.getCompanyId());
        this.accountUpdate(accountUpdateDTO);
        //生成充值记录
        this.createChangeRecord(rechargeDTO.getRechargeAmount(), account, "PAID",
                rechargeDTO.getRemarks(), rechargeDTO.getPaymentMethod(), rechargeDTO.getErpAccountSet(), rechargeDTO.getFinishedDate());
        return true;
    }

    /**
     * 充值记录-生成
     *
     * @param sum
     * @param account
     * @param state
     * @param remark
     * @param paidMode
     * @param erpAccountSet ERP账套
     * @param finishedDate
     * @return
     */
    public boolean createChangeRecord(BigDecimal sum, Account account, String state, String remark,
                                      String paidMode, ErpAccountSet erpAccountSet, LocalDateTime finishedDate) {
        TopUpList topUpList = new TopUpList();
        topUpList.setCompany(account.getCompany());
        topUpList.setTopUpNo(sequenceUtil.getNextSequenceNumber("CZ"));
        topUpList.setAmount(sum);
        topUpList.setAccount(account);
        topUpList.setState(TopUpState.valueOf(state));
        topUpList.setFinishTime(finishedDate);
        topUpList.setRemark(remark);
        topUpList.setPaidMode(paidMode);
        topUpList.setErpAccountSet(erpAccountSet);
        topUpListRepository.saveAndFlush(topUpList);
        return true;
    }

    /**
     * 账户-更新余额
     *
     * @param accountUpdateDTO
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean accountUpdate(AccountUpdateDTO accountUpdateDTO) {
        // 加锁，避免并发请求
        String lockKey = getAccountUpdateLockKey(accountUpdateDTO.getId());
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockKey);
        int i = 1;
        // 拿不到锁时，睡眠一段时间然后重试，重试超过指定次数还未拿到则放弃
        while (!lock) {
            if (i > 10) {
                throw new YnceErrorException(YnceError.YNCE_000000);
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new YnceErrorException(YnceError.YNCE_000000);
            }
            lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockKey);
            i++;
        }

        stringRedisTemplate.expire(lockKey, 5, TimeUnit.SECONDS);
        try {
            // 获取账户信息
            Optional<Account> accountOptional = accountRepository.findById(accountUpdateDTO.getId());
            if (BooleanUtils.isFalse(accountOptional.isPresent())) {
                throw new YnceErrorException(YnceError.YNCE_205004);
            }
            Account account = accountOptional.get();
            // 更新账户数据
            if (accountUpdateDTO.getChangeDirection().equals(ChangeDirection.ADD)) {
                account.setBalance(account.getBalance().add(accountUpdateDTO.getMoveAmount()));
            } else {
                if (account.getBalance().compareTo(accountUpdateDTO.getMoveAmount()) == -1) {
                    throw new YnceErrorException(YnceError.YNCE_205005, account.getType().getName() + "账户");
                }
                account.setBalance(account.getBalance().subtract(accountUpdateDTO.getMoveAmount()));
            }
            accountRepository.saveAndFlush(account);
            Company sourceCompany = null;
            if (accountUpdateDTO.getSourceCompanyId() != null) {
                Optional<Company> companyOpt = companyRepository.findById(accountUpdateDTO.getSourceCompanyId());
                sourceCompany = companyOpt.get();
            }
            // 生成对应异动记录
            this.createChangeRecord(account, accountUpdateDTO.getChangeDirection(), accountUpdateDTO.getAccountChangeType(),
                    accountUpdateDTO.getMoveAmount(), accountUpdateDTO.getSourceNo(), accountUpdateDTO.getRemark(), sourceCompany);
        } finally {
            // 执行成功和失败都需要删除锁
            stringRedisTemplate.delete(lockKey);
        }
        return true;
    }

    /**
     * 转账后续处理
     *
     * @param amount
     * @param inputAccount
     * @param outputAccount
     * @param transferResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void transferHandle(BigDecimal amount, Account inputAccount, Account outputAccount, AccountPaymentsVO transferResult) {
        TransferList transferList = transferListRepository.findByTransOrderNo(transferResult.getOutOrderNo());
        if (!TransferState.PROCESSING.equals(transferList.getState())) {
            return;
        }
        transferList.setState(TRANSFER_STATE_MAP.get(transferResult.getStatus()));
        transferList.setMctOrderNo(transferResult.getPaymentId());
        transferList.setReason(transferResult.getReason());
        if (StringUtils.isNotEmpty(transferResult.getFinishedAt())) {
            // transferList.setFinishTime(LocalDateTime.parse(transferResult.getFinishedAt(),
            //         DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSzzz")));
            transferList.setFinishTime(DateTimeUtils.getLocalDateTimeByRFC3339(transferResult.getFinishedAt()));
        }
        transferListRepository.save(transferList);
        if (TransferState.SUCCESS.equals(transferList.getState())) {
            // 更新账户数据
            outputAccount.setBalance(outputAccount.getBalance().subtract(amount));
            inputAccount.setBalance(inputAccount.getBalance().add(amount));
            accountRepository.saveAndFlush(inputAccount);
            accountRepository.saveAndFlush(outputAccount);

            // 生成对应异动记录
            this.createChangeRecord(inputAccount, ChangeDirection.ADD,
                    AccountChangeType.TRANSFER, amount, transferResult.getOutOrderNo(),
                    StringUtils.isNotBlank(transferResult.getRemark()) ? transferResult.getRemark() : "转账-来自" + outputAccount.getCompany().getName(), outputAccount.getCompany());
            this.createChangeRecord(outputAccount, ChangeDirection.REDUCE,
                    AccountChangeType.TRANSFER, amount, transferResult.getOutOrderNo(),
                    StringUtils.isNotEmpty(transferList.getRemark()) ? transferList.getRemark() : inputAccount.getCompany().getName(), inputAccount.getCompany());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void withdrawalsHandle(Account account, WithdrawalsVO withdrawalsVO) {
        AllinpayMutual dbAllinpayMutual = allinpayMutualRepository.findFirstByBizOrderNo(withdrawalsVO.getOutOrderNo());
        if (!DealStatus.DEALSTATUS_ING.equals(dbAllinpayMutual.getDealStatus())) {
            return;
        }
        dbAllinpayMutual.setOriBizOrderNo(withdrawalsVO.getWithdrawalId());
        dbAllinpayMutual.setDealStatus(DEAL_STATUS_MAP.get(withdrawalsVO.getStatus()));
        dbAllinpayMutual.setAttrs(withdrawalsVO.getReason());
        if (DealStatus.DEALSTATUS_SUCC.equals(dbAllinpayMutual.getDealStatus())) {
            // dbAllinpayMutual.setFinishedTime(LocalDateTime.parse(withdrawalsVO.getFinishedAt(),
            //         DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSzzz")));
            dbAllinpayMutual.setFinishedTime(DateTimeUtils.getLocalDateTimeByRFC3339(withdrawalsVO.getFinishedAt()));
            AccountUpdateDTO accountUpdateDTO = new AccountUpdateDTO();
            accountUpdateDTO.setId(account.getId());
            accountUpdateDTO.setMoveAmount(new BigDecimal(withdrawalsVO.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
            accountUpdateDTO.setChangeDirection(ChangeDirection.REDUCE);
            accountUpdateDTO.setAccountChangeType(AccountChangeType.WITHDRAWDEPOSIT);
            accountUpdateDTO.setSourceNo(withdrawalsVO.getOutOrderNo());
            accountUpdateDTO.setSourceCompanyId(account.getCompany().getId());
            accountUpdateDTO.setRemark("提现");
            this.accountUpdate(accountUpdateDTO);
        }
        allinpayMutualRepository.save(dbAllinpayMutual);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unionpayCreditRefundHandle(PaymentDTO paymentDTO, Long saleCompanyId, AccountPaymentsVO transferResult) {
        ReimbursementRecord record = reimbursementRecordRepository.findByOrderNo(transferResult.getOutOrderNo());
        if (!TransferState.PROCESSING.equals(record.getState())) {
            return;
        }
        record.setState(TRANSFER_STATE_MAP.get(transferResult.getStatus()));
        record.setUnionpayPaymentId(transferResult.getPaymentId());
        record.setReason(transferResult.getReason());
        if (StringUtils.isNotEmpty(transferResult.getFinishedAt())) {
            // transferList.setFinishTime(LocalDateTime.parse(transferResult.getFinishedAt(),
            //         DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSzzz")));
            record.setFinishTime(DateTimeUtils.getLocalDateTimeByRFC3339(transferResult.getFinishedAt()));
        }
        reimbursementRecordRepository.save(record);
        if (TransferState.SUCCESS.equals(record.getState())) {
            Long companyId = record.getCompany().getId();
            String remark = StringUtils.isNotBlank(paymentDTO.getRemarks()) ? paymentDTO.getRemarks() : "信用还款";
            // 预存款账户
            Account depositAccount = accountRepository.findDepositAccount(companyId);
            // 扣除预存款
            AccountUpdateDTO depositAccountUpdate = new AccountUpdateDTO();
            depositAccountUpdate.setId(depositAccount.getId());
            depositAccountUpdate.setMoveAmount(paymentDTO.getPaymentAmount());
            depositAccountUpdate.setChangeDirection(ChangeDirection.REDUCE);
            depositAccountUpdate.setAccountChangeType(AccountChangeType.REIMBURSEMENT);
            depositAccountUpdate.setSourceNo(transferResult.getOutOrderNo());
            depositAccountUpdate.setSourceCompanyId(companyId);
            depositAccountUpdate.setRemark(remark);
            this.accountUpdate(depositAccountUpdate);
            // 信用还款
            AccountUpdateDTO creditAccountUpdate = new AccountUpdateDTO();
            creditAccountUpdate.setId(paymentDTO.getId());
            creditAccountUpdate.setMoveAmount(paymentDTO.getPaymentAmount());
            creditAccountUpdate.setChangeDirection(ChangeDirection.ADD);
            creditAccountUpdate.setAccountChangeType(AccountChangeType.REIMBURSEMENT);
            creditAccountUpdate.setSourceNo(transferResult.getOutOrderNo());
            creditAccountUpdate.setSourceCompanyId(companyId);
            creditAccountUpdate.setRemark(remark);
            this.accountUpdate(creditAccountUpdate);

            // 收款方预存款账户
            // Account receiptDepositAccount = accountRepository.findDepositAccount(saleCompanyId);
            // 收款方增加预存款
            /*AccountUpdateDTO receiptDepositAccountUpdate = new AccountUpdateDTO();
            receiptDepositAccountUpdate.setId(receiptDepositAccount.getId());
            receiptDepositAccountUpdate.setMoveAmount(paymentDTO.getPaymentAmount());
            receiptDepositAccountUpdate.setChangeDirection(ChangeDirection.ADD);
            receiptDepositAccountUpdate.setAccountChangeType(AccountChangeType.REIMBURSEMENT);
            receiptDepositAccountUpdate.setSourceNo(transferResult.getOutOrderNo());
            receiptDepositAccountUpdate.setSourceCompanyId(companyId);
            receiptDepositAccountUpdate.setRemark(transferResult.getRemark());
            this.accountUpdate(receiptDepositAccountUpdate);*/
        }
    }

    /**
     * 创建异动记录
     *
     * @param account   账户
     * @param direction 异动方向
     * @param type      异动类型
     * @param sum       金额
     * @param sourceNo  来源单号
     * @param remark    备注
     * @return 是否成功
     */
    public boolean createChangeRecord(Account account, ChangeDirection direction, AccountChangeType type,
                                      BigDecimal sum, String sourceNo, String remark, Company sourceCompany) {
        AccountChangeList accountChangeList = new AccountChangeList();
        accountChangeList.setAccount(account);
        accountChangeList.setDirection(direction);
        accountChangeList.setType(type);
        accountChangeList.setSum(direction.equals(ChangeDirection.ADD) ? sum : sum.negate());
        accountChangeList.setTime(LocalDateTime.now());
        accountChangeList.setRemark(remark);
        accountChangeList.setSerialNumber(sequenceUtil.getNextSequenceNumber("YD"));
        accountChangeList.setSourceNo(sourceNo);
        accountChangeList.setSourceCompany(sourceCompany);
        accountChangeList.setBalance(account.getBalance());
        accountChangeListRepository.saveAndFlush(accountChangeList);
        return true;
    }

    private Account createDepositAccount(Company company) {
        Account account = new Account();
        account.setType(AccountType.DEPOSIT);
        account.setBalance(BigDecimal.ZERO);
        account.setState(AccountState.ENABLED);
        account.setCompany(company);
        accountRepository.saveAndFlush(account);
        return account;
    }

    /**
     * 获取账户更新锁key
     *
     * @param accountId
     * @return
     */
    @Override
    public String getAccountUpdateLockKey(Long accountId) {
        return accountId + ":ACCOUNT_UPDATE";
    }
}
