package com.jeesharp.business.modules.food.application;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.finance.api.PayOrderApi;
import com.jeesharp.business.modules.finance.payorder.vo.PayOrderIntentVO;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordBizSceneEnum;
import com.jeesharp.business.modules.food.dto.FoodCompanyRechargeConfirmDTO;
import com.jeesharp.business.modules.food.dto.FoodCompanyRechargeCreateDTO;
import com.jeesharp.business.modules.food.dto.FoodCompanyRechargePaidDTO;
import com.jeesharp.business.modules.food.entity.FoodCompanyAccount;
import com.jeesharp.business.modules.food.entity.FoodCompanyAccountLog;
import com.jeesharp.business.modules.food.entity.FoodCompanyRecharge;
import com.jeesharp.business.modules.food.service.FoodCompanyAccountLogService;
import com.jeesharp.business.modules.food.service.FoodCompanyAccountService;
import com.jeesharp.business.modules.food.service.FoodCompanyRechargeService;
import com.jeesharp.business.modules.food.vo.FoodCompanyAccountInfoVO;
import com.jeesharp.business.modules.food.vo.FoodCompanyAccountLogListVO;
import com.jeesharp.business.modules.food.vo.FoodCompanyAccountLogQueryVO;
import com.jeesharp.business.modules.food.vo.FoodCompanyRechargeResultVO;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import com.jeesharp.framework.utils.WebFrameworkUtils;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 企业团餐充值应用服务。
 *
 * @author jeesharp
 * @since 2026-06-07
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class FoodCompanyRechargeApplicationService {

    private static final String CHANNEL_OFFLINE = "OFFLINE";
    private static final String CHANNEL_ONLINE = "ONLINE";
    private static final int STATUS_OFFLINE_WAIT_CONFIRM = 0;
    private static final int STATUS_ONLINE_WAIT_PAY = 1;
    private static final int STATUS_PAID = 2;
    private static final String CHANGE_TYPE_RECHARGE = "RECHARGE";
    private static final String BIZ_TYPE_RECHARGE = "RECHARGE";
    private static final String SOURCE_ADMIN = "ADMIN";
    private static final String SOURCE_PAY_NOTIFY = "PAY_NOTIFY";
    private static final String RECHARGE_NO_PREFIX = "FCR";
    private static final DateTimeFormatter NO_TIME_FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private final FoodCompanyRechargeService foodCompanyRechargeService;
    private final FoodCompanyAccountService foodCompanyAccountService;
    private final FoodCompanyAccountLogService foodCompanyAccountLogService;
    private final PayOrderApi payOrderApi;

    @Transactional(readOnly = false)
    public FoodCompanyRechargeResultVO createOfflineRecharge(FoodCompanyRechargeCreateDTO dto) {
        log.info("企业线下充值申请开始 companyId={}, amount={}, operatorName={}", dto.getCompanyId(), dto.getRechargeAmount(), WebFrameworkUtils.getLoginUserName());
        FoodCompanyAccount account = getEnabledAccount(dto.getCompanyId());
        FoodCompanyRecharge recharge = buildRecharge(dto, account, CHANNEL_OFFLINE, STATUS_OFFLINE_WAIT_CONFIRM);
        insertRecharge(recharge);
        log.info("企业线下充值申请已创建 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, amount={}, channel={}, status={}",
                recharge.getCompanyId(), recharge.getAccountId(), recharge.getId(), recharge.getRechargeNo(),
                recharge.getRechargeAmount(), recharge.getRechargeChannel(), recharge.getStatus());
        return toRechargeResult(recharge);
    }

    @Transactional(readOnly = false)
    public FoodCompanyRechargeResultVO createOnlineRecharge(FoodCompanyRechargeCreateDTO dto) {
        log.info("企业线上充值申请开始 companyId={}, memberId={}, amount={}", dto.getCompanyId(), dto.getMemberId(), dto.getRechargeAmount());
        FoodCompanyAccount account = getEnabledAccount(dto.getCompanyId());
        FoodCompanyRecharge recharge = buildRecharge(dto, account, CHANNEL_ONLINE, STATUS_ONLINE_WAIT_PAY);
        insertRecharge(recharge);
        log.info("企业线上充值单已创建 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, amount={}, status={}",
                recharge.getCompanyId(), recharge.getAccountId(), recharge.getId(), recharge.getRechargeNo(),
                recharge.getRechargeAmount(), recharge.getStatus());
        PayOrderIntentVO intent = createUnifiedPayOrderIntent(recharge, dto);
        recharge.setPayOrderId(intent.getPayOrderId());
        recharge.setPayOrderNo(intent.getOutTradeNo());
        updateRecharge(recharge);
        log.info("企业线上充值支付单已绑定 companyId={}, rechargeId={}, rechargeNo={}, payOrderId={}, payOrderNo={}, amount={}",
                recharge.getCompanyId(), recharge.getId(), recharge.getRechargeNo(), recharge.getPayOrderId(),
                recharge.getPayOrderNo(), recharge.getRechargeAmount());
        return toRechargeResult(recharge, intent);
    }

    @Transactional(readOnly = false)
    public FoodCompanyRechargeResultVO confirmOfflineRecharge(FoodCompanyRechargeConfirmDTO dto) {
        log.info("企业线下充值确认开始 rechargeId={}, operatorName={}", dto.getRechargeId(), WebFrameworkUtils.getLoginUserName());
        FoodCompanyRecharge recharge = foodCompanyRechargeService.lockById(dto.getRechargeId(), WebFrameworkUtils.getLoginUserTenantCode());
        if (recharge == null) {
            log.warn("企业线下充值确认失败，充值单不存在 rechargeId={}", dto.getRechargeId());
            throw new BizErrorException("充值单不存在");
        }
        log.info("企业线下充值单已锁定 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, amount={}, channel={}, status={}",
                recharge.getCompanyId(), recharge.getAccountId(), recharge.getId(), recharge.getRechargeNo(),
                recharge.getRechargeAmount(), recharge.getRechargeChannel(), recharge.getStatus());
        if (Integer.valueOf(STATUS_PAID).equals(recharge.getStatus())) {
            log.info("企业线下充值确认幂等返回 rechargeId={}, rechargeNo={}, status={}", recharge.getId(), recharge.getRechargeNo(), recharge.getStatus());
            return toRechargeResult(recharge);
        }
        if (!CHANNEL_OFFLINE.equals(recharge.getRechargeChannel())
                || !Integer.valueOf(STATUS_OFFLINE_WAIT_CONFIRM).equals(recharge.getStatus())) {
            log.warn("企业线下充值确认状态非法 rechargeId={}, rechargeNo={}, channel={}, status={}",
                    recharge.getId(), recharge.getRechargeNo(), recharge.getRechargeChannel(), recharge.getStatus());
            throw new BizErrorException("当前充值单不可线下确认");
        }
        FoodCompanyAccount account = lockAccount(recharge);
        log.info("企业线下充值账户已锁定 companyId={}, accountId={}, rechargeId={}, availableBalance={}, frozenBalance={}",
                account.getCompanyId(), account.getId(), recharge.getId(), account.getAvailableBalance(), account.getFrozenBalance());
        LocalDateTime now = LocalDateTime.now();
        recharge.setConfirmBy(WebFrameworkUtils.getLoginUserId());
        recharge.setConfirmTime(now);
        recharge.setPaidTime(now);
        postRecharge(recharge, account, SOURCE_ADMIN, WebFrameworkUtils.getLoginUserId(), WebFrameworkUtils.getLoginUserName());
        return toRechargeResult(recharge);
    }

    @Transactional(readOnly = false)
    public FoodCompanyRechargeResultVO handleOnlineRechargePaid(FoodCompanyRechargePaidDTO dto) {
        log.info("企业线上充值支付回调开始 rechargeId={}, payOrderId={}, payOrderNo={}, paidAmount={}, paidTime={}",
                dto.getRechargeId(), dto.getPayOrderId(), dto.getPayOrderNo(), dto.getPaidAmount(), dto.getPaidTime());
        FoodCompanyRecharge recharge = lockRechargeForPaid(dto);
        if (recharge == null) {
            log.warn("企业线上充值支付回调失败，充值单不存在 rechargeId={}, payOrderId={}, payOrderNo={}",
                    dto.getRechargeId(), dto.getPayOrderId(), dto.getPayOrderNo());
            throw new BizErrorException("充值单不存在");
        }
        log.info("企业线上充值单已锁定 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, amount={}, channel={}, status={}",
                recharge.getCompanyId(), recharge.getAccountId(), recharge.getId(), recharge.getRechargeNo(),
                recharge.getRechargeAmount(), recharge.getRechargeChannel(), recharge.getStatus());
        if (Integer.valueOf(STATUS_PAID).equals(recharge.getStatus())) {
            log.info("企业线上充值支付回调幂等返回 rechargeId={}, rechargeNo={}, paidAmount={}, status={}",
                    recharge.getId(), recharge.getRechargeNo(), dto.getPaidAmount(), recharge.getStatus());
            assertPaidAmountMatched(recharge, dto.getPaidAmount());
            return toRechargeResult(recharge);
        }
        if (!CHANNEL_ONLINE.equals(recharge.getRechargeChannel())
                || !Integer.valueOf(STATUS_ONLINE_WAIT_PAY).equals(recharge.getStatus())) {
            log.warn("企业线上充值支付回调状态非法 rechargeId={}, rechargeNo={}, channel={}, status={}",
                    recharge.getId(), recharge.getRechargeNo(), recharge.getRechargeChannel(), recharge.getStatus());
            throw new BizErrorException("当前充值单不可支付入账");
        }
        assertPaidAmountMatched(recharge, dto.getPaidAmount());
        FoodCompanyAccount account = lockAccount(recharge);
        log.info("企业线上充值账户已锁定 companyId={}, accountId={}, rechargeId={}, availableBalance={}, frozenBalance={}",
                account.getCompanyId(), account.getId(), recharge.getId(), account.getAvailableBalance(), account.getFrozenBalance());
        recharge.setPaidTime(dto.getPaidTime() != null ? dto.getPaidTime() : LocalDateTime.now());
        postRecharge(recharge, account, SOURCE_PAY_NOTIFY, null, null);
        return toRechargeResult(recharge);
    }

    public FoodCompanyAccountInfoVO getAccountByCompanyId(String companyId) {
        FoodCompanyAccount account = foodCompanyAccountService.getByCompanyId(companyId, WebFrameworkUtils.getLoginUserTenantCode());
        return toAccountInfo(account);
    }

    public PageRespVO<FoodCompanyAccountLogListVO> findAccountLogPage(Page<FoodCompanyAccountLog> page, FoodCompanyAccountLogQueryVO query) {
        if (query == null) {
            query = new FoodCompanyAccountLogQueryVO();
        }
        Page<FoodCompanyAccountLog> result = foodCompanyAccountLogService.findLogPage(page, query);
        PageRespVO<FoodCompanyAccountLogListVO> resp = new PageRespVO<>();
        resp.setTotal(result.getTotal());
        resp.setPageNo(result.getPageNo());
        resp.setPageSize(result.getPageSize());
        List<FoodCompanyAccountLogListVO> rows = new ArrayList<>();
        if (result.getRecords() != null) {
            for (FoodCompanyAccountLog log : result.getRecords()) {
                rows.add(toAccountLogList(log));
            }
        }
        resp.setRecords(rows);
        return resp;
    }

    private PayOrderIntentVO createUnifiedPayOrderIntent(FoodCompanyRecharge recharge, FoodCompanyRechargeCreateDTO dto) {
        return payOrderApi.createIntentForJsapi(
                PayRecordBizSceneEnum.FOOD_COMPANY_RECHARGE.getCode(),
                recharge.getId(),
                recharge.getRechargeAmount(),
                dto.getMemberId(),
                StrUtil.blankToDefault(dto.getSubject(), "企业团餐充值"),
                StrUtil.blankToDefault(dto.getDescription(), "企业团餐账户充值"),
                dto.getNotifyUrl(),
                dto.getChannelUserId(),
                dto.getUserIp(),
                recharge.getTenantId());
    }

    private FoodCompanyRecharge buildRecharge(FoodCompanyRechargeCreateDTO dto, FoodCompanyAccount account, String channel, int status) {
        if (dto.getRechargeAmount() == null || dto.getRechargeAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizErrorException("充值金额必须大于0");
        }
        FoodCompanyRecharge recharge = new FoodCompanyRecharge();
        recharge.setId(IdUtil.getSnowflakeNextIdStr());
        recharge.setRechargeNo(generateRechargeNo());
        recharge.setCompanyId(account.getCompanyId());
        recharge.setAccountId(account.getId());
        recharge.setRechargeAmount(dto.getRechargeAmount());
        recharge.setRechargeChannel(channel);
        recharge.setStatus(status);
        recharge.setApplyTime(LocalDateTime.now());
        return recharge;
    }

    private FoodCompanyAccount getEnabledAccount(String companyId) {
        if (StrUtil.isBlank(companyId)) {
            throw new BizErrorException("企业ID不能为空");
        }
        FoodCompanyAccount account = foodCompanyAccountService.getByCompanyId(companyId.trim(), WebFrameworkUtils.getLoginUserTenantCode());
        if (account == null) {
            throw new BizErrorException("企业账户不存在");
        }
        if (!Integer.valueOf(1).equals(account.getStatus())) {
            throw new BizErrorException("企业账户未启用");
        }
        return account;
    }

    private FoodCompanyAccount lockAccount(FoodCompanyRecharge recharge) {
        FoodCompanyAccount account = foodCompanyAccountService.lockById(recharge.getAccountId(), recharge.getTenantCode());
        if (account == null) {
            throw new BizErrorException("企业账户不存在");
        }
        if (!Integer.valueOf(1).equals(account.getStatus())) {
            throw new BizErrorException("企业账户未启用");
        }
        return account;
    }

    private FoodCompanyRecharge lockRechargeForPaid(FoodCompanyRechargePaidDTO dto) {
        if (StrUtil.isNotBlank(dto.getRechargeId())) {
            return foodCompanyRechargeService.lockById(dto.getRechargeId(), WebFrameworkUtils.getLoginUserTenantCode());
        }
        if (StrUtil.isNotBlank(dto.getPayOrderId())) {
            return foodCompanyRechargeService.lockByPayOrderId(dto.getPayOrderId());
        }
        if (StrUtil.isNotBlank(dto.getPayOrderNo())) {
            return foodCompanyRechargeService.lockByPayOrderNo(dto.getPayOrderNo());
        }
        throw new BizErrorException("充值单或支付单标识不能为空");
    }

    private void postRecharge(FoodCompanyRecharge recharge, FoodCompanyAccount account, String source,
                              String operatorId, String operatorName) {
        BigDecimal amount = recharge.getRechargeAmount() == null ? BigDecimal.ZERO : recharge.getRechargeAmount();
        BigDecimal beforeAvailable = nvl(account.getAvailableBalance());
        BigDecimal beforeFrozen = nvl(account.getFrozenBalance());
        BigDecimal beforeTotalRecharge = nvl(account.getTotalRechargeAmount());
        BigDecimal afterAvailable = beforeAvailable.add(amount);
        BigDecimal afterTotalRecharge = beforeTotalRecharge.add(amount);
        log.info("企业充值入账开始 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, amount={}, source={}, beforeAvailable={}, beforeFrozen={}, beforeTotalRecharge={}",
                account.getCompanyId(), account.getId(), recharge.getId(), recharge.getRechargeNo(), amount, source,
                beforeAvailable, beforeFrozen, beforeTotalRecharge);
        account.setAvailableBalance(afterAvailable);
        account.setTotalRechargeAmount(afterTotalRecharge);
        updateAccount(account);
        log.info("企业充值账户余额已更新 companyId={}, accountId={}, rechargeId={}, amount={}, afterAvailable={}, afterFrozen={}, afterTotalRecharge={}",
                account.getCompanyId(), account.getId(), recharge.getId(), amount, afterAvailable, beforeFrozen, afterTotalRecharge);

        recharge.setStatus(STATUS_PAID);
        updateRecharge(recharge);
        log.info("企业充值单状态已更新为已入账 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, status={}, paidTime={}",
                recharge.getCompanyId(), recharge.getAccountId(), recharge.getId(), recharge.getRechargeNo(), recharge.getStatus(), recharge.getPaidTime());

        FoodCompanyAccountLog accountLog = new FoodCompanyAccountLog();
        accountLog.setId(IdUtil.getSnowflakeNextIdStr());
        accountLog.setAccountId(account.getId());
        accountLog.setCompanyId(account.getCompanyId());
        accountLog.setChangeType(CHANGE_TYPE_RECHARGE);
        accountLog.setChangeAmount(amount);
        accountLog.setBeforeAvailableBalance(beforeAvailable);
        accountLog.setAfterAvailableBalance(afterAvailable);
        accountLog.setBeforeFrozenBalance(beforeFrozen);
        accountLog.setAfterFrozenBalance(beforeFrozen);
        accountLog.setBizType(BIZ_TYPE_RECHARGE);
        accountLog.setBizId(recharge.getId());
        accountLog.setBizNo(recharge.getRechargeNo());
        accountLog.setOperateSource(source);
        accountLog.setOperatorId(operatorId);
        accountLog.setOperatorName(operatorName);
        insertAccountLog(accountLog);
        log.info("企业充值账户流水已写入 companyId={}, accountId={}, rechargeId={}, rechargeNo={}, logId={}, changeType={}, amount={}, beforeAvailable={}, afterAvailable={}, beforeFrozen={}, afterFrozen={}, source={}",
                account.getCompanyId(), account.getId(), recharge.getId(), recharge.getRechargeNo(), accountLog.getId(),
                accountLog.getChangeType(), amount, beforeAvailable, afterAvailable, beforeFrozen, beforeFrozen, source);
    }

    private void assertPaidAmountMatched(FoodCompanyRecharge recharge, BigDecimal paidAmount) {
        if (paidAmount == null || recharge.getRechargeAmount() == null
                || recharge.getRechargeAmount().compareTo(paidAmount) != 0) {
            log.warn("企业充值支付金额不一致 rechargeId={}, rechargeNo={}, expectedAmount={}, paidAmount={}",
                    recharge.getId(), recharge.getRechargeNo(), recharge.getRechargeAmount(), paidAmount);
            throw new BizErrorException("支付金额与充值金额不一致");
        }
    }

    private void insertRecharge(FoodCompanyRecharge recharge) {
        foodCompanyRechargeService.insertRecharge(recharge);
    }

    private void updateRecharge(FoodCompanyRecharge recharge) {
        foodCompanyRechargeService.updateRecharge(recharge);
    }

    private void updateAccount(FoodCompanyAccount account) {
        foodCompanyAccountService.updateAccount(account);
    }

    private void insertAccountLog(FoodCompanyAccountLog log) {
        foodCompanyAccountLogService.insertLog(log);
    }

    private FoodCompanyRechargeResultVO toRechargeResult(FoodCompanyRecharge recharge) {
        return toRechargeResult(recharge, null);
    }

    private FoodCompanyRechargeResultVO toRechargeResult(FoodCompanyRecharge recharge, PayOrderIntentVO intent) {
        if (recharge == null) {
            return null;
        }
        FoodCompanyRechargeResultVO vo = new FoodCompanyRechargeResultVO();
        vo.setId(recharge.getId());
        vo.setRechargeNo(recharge.getRechargeNo());
        vo.setCompanyId(recharge.getCompanyId());
        vo.setAccountId(recharge.getAccountId());
        vo.setRechargeAmount(recharge.getRechargeAmount());
        vo.setRechargeChannel(recharge.getRechargeChannel());
        vo.setPayOrderId(recharge.getPayOrderId());
        vo.setPayOrderNo(recharge.getPayOrderNo());
        vo.setTenantId(intent != null ? intent.getTenantId() : recharge.getTenantId());
        vo.setStatus(recharge.getStatus());
        vo.setApplyTime(recharge.getApplyTime());
        vo.setPaidTime(recharge.getPaidTime());
        vo.setConfirmTime(recharge.getConfirmTime());
        return vo;
    }

    private FoodCompanyAccountInfoVO toAccountInfo(FoodCompanyAccount account) {
        if (account == null) {
            return null;
        }
        FoodCompanyAccountInfoVO vo = new FoodCompanyAccountInfoVO();
        vo.setId(account.getId());
        vo.setCompanyId(account.getCompanyId());
        vo.setAvailableBalance(account.getAvailableBalance());
        vo.setFrozenBalance(account.getFrozenBalance());
        vo.setTotalRechargeAmount(account.getTotalRechargeAmount());
        vo.setTotalConsumeAmount(account.getTotalConsumeAmount());
        vo.setTotalRefundAmount(account.getTotalRefundAmount());
        vo.setStatus(account.getStatus());
        return vo;
    }

    private FoodCompanyAccountLogListVO toAccountLogList(FoodCompanyAccountLog log) {
        FoodCompanyAccountLogListVO vo = new FoodCompanyAccountLogListVO();
        vo.setId(log.getId());
        vo.setAccountId(log.getAccountId());
        vo.setCompanyId(log.getCompanyId());
        vo.setChangeType(log.getChangeType());
        vo.setChangeAmount(log.getChangeAmount());
        vo.setBeforeAvailableBalance(log.getBeforeAvailableBalance());
        vo.setAfterAvailableBalance(log.getAfterAvailableBalance());
        vo.setBeforeFrozenBalance(log.getBeforeFrozenBalance());
        vo.setAfterFrozenBalance(log.getAfterFrozenBalance());
        vo.setBizType(log.getBizType());
        vo.setBizId(log.getBizId());
        vo.setBizNo(log.getBizNo());
        vo.setOperateSource(log.getOperateSource());
        vo.setOperatorId(log.getOperatorId());
        vo.setOperatorName(log.getOperatorName());
        vo.setCreateDate(log.getCreateDate());
        vo.setUpdateDate(log.getUpdateDate());
        return vo;
    }

    private static BigDecimal nvl(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }

    private static String generateRechargeNo() {
        return RECHARGE_NO_PREFIX + LocalDateTime.now().format(NO_TIME_FMT) + IdUtil.fastSimpleUUID().substring(0, 8).toUpperCase();
    }
}
