package org.dromara.main.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.main.domain.CompanyAccountFlow;
import org.dromara.main.mapper.CompanyAccountFlowMapper;
import org.dromara.main.service.ICompanyAccountService;
import org.dromara.system.domain.SysTenant;
import org.dromara.system.mapper.SysTenantMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;

/**
 * 企业账户服务实现
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class CompanyAccountServiceImpl implements ICompanyAccountService {

    private final SysTenantMapper sysTenantMapper;
    private final CompanyAccountFlowMapper companyAccountFlowMapper;

    @Override
    public Boolean checkAvailableBalance(Long companyId, BigDecimal amount) {
        SysTenant tenant = getTenant(companyId);
        return defaultAmount(tenant.getAvailableBalance()).compareTo(defaultAmount(amount)) >= 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addCommission(Long companyId, BigDecimal amount, Long orderId, String orderNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal availableBefore = defaultAmount(tenant.getAvailableBalance());
        BigDecimal availableAfter = availableBefore.add(delta);

        updateTenantBalance(tenant.getId(), availableAfter, null, null, null);
        insertFlow(tenant, 1, delta, 1, availableBefore, availableAfter, 4, orderId, orderNo, remark);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean payByBalance(Long companyId, BigDecimal amount, Long orderId, String orderNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal availableBefore = defaultAmount(tenant.getAvailableBalance());
        BigDecimal inUseBefore = defaultAmount(tenant.getInUseBalance());
        if (availableBefore.compareTo(delta) < 0) {
            throw new ServiceException("可使用余额不足，当前可用余额：{}元", availableBefore);
        }

        BigDecimal availableAfter = availableBefore.subtract(delta);
        BigDecimal inUseAfter = inUseBefore.add(delta);
        updateTenantBalance(tenant.getId(), availableAfter, inUseAfter, null, null);

        insertFlow(tenant, 2, delta, 1, availableBefore, availableAfter, 2, orderId, orderNo, remark);
        insertFlow(tenant, 1, delta, 2, inUseBefore, inUseAfter, 2, orderId, orderNo, remark + "（冻结）");
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deductInUseBalance(Long companyId, BigDecimal amount, Long orderId, String orderNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal inUseBefore = defaultAmount(tenant.getInUseBalance());
        BigDecimal totalConsumeBefore = defaultAmount(tenant.getTotalConsume());
        if (inUseBefore.compareTo(delta) < 0) {
            throw new ServiceException("使用中余额不足");
        }

        BigDecimal inUseAfter = inUseBefore.subtract(delta);
        BigDecimal totalConsumeAfter = totalConsumeBefore.add(delta);
        updateTenantBalance(tenant.getId(), null, inUseAfter, null, totalConsumeAfter);

        insertFlow(tenant, 2, delta, 2, inUseBefore, inUseAfter, 8, orderId, orderNo, remark);
        insertFlow(tenant, 1, delta, 4, totalConsumeBefore, totalConsumeAfter, 8, orderId, orderNo, remark);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean refundToAvailable(Long companyId, BigDecimal amount, Long orderId, String orderNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal inUseBefore = defaultAmount(tenant.getInUseBalance());
        BigDecimal availableBefore = defaultAmount(tenant.getAvailableBalance());
        if (inUseBefore.compareTo(delta) < 0) {
            throw new ServiceException("使用中余额不足");
        }

        BigDecimal inUseAfter = inUseBefore.subtract(delta);
        BigDecimal availableAfter = availableBefore.add(delta);
        updateTenantBalance(tenant.getId(), availableAfter, inUseAfter, null, null);

        insertFlow(tenant, 2, delta, 2, inUseBefore, inUseAfter, 3, orderId, orderNo, remark + "（解冻）");
        insertFlow(tenant, 1, delta, 1, availableBefore, availableAfter, 3, orderId, orderNo, remark);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean applyWithdraw(Long companyId, BigDecimal amount, String withdrawNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal availableBefore = defaultAmount(tenant.getAvailableBalance());
        BigDecimal withdrawingBefore = defaultAmount(tenant.getWithdrawingBalance());
        if (availableBefore.compareTo(delta) < 0) {
            throw new ServiceException("可使用余额不足，当前可用余额：{}元", availableBefore);
        }

        BigDecimal availableAfter = availableBefore.subtract(delta);
        BigDecimal withdrawingAfter = withdrawingBefore.add(delta);
        updateTenantBalance(tenant.getId(), availableAfter, null, withdrawingAfter, null);

        insertFlow(tenant, 2, delta, 1, availableBefore, availableAfter, 5, null, withdrawNo, remark);
        insertFlow(tenant, 1, delta, 3, withdrawingBefore, withdrawingAfter, 5, null, withdrawNo, remark + "（冻结）");
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deductWithdrawingBalance(Long companyId, BigDecimal amount, String withdrawNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal withdrawingBefore = defaultAmount(tenant.getWithdrawingBalance());
        if (withdrawingBefore.compareTo(delta) < 0) {
            throw new ServiceException("提现中余额不足");
        }

        BigDecimal withdrawingAfter = withdrawingBefore.subtract(delta);
        updateTenantBalance(tenant.getId(), null, null, withdrawingAfter, null);

        insertFlow(tenant, 2, delta, 3, withdrawingBefore, withdrawingAfter, 6, null, withdrawNo, remark);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean refundWithdrawToAvailable(Long companyId, BigDecimal amount, String withdrawNo, String remark) {
        SysTenant tenant = getTenant(companyId);
        BigDecimal delta = defaultAmount(amount);
        BigDecimal withdrawingBefore = defaultAmount(tenant.getWithdrawingBalance());
        BigDecimal availableBefore = defaultAmount(tenant.getAvailableBalance());
        if (withdrawingBefore.compareTo(delta) < 0) {
            throw new ServiceException("提现中余额不足");
        }

        BigDecimal withdrawingAfter = withdrawingBefore.subtract(delta);
        BigDecimal availableAfter = availableBefore.add(delta);
        updateTenantBalance(tenant.getId(), availableAfter, null, withdrawingAfter, null);

        insertFlow(tenant, 2, delta, 3, withdrawingBefore, withdrawingAfter, 7, null, withdrawNo, remark + "（解冻）");
        insertFlow(tenant, 1, delta, 1, availableBefore, availableAfter, 7, null, withdrawNo, remark);
        return true;
    }

    private SysTenant getTenant(Long companyId) {
        SysTenant tenant = sysTenantMapper.selectById(companyId);
        if (tenant == null) {
            throw new ServiceException("企业不存在");
        }
        return tenant;
    }

    private void updateTenantBalance(Long tenantId, BigDecimal availableBalance, BigDecimal inUseBalance,
                                     BigDecimal withdrawingBalance, BigDecimal totalConsume) {
        SysTenant update = new SysTenant();
        update.setId(tenantId);
        update.setAvailableBalance(availableBalance);
        update.setInUseBalance(inUseBalance);
        update.setWithdrawingBalance(withdrawingBalance);
        update.setTotalConsume(totalConsume);
        sysTenantMapper.updateById(update);
    }

    private void insertFlow(SysTenant tenant, Integer flowType, BigDecimal amount, Integer balanceType,
                            BigDecimal balanceBefore, BigDecimal balanceAfter, Integer businessType,
                            Long businessId, String businessNo, String remark) {
        CompanyAccountFlow flow = new CompanyAccountFlow();
        flow.setCompanyId(tenant.getId());
        flow.setTenantId(tenant.getTenantId());
        flow.setFlowNo(generateFlowNo());
        flow.setFlowType(flowType);
        flow.setAmount(amount);
        flow.setBalanceType(balanceType);
        flow.setBalanceBefore(balanceBefore);
        flow.setBalanceAfter(balanceAfter);
        flow.setBusinessType(businessType);
        flow.setBusinessId(businessId);
        flow.setBusinessNo(businessNo);
        flow.setRemark(remark);
        companyAccountFlowMapper.insert(flow);
    }

    private BigDecimal defaultAmount(BigDecimal amount) {
        return amount == null ? BigDecimal.ZERO : amount;
    }

    private String generateFlowNo() {
        return "CF" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4);
    }
}
