package com.ruoyi.web.modules.finance.service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.web.modules.finance.constant.FinanceConstants;
import com.ruoyi.web.modules.finance.domain.BizShopFundLog;
import com.ruoyi.web.modules.finance.domain.BizShopPayAccount;
import com.ruoyi.web.modules.finance.domain.BizShopWallet;
import com.ruoyi.web.modules.finance.domain.BizShopWithdraw;
import com.ruoyi.web.modules.finance.dto.PayAccountSaveDTO;
import com.ruoyi.web.modules.finance.dto.PlatformFundLogQuery;
import com.ruoyi.web.modules.finance.dto.PlatformShopFundRankQuery;
import com.ruoyi.web.modules.finance.dto.PlatformWithdrawSummaryDetailQuery;
import com.ruoyi.web.modules.finance.dto.PlatformWithdrawSummaryQuery;
import com.ruoyi.web.modules.finance.dto.PlatformWithdrawAuditQuery;
import com.ruoyi.web.modules.finance.dto.SellerWithdrawQuery;
import com.ruoyi.web.modules.finance.dto.SellerWithdrawSubmitDTO;
import com.ruoyi.web.modules.finance.dto.WithdrawAuditDTO;
import com.ruoyi.web.modules.finance.mapper.BizShopFundLogMapper;
import com.ruoyi.web.modules.finance.mapper.BizShopPayAccountMapper;
import com.ruoyi.web.modules.finance.mapper.BizShopWalletMapper;
import com.ruoyi.web.modules.finance.mapper.BizShopWithdrawMapper;
import com.ruoyi.web.modules.finance.service.IShopFundService;
import com.ruoyi.web.modules.finance.support.FinanceSupport;
import com.ruoyi.web.modules.finance.vo.FundLogListRowVO;
import com.ruoyi.web.modules.finance.vo.FundOverviewSummaryVO;
import com.ruoyi.web.modules.finance.vo.PayAccountListRowVO;
import com.ruoyi.web.modules.finance.vo.PayAccountOptionVO;
import com.ruoyi.web.modules.finance.vo.PlatformFundOverviewSummaryVO;
import com.ruoyi.web.modules.finance.vo.PlatformShopFundRankRowVO;
import com.ruoyi.web.modules.finance.vo.PlatformWithdrawSummaryDetailRowVO;
import com.ruoyi.web.modules.finance.vo.PlatformWithdrawSummaryRowVO;
import com.ruoyi.web.modules.finance.vo.PlatformWithdrawAuditListRowVO;
import com.ruoyi.web.modules.finance.vo.SellerWithdrawListRowVO;
import com.ruoyi.web.modules.order.domain.BizOrder;
import com.ruoyi.web.modules.order.mapper.BizOrderMapper;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;
import com.ruoyi.web.modules.store.constant.ShopConstants;

