package com.jeesharp.business.modules.food.service.impl;

import cn.hutool.core.util.IdUtil;
import com.jeesharp.business.modules.food.entity.FoodCompanyAccount;
import com.jeesharp.business.modules.food.entity.FoodCompanyAccountLog;
import com.jeesharp.business.modules.food.entity.FoodMealOrder;
import com.jeesharp.business.modules.food.service.FoodCompanyAccountLedgerService;
import com.jeesharp.business.modules.food.service.FoodCompanyAccountLogService;
import com.jeesharp.business.modules.food.service.FoodCompanyAccountService;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * {@link FoodCompanyAccountLedgerService} 实现。
 *
 * @author jeesharp
 * @since 2026-06-11
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class FoodCompanyAccountLedgerServiceImpl implements FoodCompanyAccountLedgerService {

    private static final String CHANGE_TYPE_FREEZE = "FREEZE";
    private static final String CHANGE_TYPE_UNFREEZE = "UNFREEZE";
    private static final String CHANGE_TYPE_CONSUME = "CONSUME";
    private static final String CHANGE_TYPE_REFUND = "REFUND";
    private static final String BIZ_TYPE_MEAL_ORDER = "MEAL_ORDER";
    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);

    private final FoodCompanyAccountService foodCompanyAccountService;
    private final FoodCompanyAccountLogService foodCompanyAccountLogService;

    @Override
    @Transactional(readOnly = false)
    public void freezeAmount(FoodCompanyAccount account, FoodMealOrder order, BigDecimal amount, String source, String operatorId) {
        BigDecimal beforeAvailable = money(account.getAvailableBalance());
        BigDecimal beforeFrozen = money(account.getFrozenBalance());
        BigDecimal afterAvailable = money(beforeAvailable.subtract(amount));
        BigDecimal afterFrozen = money(beforeFrozen.add(amount));
        if (afterAvailable.compareTo(ZERO) < 0) {
            throw new BizErrorException("企业账户余额不足");
        }
        account.setAvailableBalance(afterAvailable);
        account.setFrozenBalance(afterFrozen);
        foodCompanyAccountService.updateAccount(account);
        insertAccountLog(account, order, CHANGE_TYPE_FREEZE, amount, beforeAvailable, afterAvailable, beforeFrozen, afterFrozen, source, operatorId);
    }

    @Override
    @Transactional(readOnly = false)
    public void unfreezeAmount(FoodCompanyAccount account, FoodMealOrder order, BigDecimal amount, String source, String operatorId) {
        if (account == null) {
            throw new BizErrorException("企业账户不存在");
        }
        BigDecimal beforeAvailable = money(account.getAvailableBalance());
        BigDecimal beforeFrozen = money(account.getFrozenBalance());
        BigDecimal afterAvailable = money(beforeAvailable.add(amount));
        BigDecimal afterFrozen = money(beforeFrozen.subtract(amount));
        if (afterFrozen.compareTo(ZERO) < 0) {
            throw new BizErrorException("企业账户冻结余额不足");
        }
        account.setAvailableBalance(afterAvailable);
        account.setFrozenBalance(afterFrozen);
        foodCompanyAccountService.updateAccount(account);
        insertAccountLog(account, order, CHANGE_TYPE_UNFREEZE, amount, beforeAvailable, afterAvailable, beforeFrozen, afterFrozen, source, operatorId);
    }

    @Override
    @Transactional(readOnly = false)
    public void consumeFrozenAmount(FoodCompanyAccount account, FoodMealOrder order, BigDecimal amount, String source, String operatorId) {
        if (account == null) {
            throw new BizErrorException("企业账户不存在");
        }
        BigDecimal beforeAvailable = money(account.getAvailableBalance());
        BigDecimal beforeFrozen = money(account.getFrozenBalance());
        BigDecimal afterFrozen = money(beforeFrozen.subtract(amount));
        if (afterFrozen.compareTo(ZERO) < 0) {
            throw new BizErrorException("企业账户冻结余额不足");
        }
        account.setFrozenBalance(afterFrozen);
        account.setTotalConsumeAmount(money(account.getTotalConsumeAmount()).add(amount));
        foodCompanyAccountService.updateAccount(account);
        insertAccountLog(account, order, CHANGE_TYPE_CONSUME, amount, beforeAvailable, beforeAvailable, beforeFrozen, afterFrozen, source, operatorId);
    }

    @Override
    @Transactional(readOnly = false)
    public void refundToAvailable(FoodCompanyAccount account, FoodMealOrder order, BigDecimal amount, String source, String operatorId) {
        if (account == null) {
            throw new BizErrorException("企业账户不存在");
        }
        BigDecimal beforeAvailable = money(account.getAvailableBalance());
        BigDecimal beforeFrozen = money(account.getFrozenBalance());
        BigDecimal beforeTotalRefund = money(account.getTotalRefundAmount());
        BigDecimal afterAvailable = money(beforeAvailable.add(amount));
        BigDecimal afterTotalRefund = beforeTotalRefund.add(amount);
        account.setAvailableBalance(afterAvailable);
        account.setTotalRefundAmount(afterTotalRefund);
        foodCompanyAccountService.updateAccount(account);
        insertAccountLog(account, order, CHANGE_TYPE_REFUND, amount, beforeAvailable, afterAvailable, beforeFrozen, beforeFrozen, source, operatorId);
    }

    private void insertAccountLog(FoodCompanyAccount account, FoodMealOrder order, String changeType, BigDecimal amount,
                                  BigDecimal beforeAvailable, BigDecimal afterAvailable, BigDecimal beforeFrozen,
                                  BigDecimal afterFrozen, String source, String operatorId) {
        FoodCompanyAccountLog accountLog = new FoodCompanyAccountLog();
        accountLog.setId(IdUtil.getSnowflakeNextIdStr());
        accountLog.setAccountId(account.getId());
        accountLog.setCompanyId(account.getCompanyId());
        accountLog.setChangeType(changeType);
        accountLog.setChangeAmount(amount);
        accountLog.setBeforeAvailableBalance(beforeAvailable);
        accountLog.setAfterAvailableBalance(afterAvailable);
        accountLog.setBeforeFrozenBalance(beforeFrozen);
        accountLog.setAfterFrozenBalance(afterFrozen);
        accountLog.setBizType(BIZ_TYPE_MEAL_ORDER);
        accountLog.setBizId(order.getId());
        accountLog.setBizNo(order.getOrderNo());
        accountLog.setOperateSource(source);
        accountLog.setOperatorId(operatorId);
        accountLog.setTenantId(order.getTenantId());
        accountLog.setTenantCode(order.getTenantCode());
        foodCompanyAccountLogService.insertLog(accountLog);
    }

    private static BigDecimal money(BigDecimal value) {
        return value == null ? ZERO : value.setScale(2, RoundingMode.HALF_UP);
    }
}
