package com.yn.bftl.thirdparty.modules.business.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.entity.Account;
import com.yn.bftl.common.modules.account.entity.AccountChangeList;
import com.yn.bftl.common.modules.account.enums.AccountChangeType;
import com.yn.bftl.common.modules.account.enums.ChangeDirection;
import com.yn.bftl.common.modules.company.entity.Company;
import com.yn.bftl.thirdparty.common.exception.YnceErrorException;
import com.yn.bftl.thirdparty.common.repository.AccountChangeListRepository;
import com.yn.bftl.thirdparty.common.repository.AccountRepository;
import com.yn.bftl.thirdparty.common.repository.CompanyRepository;
import com.yn.bftl.thirdparty.common.util.SequenceUtil;
import com.yn.bftl.thirdparty.modules.business.dto.AccountChangeCreateDTO;
import com.yn.bftl.thirdparty.modules.business.service.AccountService;
import org.apache.commons.lang3.BooleanUtils;
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.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 账户服务实现
 *
 * @author huabiao
 * @create 2022/12/17  16:58
 **/
@Service
public class AccountServiceImpl implements AccountService {

    @Resource
    private AccountChangeListRepository accountChangeListRepository;
    @Resource
    private AccountRepository accountRepository;
    @Resource
    private SequenceUtil sequenceUtil;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private CompanyRepository companyRepository;

    @Transactional(rollbackFor = Exception.class)
    public void 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);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void intoAccount(AccountChangeCreateDTO accountChangeCreateDTO) {
        Account account = accountRepository.findDepositAccount(accountChangeCreateDTO.getCompany().getId());
        AccountUpdateDTO accountUpdateDTO = new AccountUpdateDTO();
        accountUpdateDTO.setId(account.getId());
        accountUpdateDTO.setMoveAmount(accountChangeCreateDTO.getAmount());
        accountUpdateDTO.setChangeDirection(accountChangeCreateDTO.getDirection());
        accountUpdateDTO.setAccountChangeType(accountChangeCreateDTO.getType());
        accountUpdateDTO.setRemark(accountChangeCreateDTO.getRemark());
        accountUpdateDTO.setSourceNo(accountChangeCreateDTO.getSourceNo());
        accountUpdateDTO.setSourceCompanyId(accountChangeCreateDTO.getSourceCompanyId());
        accountUpdate(accountUpdateDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean accountUpdate(AccountUpdateDTO accountUpdateDTO) {
        // 加锁，避免并发请求
        String lockKey = accountUpdateDTO.getId() + ":ACCOUNT_UPDATE";
        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();
            }
            // 生成对应异动记录
            createChangeRecord(account, accountUpdateDTO.getChangeDirection(), accountUpdateDTO.getAccountChangeType(),
                    accountUpdateDTO.getMoveAmount(), accountUpdateDTO.getSourceNo(), accountUpdateDTO.getRemark(), sourceCompany);
        } finally {
            // 执行成功和失败都需要删除锁
            stringRedisTemplate.delete(lockKey);
        }
        return true;
    }
}