@Service
public class ShopFundServiceImpl implements IShopFundService
{
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);

    @Autowired
    private BizShopWalletMapper walletMapper;

    @Autowired
    private BizShopFundLogMapper fundLogMapper;

    @Autowired
    private BizShopWithdrawMapper withdrawMapper;

    @Autowired
    private BizShopPayAccountMapper payAccountMapper;

    @Autowired
    private BizOrderMapper orderMapper;

    @Autowired
    private BizShopMapper shopMapper;

    @Autowired
    private FinanceSupport financeSupport;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void settleOrder(BizOrder order)
    {
        if (order == null || order.getOrderId() == null || order.getShopId() == null)
        {
            return;
        }
        if (fundLogMapper.countByRef(FinanceConstants.REF_TYPE_ORDER, order.getOrderId()) > 0)
        {
            return;
        }
        BigDecimal amount = normalizeAmount(order.getPayAmount());
        if (amount.compareTo(ZERO) <= 0)
        {
            return;
        }
        Date now = new Date();
        BizShopWallet wallet = requireWalletForUpdate(order.getShopId(), now);
        BigDecimal balanceAfter = wallet.getAvailableBalance().add(amount);
        walletMapper.updateBalance(order.getShopId(), balanceAfter, wallet.getFrozenAmount(), now);
        insertFundLog(order.getShopId(), order.getShopName(), order.getOrderNo(),
                FinanceConstants.REASON_ORDER_SETTLE, amount, balanceAfter, ZERO, wallet.getFrozenAmount(),
                FinanceConstants.REF_TYPE_ORDER, order.getOrderId(), now);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitWithdraw(Long shopId, SellerWithdrawSubmitDTO dto, String createBy)
    {
        validateSubmitDto(dto);
        BizShopPayAccount account = payAccountMapper.selectByIdAndShop(dto.getAccountId(), shopId);
        if (account == null)
        {
            throw new ServiceException(FinanceConstants.MSG_ACCOUNT_NOT_FOUND);
        }
        BigDecimal amount = normalizeAmount(dto.getWithdrawAmount());
        if (amount.compareTo(ZERO) <= 0)
        {
            throw new ServiceException(FinanceConstants.MSG_AMOUNT_REQUIRED);
        }
        Date now = new Date();
        BizShopWallet wallet = requireWalletForUpdate(shopId, now);
        if (amount.compareTo(wallet.getAvailableBalance()) > 0)
        {
            throw new ServiceException(FinanceConstants.MSG_AMOUNT_EXCEED);
        }
        String shopName = resolveShopName(shopId);
        String accountMask = financeSupport.maskAccountNo(account.getAccountType(), account.getAccountNo());
        BizShopWithdraw withdraw = new BizShopWithdraw();
        withdraw.setWithdrawNo(financeSupport.nextWithdrawNo());
        withdraw.setShopId(shopId);
        withdraw.setShopName(shopName);
        withdraw.setAccountId(account.getAccountId());
        withdraw.setAccountType(account.getAccountType());
        withdraw.setAccountRealName(account.getRealName());
        withdraw.setAccountNoMask(accountMask);
        withdraw.setWithdrawAmount(amount);
        withdraw.setRemark(StringUtils.trim(dto.getRemark()));
        withdraw.setWithdrawStatus(FinanceConstants.WITHDRAW_STATUS_PENDING);
        withdraw.setApplyTime(now);
        withdraw.setCreateBy(createBy);
        withdrawMapper.insert(withdraw);

        BigDecimal balanceAfter = wallet.getAvailableBalance().subtract(amount);
        BigDecimal frozenAfter = wallet.getFrozenAmount().add(amount);
        walletMapper.updateBalance(shopId, balanceAfter, frozenAfter, now);
        insertFundLog(shopId, shopName, withdraw.getWithdrawNo(), FinanceConstants.REASON_WITHDRAW_APPLY,
                amount.negate(), balanceAfter, amount, frozenAfter, FinanceConstants.REF_TYPE_WITHDRAW,
                withdraw.getWithdrawId(), now);

        Map<String, Object> data = new HashMap<>();
        data.put("withdrawId", withdraw.getWithdrawId());
        data.put("withdrawNo", withdraw.getWithdrawNo());
        data.put("withdrawStatus", withdraw.getWithdrawStatus());
        return data;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveWithdraw(Long withdrawId, String processRemark, String auditBy)
    {
        validateProcessRemark(processRemark);
        BizShopWithdraw withdraw = requirePendingWithdraw(withdrawId);
        Date now = new Date();
        BizShopWithdraw update = new BizShopWithdraw();
        update.setWithdrawId(withdrawId);
        update.setWithdrawStatus(FinanceConstants.WITHDRAW_STATUS_COMPLETED);
        update.setProcessRemark(StringUtils.trim(processRemark));
        update.setAuditBy(auditBy);
        update.setAuditTime(now);
        if (withdrawMapper.updateForAudit(update) == 0)
        {
            throw new ServiceException(FinanceConstants.MSG_WITHDRAW_STATE_CHANGED);
        }

        BizShopWallet wallet = requireWalletForUpdate(withdraw.getShopId(), now);
        BigDecimal amount = withdraw.getWithdrawAmount();
        BigDecimal frozenAfter = wallet.getFrozenAmount().subtract(amount);
        walletMapper.updateBalance(withdraw.getShopId(), wallet.getAvailableBalance(), frozenAfter, now);
        insertFundLog(withdraw.getShopId(), withdraw.getShopName(), withdraw.getWithdrawNo(),
                FinanceConstants.REASON_WITHDRAW_COMPLETE, ZERO, wallet.getAvailableBalance(),
                amount.negate(), frozenAfter, FinanceConstants.REF_TYPE_WITHDRAW, withdrawId, now);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectWithdraw(WithdrawAuditDTO dto, String auditBy)
    {
        if (dto == null || dto.getWithdrawId() == null)
        {
            throw new ServiceException(FinanceConstants.MSG_WITHDRAW_NOT_FOUND);
        }
        validateProcessRemark(dto.getProcessRemark());
        BizShopWithdraw withdraw = requirePendingWithdraw(dto.getWithdrawId());
        Date now = new Date();
        BizShopWithdraw update = new BizShopWithdraw();
        update.setWithdrawId(withdraw.getWithdrawId());
        update.setWithdrawStatus(FinanceConstants.WITHDRAW_STATUS_REJECTED);
        update.setProcessRemark(StringUtils.trim(dto.getProcessRemark()));
        update.setAuditBy(auditBy);
        update.setAuditTime(now);
        if (withdrawMapper.updateForAudit(update) == 0)
        {
            throw new ServiceException(FinanceConstants.MSG_WITHDRAW_STATE_CHANGED);
        }

        BizShopWallet wallet = requireWalletForUpdate(withdraw.getShopId(), now);
        BigDecimal amount = withdraw.getWithdrawAmount();
        BigDecimal balanceAfter = wallet.getAvailableBalance().add(amount);
        BigDecimal frozenAfter = wallet.getFrozenAmount().subtract(amount);
        walletMapper.updateBalance(withdraw.getShopId(), balanceAfter, frozenAfter, now);
        insertFundLog(withdraw.getShopId(), withdraw.getShopName(), withdraw.getWithdrawNo(),
                FinanceConstants.REASON_WITHDRAW_REJECT, amount, balanceAfter, amount.negate(), frozenAfter,
                FinanceConstants.REF_TYPE_WITHDRAW, withdraw.getWithdrawId(), now);
    }

    @Override
    public BigDecimal getAvailableBalance(Long shopId)
    {
        BizShopWallet wallet = walletMapper.selectByShopId(shopId);
        if (wallet == null)
        {
            return ZERO;
        }
        return wallet.getAvailableBalance();
    }

    @Override
    public BigDecimal getPendingSettleAmount(Long shopId)
    {
        BigDecimal amount = orderMapper.sumPendingSettleByShopId(shopId);
        return amount == null ? ZERO : normalizeAmount(amount);
    }

    @Override
    public FundOverviewSummaryVO getOverviewSummary(Long shopId, String username)
    {
        BizShopWallet wallet = walletMapper.selectByShopId(shopId);
        FundOverviewSummaryVO vo = new FundOverviewSummaryVO();
        vo.setAvailableBalance(wallet == null ? ZERO : wallet.getAvailableBalance());
        vo.setFrozenAmount(wallet == null ? ZERO : wallet.getFrozenAmount());
        vo.setPendingSettleAmount(getPendingSettleAmount(shopId));
        vo.setUsername(username);
        vo.setBankAccountCount(payAccountMapper.countBankByShopId(shopId));
        return vo;
    }

    @Override
    public List<FundLogListRowVO> listFundLogs(Long shopId)
    {
        List<FundLogListRowVO> rows = fundLogMapper.selectByShopId(shopId);
        for (FundLogListRowVO row : rows)
        {
            row.setChangeReasonText(financeSupport.toChangeReasonText(row.getChangeReason()));
        }
        return rows;
    }

    @Override
    public PlatformFundOverviewSummaryVO getPlatformOverviewSummary()
    {
        PlatformFundOverviewSummaryVO vo = new PlatformFundOverviewSummaryVO();
        vo.setTotalBalance(normalizeAmount(walletMapper.sumAvailableBalance()));
        vo.setFrozenTotal(normalizeAmount(walletMapper.sumFrozenAmount()));
        BigDecimal pending = orderMapper.sumPlatformPendingSettle();
        vo.setPendingSettleTotal(pending == null ? ZERO : normalizeAmount(pending));
        return vo;
    }

    @Override
    public List<PlatformShopFundRankRowVO> listPlatformShopRanks(PlatformShopFundRankQuery query)
    {
        applyPlatformRankSortDefaults(query);
        List<PlatformShopFundRankRowVO> rows = walletMapper.selectPlatformShopRankList(query);
        for (PlatformShopFundRankRowVO row : rows)
        {
            row.setAvailableBalance(normalizeAmount(row.getAvailableBalance()));
            row.setFrozenAmount(normalizeAmount(row.getFrozenAmount()));
            row.setPendingSettleAmount(normalizeAmount(row.getPendingSettleAmount()));
            row.setDeleted(ShopConstants.DEL_DELETED.equals(row.getDelFlag()));
            row.setShopStatusText(financeSupport.toShopStatusText(row.getShopStatus(), row.getDelFlag()));
        }
        return rows;
    }

    @Override
    public List<FundLogListRowVO> listPlatformFundLogs(PlatformFundLogQuery query)
    {
        List<FundLogListRowVO> rows = fundLogMapper.selectPlatformList(query);
        for (FundLogListRowVO row : rows)
        {
            row.setChangeReasonText(financeSupport.toChangeReasonText(row.getChangeReason()));
        }
        return rows;
    }

    @Override
    public List<PlatformWithdrawSummaryRowVO> listPlatformWithdrawSummary(PlatformWithdrawSummaryQuery query)
    {
        List<PlatformWithdrawSummaryRowVO> rows = withdrawMapper.selectPlatformSummaryList(query);
        for (PlatformWithdrawSummaryRowVO row : rows)
        {
            row.setWithdrawnAmount(normalizeAmount(row.getWithdrawnAmount()));
            row.setPendingAuditAmount(normalizeAmount(row.getPendingAuditAmount()));
            row.setDeleted(ShopConstants.DEL_DELETED.equals(row.getDelFlag()));
            row.setShopStatusText(financeSupport.toShopStatusText(row.getShopStatus(), row.getDelFlag()));
        }
        return rows;
    }

    @Override
    public List<PlatformWithdrawSummaryDetailRowVO> listPlatformWithdrawSummaryDetails(
            PlatformWithdrawSummaryDetailQuery query)
    {
        if (query == null || query.getShopId() == null || query.getShopId() <= 0)
        {
            throw new ServiceException(FinanceConstants.MSG_SHOP_ID_REQUIRED);
        }
        List<PlatformWithdrawSummaryDetailRowVO> rows = withdrawMapper.selectSummaryDetailList(query.getShopId(), query);
        enrichSummaryDetailRows(rows);
        return rows;
    }

    @Override
    public List<SellerWithdrawListRowVO> listSellerWithdraws(Long shopId, SellerWithdrawQuery query)
    {
        List<SellerWithdrawListRowVO> rows = withdrawMapper.selectSellerList(shopId, query);
        enrichWithdrawRows(rows);
        return rows;
    }

    @Override
    public List<PlatformWithdrawAuditListRowVO> listPlatformWithdraws(PlatformWithdrawAuditQuery query)
    {
        List<PlatformWithdrawAuditListRowVO> rows = withdrawMapper.selectPlatformList(query);
        for (PlatformWithdrawAuditListRowVO row : rows)
        {
            row.setAccountTypeText(financeSupport.toAccountTypeText(row.getAccountType()));
            row.setWithdrawStatusText(financeSupport.toWithdrawStatusText(row.getWithdrawStatus()));
            row.setAccountSummary(financeSupport.buildAccountSummary(row.getAccountType(), row.getAccountRealName(),
                    row.getAccountNoMask()));
        }
        return rows;
    }

    @Override
    public int countPendingWithdraws()
    {
        return withdrawMapper.countByStatus(FinanceConstants.WITHDRAW_STATUS_PENDING);
    }

    @Override
    public List<PayAccountListRowVO> listPayAccounts(Long shopId)
    {
        List<PayAccountListRowVO> rows = payAccountMapper.selectByShopId(shopId);
        for (PayAccountListRowVO row : rows)
        {
            row.setAccountTypeText(financeSupport.toAccountTypeText(row.getAccountType()));
            row.setAccountNoMask(financeSupport.maskAccountNo(row.getAccountType(), row.getAccountNo()));
        }
        return rows;
    }

    @Override
    public List<PayAccountOptionVO> listPayAccountOptions(Long shopId)
    {
        List<PayAccountOptionVO> rows = payAccountMapper.selectOptionsByShopId(shopId);
        for (PayAccountOptionVO row : rows)
        {
            row.setAccountTypeText(financeSupport.toAccountTypeText(row.getAccountType()));
            row.setAccountNoMask(financeSupport.maskAccountNo(row.getAccountType(), row.getAccountNo()));
            row.setLabel(financeSupport.buildAccountSummary(row.getAccountType(), row.getRealName(),
                    row.getAccountNoMask()));
        }
        return rows;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPayAccount(Long shopId, PayAccountSaveDTO dto, String operator)
    {
        validatePayAccountDto(dto, false);
        Date now = new Date();
        BizShopPayAccount account = new BizShopPayAccount();
        account.setShopId(shopId);
        account.setAccountType(dto.getAccountType());
        account.setRealName(StringUtils.trim(dto.getRealName()));
        account.setAccountNo(StringUtils.trim(dto.getAccountNo()));
        account.setDelFlag(FinanceConstants.DEL_NORMAL);
        account.setCreateBy(operator);
        account.setCreateTime(now);
        payAccountMapper.insert(account);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePayAccount(Long shopId, PayAccountSaveDTO dto, String operator)
    {
        if (dto.getAccountId() == null)
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NOT_FOUND);
        }
        validatePayAccountDto(dto, true);
        Date now = new Date();
        BizShopPayAccount account = new BizShopPayAccount();
        account.setAccountId(dto.getAccountId());
        account.setShopId(shopId);
        account.setAccountType(dto.getAccountType());
        account.setRealName(StringUtils.trim(dto.getRealName()));
        account.setAccountNo(StringUtils.trim(dto.getAccountNo()));
        account.setUpdateBy(operator);
        account.setUpdateTime(now);
        if (payAccountMapper.update(account) == 0)
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NOT_FOUND);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePayAccount(Long shopId, Long accountId, String operator)
    {
        if (accountId == null)
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NOT_FOUND);
        }
        if (payAccountMapper.logicDelete(accountId, shopId, operator, new Date()) == 0)
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NOT_FOUND);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void seedContactBankAccountFromEntry(Long shopId, String contactName, String bankAccount, String operator)
    {
        if (shopId == null || shopId <= 0)
        {
            return;
        }
        String realName = StringUtils.trim(contactName);
        String accountNo = StringUtils.trim(bankAccount);
        if (StringUtils.isEmpty(realName) || StringUtils.isEmpty(accountNo))
        {
            return;
        }
        if (payAccountMapper.countByShopIdAndAccountNo(shopId, accountNo) > 0)
        {
            return;
        }
        Date now = new Date();
        BizShopPayAccount account = new BizShopPayAccount();
        account.setShopId(shopId);
        account.setAccountType(FinanceConstants.ACCOUNT_TYPE_BANK);
        account.setRealName(realName);
        account.setAccountNo(accountNo);
        account.setDelFlag(FinanceConstants.DEL_NORMAL);
        account.setCreateBy(operator);
        account.setCreateTime(now);
        payAccountMapper.insert(account);
    }

    private void enrichWithdrawRows(List<SellerWithdrawListRowVO> rows)
    {
        for (SellerWithdrawListRowVO row : rows)
        {
            row.setAccountTypeText(financeSupport.toAccountTypeText(row.getAccountType()));
            row.setWithdrawStatusText(financeSupport.toWithdrawStatusText(row.getWithdrawStatus()));
            row.setAccountSummary(financeSupport.buildAccountSummary(row.getAccountType(), row.getAccountRealName(),
                    row.getAccountNoMask()));
        }
    }

    private void enrichSummaryDetailRows(List<PlatformWithdrawSummaryDetailRowVO> rows)
    {
        for (PlatformWithdrawSummaryDetailRowVO row : rows)
        {
            row.setWithdrawAmount(normalizeAmount(row.getWithdrawAmount()));
            row.setAccountTypeText(financeSupport.toAccountTypeText(row.getAccountType()));
            row.setWithdrawStatusText(financeSupport.toWithdrawStatusText(row.getWithdrawStatus()));
            row.setAccountSummary(financeSupport.buildAccountSummary(row.getAccountType(), row.getAccountRealName(),
                    row.getAccountNoMask()));
        }
    }

    private BizShopWithdraw requirePendingWithdraw(Long withdrawId)
    {
        BizShopWithdraw withdraw = withdrawMapper.selectById(withdrawId);
        if (withdraw == null)
        {
            throw new ServiceException(FinanceConstants.MSG_WITHDRAW_NOT_FOUND);
        }
        if (!FinanceConstants.WITHDRAW_STATUS_PENDING.equals(withdraw.getWithdrawStatus()))
        {
            throw new ServiceException(FinanceConstants.MSG_CANNOT_AUDIT);
        }
        return withdraw;
    }

    private BizShopWallet requireWalletForUpdate(Long shopId, Date now)
    {
        BizShopWallet wallet = walletMapper.selectByShopIdForUpdate(shopId);
        if (wallet == null)
        {
            wallet = new BizShopWallet();
            wallet.setShopId(shopId);
            wallet.setAvailableBalance(ZERO);
            wallet.setFrozenAmount(ZERO);
            wallet.setUpdateTime(now);
            walletMapper.insert(wallet);
            wallet = walletMapper.selectByShopIdForUpdate(shopId);
        }
        return wallet;
    }

    private void insertFundLog(Long shopId, String shopName, String bizNo, String changeReason,
            BigDecimal balanceChange, BigDecimal balanceAfter, BigDecimal frozenChange, BigDecimal frozenAfter,
            String refType, Long refId, Date createTime)
    {
        BizShopFundLog log = new BizShopFundLog();
        log.setShopId(shopId);
        log.setShopName(shopName);
        log.setBizNo(bizNo);
        log.setChangeReason(changeReason);
        log.setBalanceChange(balanceChange);
        log.setBalanceAfter(balanceAfter);
        log.setFrozenChange(frozenChange);
        log.setFrozenAfter(frozenAfter);
        log.setRefType(refType);
        log.setRefId(refId);
        log.setCreateTime(createTime);
        fundLogMapper.insert(log);
    }

    private void validateSubmitDto(SellerWithdrawSubmitDTO dto)
    {
        if (dto == null || dto.getAccountId() == null)
        {
            throw new ServiceException(FinanceConstants.MSG_ACCOUNT_REQUIRED);
        }
        if (dto.getWithdrawAmount() == null)
        {
            throw new ServiceException(FinanceConstants.MSG_AMOUNT_REQUIRED);
        }
        if (StringUtils.isNotEmpty(dto.getRemark()) && dto.getRemark().length() > 200)
        {
            throw new ServiceException(FinanceConstants.MSG_REMARK_TOO_LONG);
        }
    }

    private void validateProcessRemark(String processRemark)
    {
        if (StringUtils.isEmpty(StringUtils.trim(processRemark)))
        {
            throw new ServiceException(FinanceConstants.MSG_PROCESS_REMARK_REQUIRED);
        }
        if (processRemark.length() > 200)
        {
            throw new ServiceException(FinanceConstants.MSG_PROCESS_REMARK_TOO_LONG);
        }
    }

    private void validatePayAccountDto(PayAccountSaveDTO dto, boolean update)
    {
        if (dto == null)
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NOT_FOUND);
        }
        if (!FinanceConstants.ACCOUNT_TYPE_BANK.equals(dto.getAccountType())
                && !FinanceConstants.ACCOUNT_TYPE_ALIPAY.equals(dto.getAccountType())
                && !FinanceConstants.ACCOUNT_TYPE_WECHAT.equals(dto.getAccountType()))
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_TYPE_INVALID);
        }
        if (StringUtils.isEmpty(StringUtils.trim(dto.getRealName())))
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NAME_REQUIRED);
        }
        if (StringUtils.isEmpty(StringUtils.trim(dto.getAccountNo())))
        {
            throw new ServiceException(FinanceConstants.MSG_PAY_ACCOUNT_NO_REQUIRED);
        }
    }

    private BigDecimal normalizeAmount(BigDecimal amount)
    {
        if (amount == null)
        {
            return ZERO;
        }
        return amount.setScale(2, RoundingMode.HALF_UP);
    }

    private boolean isAllowedPlatformRankSortColumn(String orderByColumn)
    {
        return "pendingSettleAmount".equals(orderByColumn)
                || "pending_settle_amount".equals(orderByColumn)
                || "frozenAmount".equals(orderByColumn)
                || "frozen_amount".equals(orderByColumn)
                || "availableBalance".equals(orderByColumn)
                || "available_balance".equals(orderByColumn);
    }

    private void applyPlatformRankSortDefaults(PlatformShopFundRankQuery query)
    {
        if (query == null)
        {
            return;
        }
        if (StringUtils.isEmpty(query.getOrderByColumn()))
        {
            query.setOrderByColumn("availableBalance");
        }
        else if (!isAllowedPlatformRankSortColumn(query.getOrderByColumn()))
        {
            query.setOrderByColumn("availableBalance");
        }
        if (StringUtils.isEmpty(query.getIsAsc()))
        {
            query.setIsAsc("desc");
        }
        else if (!"asc".equalsIgnoreCase(query.getIsAsc()) && !"desc".equalsIgnoreCase(query.getIsAsc()))
        {
            query.setIsAsc("desc");
        }
        else
        {
            query.setIsAsc(query.getIsAsc().toLowerCase());
        }
    }

    private String resolveShopName(Long shopId)
    {
        BizShop shop = shopMapper.selectById(shopId);
        return shop == null ? "" : shop.getShopName();
    }
}
