package com.cool.store.service.fees.impl;

import cn.hutool.core.collection.CollStreamUtil;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.constants.CommonConstants;
import com.cool.store.constants.DictConstants;
import com.cool.store.constants.RedisConstant;
import com.cool.store.context.PartnerUserHolder;
import com.cool.store.dao.FranchiseFeeDAO;
import com.cool.store.dao.ShopInfoDAO;
import com.cool.store.dao.ShopStageInfoDAO;
import com.cool.store.dao.dict.SysDictColumnDAO;
import com.cool.store.dao.fees.ShopAllocationInfoDAO;
import com.cool.store.dao.fees.WalletPayInfoDAO;
import com.cool.store.dao.wallet.WalletTradeDAO;
import com.cool.store.dto.wallet.AccountInfoDTO;
import com.cool.store.dto.wallet.AccountTransferDTO;
import com.cool.store.dto.wallet.BatchTransferDTO;
import com.cool.store.enums.wechat.WalletTypeEnum;
import com.cool.store.request.wallet.*;
import com.cool.store.dto.wallet.TransferDTO;
import com.cool.store.entity.FranchiseFeeDO;
import com.cool.store.entity.ShopInfoDO;
import com.cool.store.entity.ShopStageInfoDO;
import com.cool.store.entity.fees.ShopAllocationInfoDO;
import com.cool.store.entity.fees.WalletPayInfoDO;
import com.cool.store.entity.wallet.WalletTradeDO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.enums.fees.AllocationPayStatusEnum;
import com.cool.store.enums.fees.PayTypeEnum;
import com.cool.store.enums.fees.WalletFeeItemEnum;
import com.cool.store.enums.point.ShopSubStageEnum;
import com.cool.store.enums.point.ShopSubStageStatusEnum;
import com.cool.store.enums.wallet.WalletTradeModuleEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.request.fees.WalletCancelPayRequest;
import com.cool.store.request.fees.WalletPayRequest;
import com.cool.store.request.fees.WalletRepayRequest;
import com.cool.store.request.xgj.ReceiptRequest;
import com.cool.store.service.PushService;
import com.cool.store.service.fees.WalletPayInfoService;
import com.cool.store.service.wallet.WalletApiService;
import com.cool.store.utils.BeanUtil;
import com.cool.store.utils.GenerateNoUtil;
import com.cool.store.utils.RedisUtil;
import com.cool.store.vo.PartnerUserInfoVO;
import com.cool.store.vo.fees.WalletPayInfoVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 钱包加盟缴费服务实现类
 * </p>
 *
 * @author wangff
 * @since 2026/1/30
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class WalletPayInfoServiceImpl implements WalletPayInfoService {
    private final WalletPayInfoDAO walletPayInfoDAO;
    private final FranchiseFeeDAO franchiseFeeDAO;
    private final SysDictColumnDAO sysDictColumnDAO;
    private final ShopAllocationInfoDAO shopAllocationInfoDAO;
    private final ShopInfoDAO shopInfoDAO;
    private final WalletApiService walletApiService;
    private final SysDictColumnDAO dictColumnDAO;
    private final WalletTradeDAO walletTradeDAO;
    private final RedisUtil redisUtil;
    private final ShopStageInfoDAO shopStageInfoDAO;
    private final PushService pushService;

    @Override
    @Transactional
    public Boolean pay(WalletPayRequest request, String oldPayNo) {
        log.info("钱包缴费开始，参数:{}", JSONObject.toJSONString(request));
        if (StringUtils.isNotBlank(oldPayNo) && CollectionUtils.isNotEmpty(request.getExpenseTypes()) && request.getExpenseTypes().size() > 1) {
            // 重新支付不支持批量
            throw new ServiceException(ErrorCodeEnum.WALLET_RE_PAY_FAIL);
        }
        ShopInfoDO shopInfo = shopInfoDAO.getShopInfo(request.getShopId());
        if (Objects.isNull(shopInfo)) {
            throw new ServiceException(ErrorCodeEnum.SHOP_NOT_EXIST);
        }
        String lockKey = MessageFormat.format(RedisConstant.WALLET_ALLOCATION_PAY_LOCK_KEY, shopInfo.getId());
        Boolean lock = redisUtil.tryLock(lockKey, lockKey, 600, TimeUnit.SECONDS);
        if (!Boolean.TRUE.equals(lock)) {
            throw new ServiceException(ErrorCodeEnum.THE_USER_IS_PAYING);
        }
        try {
            // 使用网商钱包支付
            Integer walletType = 2;
            // 查询所有费用
            List<ShopAllocationInfoDO> list = shopAllocationInfoDAO.getByShopId(request.getShopId());
            Map<String, BigDecimal> allocationAmountMap = CollStreamUtil.toMap(list, ShopAllocationInfoDO::getExpenseType, ShopAllocationInfoDO::getAmount);
            Map<String, ShopAllocationInfoDO> allocationInfoMap = CollStreamUtil.toMap(list, ShopAllocationInfoDO::getExpenseType, v -> v);

            // 支付校验
            verifyPay(request, shopInfo, allocationAmountMap, walletType);

            // 批量发起转账
            PartnerUserInfoVO user = PartnerUserHolder.getUser();
            List<ShopAllocationInfoDO> updateAllocationInfoList = new ArrayList<>();
            List<WalletPayInfoDO> walletPayInfoList = new ArrayList<>();
            List<WalletPayInfoDO> walletRePayInfoList = new ArrayList<>();
            List<WalletTradeDO> walletTradeList = new ArrayList<>();
            // 构建批量请求对象
            List<BatchTransferRequest.TransDataRequest> transDataList = CollStreamUtil.toList(request.getExpenseTypes(), expenseType -> {
                ShopAllocationInfoDO shopAllocationInfoDO = allocationInfoMap.get(expenseType);
                BigDecimal payAmount = allocationAmountMap.get(expenseType);
                String payNo = StringUtils.isNotBlank(oldPayNo) ? oldPayNo : GenerateNoUtil.generateMillsNoWithRandom();
                WalletFeeItemEnum feeItem = WalletFeeItemEnum.getByExpenseType(expenseType);
                assert feeItem != null;
                return new BatchTransferRequest.TransDataRequest(payNo, feeItem.getFeeItemId(), shopAllocationInfoDO.getPayeeCode(), null, payAmount.toString(), feeItem.getDesc());
            });
            BatchTransferRequest transRequest = new BatchTransferRequest(walletType, shopInfo.getStoreId(), request.getPayPwd(),0, transDataList);
            BatchTransferDTO batchTransferDTO = walletApiService.batchTransfer(transRequest);
            if (CollectionUtils.isEmpty(batchTransferDTO.getTransArray())) {
                throw new ServiceException(ErrorCodeEnum.WALLET_TRANS_FAIL);
            }
            for (BatchTransferDTO.TransDataRequest transResult : batchTransferDTO.getTransArray()) {
                WalletFeeItemEnum feeItemEnum = WalletFeeItemEnum.getByFeeItemId(transResult.getFeeItemId());
                assert feeItemEnum != null;
                WalletPayInfoDO walletPayInfoDO = new WalletPayInfoDO(
                        WalletTradeModuleEnum.FRANCHISE_PAY.getModule(), request.getShopId(), transResult.getReqNo(), user.getUsername(), new BigDecimal(transResult.getAmount()), feeItemEnum.getExpenseType(), user.getPartnerId(), String.valueOf(transResult.getTradeId())
                );
                walletPayInfoDO.setBatchCode(batchTransferDTO.getBatchCode());
                // 平安这里只返回成功或失败，网商只返回处理中
                walletPayInfoDO.setPayStatus(batchTransferDTO.getTradeStatus());
                if (StringUtils.isNotBlank(oldPayNo)) {
                    walletRePayInfoList.add(walletPayInfoDO);
                } else {
                    walletPayInfoList.add(walletPayInfoDO);
                }
                // 分账信息
                ShopAllocationInfoDO shopAllocationInfoDO = allocationInfoMap.get(feeItemEnum.getExpenseType());
                shopAllocationInfoDO.setStatus(batchTransferDTO.getTradeStatus());
                updateAllocationInfoList.add(shopAllocationInfoDO);
                // 钱包交易单
                WalletTradeDO walletTradeDO = walletPayInfoDO.convertToTradeDO(WalletTradeModuleEnum.FRANCHISE_PAY.getModule());
                walletTradeList.add(walletTradeDO);
            }
            if (CollectionUtils.isNotEmpty(walletPayInfoList)) {
                walletPayInfoDAO.insertBatch(walletPayInfoList);
            }
            if (CollectionUtils.isNotEmpty(walletRePayInfoList)) {
                walletPayInfoDAO.updateByPayNo(walletRePayInfoList.get(0));
            }
            if (CollectionUtils.isNotEmpty(updateAllocationInfoList)) {
                shopAllocationInfoDAO.updateBatch(updateAllocationInfoList);
            }
            if (CollectionUtils.isNotEmpty(walletTradeList)) {
                walletTradeDAO.insertBatch(walletTradeList);
            }
            // 支付成功
            if (AllocationPayStatusEnum.PAID.getStatus().equals(batchTransferDTO.getTradeStatus())) {
                // 校验并流转流程
                verifyAndProcessFlow(shopInfo.getId());
                // 推送缴费单数据到新管家
                if (CollectionUtils.isNotEmpty(walletPayInfoList)) {
                    pushReceiptRequest(walletPayInfoList, shopInfo);
                }
                if (CollectionUtils.isNotEmpty(walletRePayInfoList)) {
                    pushReceiptRequest(walletRePayInfoList, shopInfo);
                }
            }
        } finally {
            redisUtil.unlock(lockKey);
        }
        return true;
    }

    @Override
    @Transactional
    public void queryAndUpdateBatchTransferStatus(String batchCode) {
        BatchTransferDTO batchTransferDTO = walletApiService.queryBatchTransfer(new BatchTransferQueryRequest(batchCode));
        // 根据批次号更新交易单
        walletTradeDAO.updatePayStatusByBatchCode(batchCode, batchTransferDTO.getTradeStatus());
        // 根据批次号更新业务单
        List<WalletPayInfoDO> payInfoList = walletPayInfoDAO.getByBatchCode(batchCode);
        if (CollectionUtils.isEmpty(payInfoList)) {
            log.error("查询并更新批量转账状态异常，业务单为空");
            return;
        }
        walletPayInfoDAO.updatePayStatusByBatchCode(batchCode, batchTransferDTO.getTradeStatus());
        // 更新分账信息
        Long shopId = payInfoList.get(0).getShopId();
        List<String> expenseTypes = CollStreamUtil.toList(payInfoList, WalletPayInfoDO::getExpenseTypes);
        shopAllocationInfoDAO.updateStatusByShopId(shopId, expenseTypes, batchTransferDTO.getTradeStatus());
        // 校验并流转流程
        if (AllocationPayStatusEnum.PAID.getStatus().equals(batchTransferDTO.getTradeStatus())) {
            ShopInfoDO shopInfo = shopInfoDAO.getShopInfo(shopId);
            // 校验并流转流程
            verifyAndProcessFlow(shopInfo.getId());
            // 推送缴费单数据到新管家
            if (CollectionUtils.isNotEmpty(payInfoList)) {
                pushReceiptRequest(payInfoList, shopInfo);
            }
        }
    }

    /**
     * 推送账单到新管家
     */
    private void pushReceiptRequest(List<WalletPayInfoDO> payInfoList, ShopInfoDO shopInfoDO) {
        if (CollectionUtils.isEmpty(payInfoList)) {
            return;
        }
        FranchiseFeeDO franchiseFeeDO = franchiseFeeDAO.selectByShopId(shopInfoDO.getId());
        for (WalletPayInfoDO payInfo : payInfoList) {
            ReceiptRequest receiptRequest = new ReceiptRequest(shopInfoDO.getId(), franchiseFeeDO.getId().intValue(), payInfo, Integer.valueOf(shopInfoDO.getFranchiseBrand()));
            try {
                pushService.pushReceiptToXGJ(receiptRequest);
            } catch (Exception e) {
                log.error("钱包账单推送新管家失败, walletPayInfoId:{}", payInfo.getId(), e);
            }
        }
    }

    /**
     * 校验并流转阶段
     */
    private void verifyAndProcessFlow(Long shopId) {
        ShopStageInfoDO shopStage = shopStageInfoDAO.getByShopIdAndSubStage(shopId, ShopSubStageEnum.SHOP_STAGE_7.getShopSubStage());
        if (Objects.nonNull(shopStage) && ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_71.getShopSubStageStatus().equals(shopStage.getShopSubStageStatus())) {
            List<ShopAllocationInfoDO> allocationList = shopAllocationInfoDAO.getByShopId(shopId);
            boolean allPaid = allocationList.stream().allMatch(v -> AllocationPayStatusEnum.PAID.getStatus().equals(v.getStatus()));
            // 已全部支付流程改为对账中
            if (allPaid) {
                shopStageInfoDAO.updateShopStageInfo(shopId, ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_72);
            }
        }
    }
    
    /**
     * 支付校验
     */
    private void verifyPay(WalletPayRequest request, ShopInfoDO shopInfo, Map<String, BigDecimal> allocationAmountMap, Integer walletType) {
        // 校验费用类型
        for (String expenseType : request.getExpenseTypes()) {
            if (!DictConstants.PING_AN_DEFAULT_ALLOCATION_EXPENSE_TYPE_CODES.contains(expenseType)) {
                throw new ServiceException(ErrorCodeEnum.NONSUPPORT_EXPENSE_TYPE);
            }
        }
        // 校验付款方式
        FranchiseFeeDO franchiseFeeDO = franchiseFeeDAO.selectByShopId(request.getShopId());
        if (Objects.isNull(franchiseFeeDO) || !PayTypeEnum.PING_AN.getType().equals(franchiseFeeDO.getPayType())) {
            throw new ServiceException(ErrorCodeEnum.PAY_TYPE_UNEQUAL_TO_PING_AN_WALLET);
        }
        Map<String, String> nameMap = sysDictColumnDAO.getNameMapByCodes(DictConstants.PING_AN_DEFAULT_ALLOCATION_EXPENSE_TYPE_CODES);
        // 校验费用类型是否已缴纳
        List<WalletPayInfoDO> paidList = walletPayInfoDAO.getPaidByShopId(request.getShopId(), WalletTradeModuleEnum.FRANCHISE_PAY.getModule());
        String paidExpenseTypes = paidList.stream()
                .flatMap(v -> Arrays.stream(v.getExpenseTypes().split(",")))
                .filter(v -> request.getExpenseTypes().contains(v))
                .map(nameMap::get)
                .collect(Collectors.joining(CommonConstants.COMMA));
        if (StringUtils.isNotBlank(paidExpenseTypes)) {
            throw new ServiceException(ErrorCodeEnum.EXPENSE_TYPE_HAS_PAID, paidExpenseTypes);
        }

        // 校验金额是否一致
        BigDecimal totalAmount = BigDecimal.valueOf(0);
        for (String expenseType : request.getExpenseTypes()) {
            BigDecimal amount = allocationAmountMap.get(expenseType);
            if (Objects.isNull(amount)) {
                throw new ServiceException(ErrorCodeEnum.THERE_IS_NO_PAYMENT_INFORMATION);
            }
            totalAmount = totalAmount.add(amount);
        }
        if (totalAmount.compareTo(request.getAmount()) != 0) {
            throw new ServiceException(ErrorCodeEnum.EXPENSE_CHANGED);
        }
        // 校验余额
        List<AccountInfoDTO> accountInfo = walletApiService.getAccountInfo(new OutStoreIdRequest(shopInfo.getStoreId()));
        AccountInfoDTO accountInfoDTO = accountInfo.stream().filter(v -> Objects.nonNull(v.getWalletType()) && v.getWalletType().equals(walletType)).findFirst().orElse(null);
        if (Objects.isNull(accountInfoDTO)) {
            throw new ServiceException(ErrorCodeEnum.NOT_EXIST_PING_AN_ACCOUNT);
        }
        if (new BigDecimal(accountInfoDTO.getTotalAmount()).compareTo(totalAmount) < 0) {
            throw new ServiceException(ErrorCodeEnum.NOT_ENOUGH_BALANCE);
        }
    }

    @Override
    @Transactional
    public Boolean rePay(WalletRepayRequest request) {
        WalletPayInfoDO payInfoDO = walletPayInfoDAO.getById(request.getPayInfoId());
        if (Objects.isNull(payInfoDO)) {
            throw new ServiceException(ErrorCodeEnum.THERE_IS_NO_PAYMENT_INFORMATION);
        }
        WalletPayRequest payRequest = new WalletPayRequest();
        payRequest.setShopId(payInfoDO.getShopId());
        payRequest.setExpenseTypes(Collections.singletonList(payInfoDO.getExpenseTypes()));
        payRequest.setAmount(payInfoDO.getPayAmount());
        payRequest.setPayPwd(request.getPayPwd());
        return pay(payRequest, payInfoDO.getPayNo());
    }

    @Override
    public Boolean cancelPay(WalletCancelPayRequest request) {
        WalletPayInfoDO payInfoDO = walletPayInfoDAO.getById(request.getPayInfoId());
        if (Objects.isNull(payInfoDO)) {
            throw new ServiceException(ErrorCodeEnum.THERE_IS_NO_PAYMENT_INFORMATION);
        }
        if (!AllocationPayStatusEnum.FAIL.getStatus().equals(payInfoDO.getPayStatus())) {
            throw new ServiceException(ErrorCodeEnum.WALLET_PAY_CANNOT_CANCEL);
        }
        WalletPayInfoDO updateInfo = new WalletPayInfoDO();
        updateInfo.setId(request.getPayInfoId());
        updateInfo.setPayStatus(AllocationPayStatusEnum.CANCEL.getStatus());
        walletPayInfoDAO.updateByPrimaryKeySelective(updateInfo);
        return true;
    }

    @Override
    public List<WalletPayInfoVO> payInfoList(Long shopId, Integer payStatus) {
        List<WalletPayInfoDO> list = walletPayInfoDAO.getByShopId(shopId, WalletTradeModuleEnum.FRANCHISE_PAY.getModule(), payStatus);
        List<String> expenseTypes = list.stream()
                .flatMap(v -> Arrays.stream(v.getExpenseTypes().split(",")))
                .distinct()
                .collect(Collectors.toList());
        Map<String, String> nameMap = dictColumnDAO.getNameMapByCodes(expenseTypes);
        List<WalletPayInfoVO> result = BeanUtil.toList(list, WalletPayInfoVO.class);
        result.forEach(v -> {
            String expenseTypeName = Arrays.stream(v.getExpenseTypes().split(",")).map(nameMap::get).collect(Collectors.joining(CommonConstants.COMMA));
            v.setExpenseTypeName(expenseTypeName);
        });
        return result;
    }

    @Override
    public void rePushReceipt(Long walletPayInfoId) {
        WalletPayInfoDO payInfoDO = walletPayInfoDAO.getById(walletPayInfoId);
        ShopInfoDO shopInfo = shopInfoDAO.getShopInfo(payInfoDO.getShopId());
        pushReceiptRequest(Collections.singletonList(payInfoDO), shopInfo);
    }

    /**
     * 调用营帐通转账接口进行
     * @param expenseType 费用类型
     * @param amount 转账金额
     * @param payPwd 支付密码
     * @param storeId 门店id
     * @param payNo 付款单号
     * @param companyCode 公司code
     * @return 转账结果DTO
     */
    public TransferDTO pay(String expenseType, BigDecimal amount, String payPwd, String storeId, String payNo, String companyCode) {
        WalletFeeItemEnum feeItem = WalletFeeItemEnum.getByExpenseType(expenseType);
        if (Objects.isNull(feeItem)) {
            throw new ServiceException(ErrorCodeEnum.NONSUPPORT_EXPENSE_TYPE);
        }
        TransferRequest request = new TransferRequest();
        request.setOutStoreId(storeId);
        request.setPayPwd(payPwd);
        request.setReqNo(payNo);
        request.setFeeItemId(feeItem.getFeeItemId());
        request.setCompanyCode(companyCode);
        request.setAmount(amount.toString());
        request.setRemark(feeItem.getDesc());
        return walletApiService.transfer(request);
    }

    @Override
    public AccountTransferDTO accountPay(String expenseType, BigDecimal amount, String inAccNo, String outAccNo,String payNo, String remark) {
        WalletFeeItemEnum feeItem = WalletFeeItemEnum.getByExpenseType(expenseType);
        if (Objects.isNull(feeItem)) {
            throw new ServiceException(ErrorCodeEnum.NONSUPPORT_EXPENSE_TYPE);
        }
        AccountTransferRequest request = new AccountTransferRequest();
        request.setReqNo(payNo);
        request.setInAccNo(inAccNo);
        request.setOutAccNo(outAccNo);
        request.setRemark(remark);
        request.setAmount(amount.toPlainString());
        request.setFeeItemId(feeItem.getFeeItemId());
        request.setWalletType(WalletTypeEnum.ONLINE_BANK.getType());
        return walletApiService.accountTransfer(request);
    }
}
