package com.bonus.canteen.core.account.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bonus.canteen.core.account.constants.AccStatusEnum;
import com.bonus.canteen.core.account.constants.AccTradeTypeEnum;
import com.bonus.canteen.core.account.constants.AccWalletTypeEnum;
import com.bonus.canteen.core.account.domain.*;
import com.bonus.canteen.core.account.domain.param.*;
import com.bonus.canteen.core.account.domain.vo.AccInfoDetailsVO;
import com.bonus.canteen.core.account.domain.vo.AccInfoInvalidSumVO;
import com.bonus.canteen.core.account.domain.vo.AccOperationListVO;
import com.bonus.canteen.core.account.domain.vo.AccWalletInfoVO;
import com.bonus.canteen.core.account.mapper.AccInfoMapper;
import com.bonus.canteen.core.account.service.AccOperationHistoryService;
import com.bonus.canteen.core.account.service.IAccInfoService;
import com.bonus.canteen.core.account.service.IAccTradeService;
import com.bonus.canteen.core.account.service.IAccWalletInfoService;
import com.bonus.canteen.core.common.utils.MqUtil;
import com.bonus.canteen.core.pay.constants.PayChannelEnum;
import com.bonus.canteen.core.pay.constants.PayTypeEnum;
import com.bonus.canteen.core.user.domain.DeviceMqPersonalUpdateMessageDTO;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.core.utils.DateUtils;
import com.bonus.common.houqin.constant.DelFlagEnum;
import com.bonus.common.houqin.constant.LeCodeUseSceneEnum;
import com.bonus.common.houqin.constant.SourceTypeEnum;
import com.bonus.common.houqin.mq.constant.LeMqConstant;
import com.bonus.common.houqin.utils.JacksonUtil;
import com.bonus.common.houqin.utils.SM4EncryptUtils;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.system.api.domain.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 账户资料Service业务层处理
 * 
 * @author xsheng
 * @date 2025-04-05
 */
@Service
public class AccInfoServiceImpl implements IAccInfoService {
    private static final Logger log = LoggerFactory.getLogger(AccInfoServiceImpl.class);

    @Autowired
    private IAccWalletInfoService accWalletInfoService;
    @Autowired
    private AccInfoMapper accInfoMapper;
    @Autowired
    private AccOperationHistoryService accOperationHistoryService;
    @Autowired
    @Lazy
    IAccTradeService accTradeService;

    @Resource(
            name = "smartCanteenTaskExecutor"
    )
    @Lazy
    private AsyncTaskExecutor asyncTaskExecutor;

    /**
     * 查询账户资料
     * 
     * @param id 账户资料主键
     * @return 账户资料
     */
    @Override
    public AccountInfo selectAccInfoById(Long id) {
        return accInfoMapper.selectAccInfoById(id);
    }

    /**
     * 查询账户资料列表
     *
     */
    @Override
    public List<AccInfoDetailsVO> selectAccInfoList(AccountInfoQueryParam accountInfoQueryParam) {
        String encryptedSearchValue = SM4EncryptUtils.sm4Encrypt(accountInfoQueryParam.getSearchValue());
        if(CollUtil.isEmpty(accountInfoQueryParam.getAccountStatusList())) {
            accountInfoQueryParam.setAccountStatusList(AccStatusEnum.getAllKeys());
        }
        List<AccInfoDetailsVO> list = accInfoMapper.queryAccInfoDetails(accountInfoQueryParam, encryptedSearchValue);
        handleResult(list);
        return list;
    }

//    @Override
//    public List<AccInfoVo> selectAccInfoVoList(AccInfo accInfo) {
//        return accInfoMapper.selectAccInfoVoList(accInfo);
//    }

    /**
     * 新增账户资料
     * 
     * @param accountInfo 账户资料
     * @return 结果
     */
    @Override
    public int insertAccInfo(AccountInfo accountInfo) {
        accountInfo.setCreateTime(DateUtils.getNowDate());
        try {
            return accInfoMapper.insertAccInfo(accountInfo);
        } catch (Exception e) {
            throw new ServiceException("错误信息描述");
        }
    }

    /**
     * 修改账户资料
     *
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAccInfo(AccountBalanceEditParam accountBalanceEditParam) {
//        LocalDate accEndDate = accountBalanceEditParam.getEndDate();
//        Map<AccWalletIdEnum, BigDecimal> walletLimits = new HashMap<>();
//        walletLimits.put(AccWalletIdEnum.WALLET, accountBalanceEditParam.getMinWalletBalLimit());
//        walletLimits.put(AccWalletIdEnum.SUBSIDY, accountBalanceEditParam.getMinSubBalLimit());
//        for (Map.Entry<AccWalletIdEnum, BigDecimal> entry : walletLimits.entrySet()) {
//            accWalletInfoService.updateMinBalance(ListUtil.toList(accountBalanceEditParam.getUserId()), entry.getKey().getKey(), entry.getValue());
//        }
//        updateAccountEndDate(ListUtil.toList(accountBalanceEditParam.getUserId()), accEndDate);
    }

    protected void updateAccountEndDate(List<Long> userIds, LocalDate accEndDate) {
//        if (ObjectUtil.isEmpty(userIds)) {
//            log.info("更新账户有效期-账户信息为空");
//            return;
//        }
//        try {
//            accInfoMapper.updateAccInfoEndDateByUserId(accEndDate, userIds, SecurityUtils.getUsername());
//        } catch (Exception e) {
//            log.error("[更新账户有效期失败", e);
//            throw new ServiceException("更新账户有效期失败");
//        }
    }


    /**
     * 批量删除账户资料
     * 
     * @param ids 需要删除的账户资料主键
     * @return 结果
     */
    @Override
    public int deleteAccInfoByIds(Long[] ids) {
        return accInfoMapper.deleteAccInfoByIds(ids);
    }

    /**
     * 删除账户资料信息
     * 
     * @param users 账户资料主键
     * @return 结果
     */
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public int deleteAccInfoByUserIds(List<SysUser> users) {
        int count = 0;
        for (int i = 0; i < users.size(); i++) {
            Long userId = users.get(i).getUserId();
            accWalletInfoService.deleteAccWalletInfoByUserId(userId);
            //MqUtil.sendDataChange(userId, LeMqConstant.DataChangeType.REMOVE, LeMqConstant.Topic.DATA_CHANGE_CUSTOMER);
            DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(userId),"del");
            MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
            count += accInfoMapper.deleteAccInfoByUserId(userId);
        }
        return count;
    }

    @Transactional(
            rollbackFor = {Exception.class}
    )
    public int syncAccInfo(SysUser sysUser) {
        AccountInfo accountInfo = new AccountInfo();
        accountInfo.setAccountStatus(AccStatusEnum.NORMAL.getKey());
        accountInfo.setUserId(sysUser.getUserId());
        accInfoMapper.insertAccInfo(accountInfo);
        List<AccWalletInfo> accWalletInfos = new ArrayList();
        AccWalletTypeEnum[] accWalletTypeEnums = AccWalletTypeEnum.values();

        for(int i = 0; i < accWalletTypeEnums.length; ++i) {
            AccWalletTypeEnum accWalletTypeEnum = accWalletTypeEnums[i];
            AccWalletInfo accWalletInfo = new AccWalletInfo();
            accWalletInfo.setAccountId(accountInfo.getAccountId());
            accWalletInfo.setUserId(sysUser.getUserId());
            accWalletInfo.setWalletType(accWalletTypeEnum.getKey());
            accWalletInfos.add(accWalletInfo);
        }

        //MqUtil.sendDataChange(sysUser.getUserId(), LeMqConstant.DataChangeType.ADD, LeMqConstant.Topic.DATA_CHANGE_CUSTOMER);
        DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(sysUser.getUserId()),"update");
        MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
        return this.accWalletInfoService.batchInsertAccWalletInfo(accWalletInfos);
    }

    @Override
    public String getOrderQRCode() {
        String stime = String.valueOf(System.currentTimeMillis());
        String userId = SecurityUtils.getUserId().toString();
        return "bns{\"s\":" + SourceTypeEnum.WECHAT_SMALL_PRO.getKey() + ",\"y\":" + LeCodeUseSceneEnum.PAY.key() + ",\"p\":\"" + userId + "\",\"t\":" + "\"" + stime + "\"}";
    }

    public AccInfoInvalidSumVO queryAccInfoInvalidSum() {
//        LocalDateTime now = LocalDateTime.now();
//        LocalDateTime startOfDay = now.toLocalDate().atTime(LocalTime.MIN);
//
//        CompletableFuture<Integer> cancelCountFuture = CompletableFuture.supplyAsync(
//                () -> accInfoMapper.queryAccInfoInvalidSum(startOfDay, now, AccountStatusEnum.CANCEL.getKey()),
//                this.asyncTaskExecutor
//        ).exceptionally(ex -> {
//            log.error("Failed to get cancel count", ex);
//            return 0;
//        });
//
//        CompletableFuture<Integer> expiredCountFuture = CompletableFuture.supplyAsync(
//                () -> accInfoMapper.queryAccInfoInvalidSum(startOfDay, now, AccountStatusEnum.OVERDUE.getKey()),
//                this.asyncTaskExecutor
//        ).exceptionally(ex -> {
//            log.error("Failed to get expired count", ex);
//            return 0;
//        });
//
//        return cancelCountFuture.thenCombine(expiredCountFuture, (cancel, expired) -> {
//            AccInfoInvalidSumVO accInfoInvalidSumVO = new AccInfoInvalidSumVO();
//            accInfoInvalidSumVO.setCancelCount(cancel);
//            accInfoInvalidSumVO.setExpiredCount(expired);
//            return accInfoInvalidSumVO;
//            }
//        ).join();
        return null;
    }
    private void handleResult(List<AccInfoDetailsVO> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        list = list.stream().peek(item -> {
            String decryptedPhoneNumber = SM4EncryptUtils.sm4Decrypt(item.getPhoneNumber());
            item.setPhoneNumber(decryptedPhoneNumber);
        }).collect(Collectors.toList());
        List<Long> custIdList = list.stream()
                .map(AccInfoDetailsVO::getUserId)
                .collect(Collectors.toList());
        Map<Long, List<AccWalletInfoVO>> walletMap = accWalletInfoService.selectAccWalletInfoByUserIds(custIdList);

        list.forEach(item -> {
            List<AccWalletInfoVO> walletInfoList = walletMap.getOrDefault(item.getUserId(), ListUtil.empty());
            setWalletBalances(item, walletInfoList);
            setTotalBalances(item, walletInfoList);
        });
    }

    private void setWalletBalances(AccInfoDetailsVO item, List<AccWalletInfoVO> walletInfoList) {
        for (AccWalletInfoVO wallet : walletInfoList) {
            switch (AccWalletTypeEnum.getEnum(wallet.getWalletType())) {
                case WALLET:
                    item.setWalletBal(wallet.getWalletBal());
                    item.setFrozenWallet(wallet.getFrozenBalance());
                    item.setMinWalletBalLimit(wallet.getLimitBalance());
                    break;
                case SUBSIDY:
                    item.setSubsidyBal(wallet.getWalletBal());
                    item.setFrozenSub(wallet.getFrozenBalance());
                    item.setMinSubBalLimit(wallet.getLimitBalance());
                    break;
                default:
                    break;
            }
        }
    }
    private void setTotalBalances(AccInfoDetailsVO item, List<AccWalletInfoVO> walletInfoList) {
        BigDecimal frozenBalanceAll = walletInfoList.stream()
                .map(AccWalletInfoVO::getFrozenBalance)
                .filter(ObjectUtil::isNotNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal accAvailableBal = walletInfoList.stream()
                .map(AccWalletInfoVO::getWalletBal)
                .filter(ObjectUtil::isNotNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        item.setAccBalTotal(NumberUtil.add(accAvailableBal, frozenBalanceAll));
        item.setAccAvailableBal(accAvailableBal);
        item.setAccFreezeBalTotal(frozenBalanceAll);
    }

    public AccInfoDetailsVO queryAccInfoBalanceSum(AccountInfoQueryParam accountInfoQueryParam) {
        String encryptedSearchValue = SM4EncryptUtils.sm4Encrypt(accountInfoQueryParam.getSearchValue());
        if(CollUtil.isEmpty(accountInfoQueryParam.getAccountStatusList())) {
            accountInfoQueryParam.setAccountStatusList(AccStatusEnum.getAllKeys());
        }
        return accInfoMapper.queryAccInfoBalanceSum(accountInfoQueryParam, encryptedSearchValue);
    }

    @Transactional(rollbackFor = {Exception.class})
    public void editAccountStatus(AccountEnableDisableParam accountEnableDisableParam) {
        int flag = accInfoMapper.update((((Wrappers.lambdaUpdate(AccountInfo.class)
                .set(AccountInfo::getAccountStatus, accountEnableDisableParam.getAccountStatus()))
                .set(AccountInfo::getUpdateBy, SecurityUtils.getUserId()))
                .set(AccountInfo::getUpdateTime, LocalDateTime.now()))
                .eq(AccountInfo::getUserId, accountEnableDisableParam.getUserId()));
        if (flag > 0) {
            this.accOperationHistoryService.addAccOperationHistory(accountEnableDisableParam.getUserId(), accountEnableDisableParam.getAccountStatus().intValue());
            try {
                DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(accountEnableDisableParam.getUserId()),"update");
                String jsonString = JacksonUtil.writeValueAsString(bean);
                log.info("账户变动发送mq内容：{}", jsonString);
                MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
            } catch (Exception e) {
                log.error("发送MQ消息失败", e);
            }
        }
    }


    public WalletBalanceVO queryWalletBalance(AccountInfo accountInfo) {
        log.info("查询账户余额入参={}", JSONUtil.toJsonStr(accountInfo));
        AccInfoVo accInfoVO = accInfoMapper.selectAccInfoVo(accountInfo);
        if (ObjectUtil.isNull(accInfoVO)) {
            throw new ServiceException("账户不存在");
        } else {
            AccWalletInfo accWalletInfo = new AccWalletInfo();
            accWalletInfo.setAccountId(accInfoVO.getAccountId());
            List<AccWalletInfo> walletInfoList = this.accWalletInfoService.selectAccWalletInfoList(accWalletInfo);
            this.setAccInfoVODetailList(accInfoVO, walletInfoList);
            log.info("获取账户(钱包)信息,出参={}", JSONUtil.toJsonStr(accInfoVO));
        }
        WalletBalanceVO walletBalanceVO = WalletBalanceVO.of(accInfoVO);
        log.info("查询账户余额入参={}", JSONUtil.toJsonStr(walletBalanceVO));
        return walletBalanceVO;
    }

    @Override
    public List<AccInfoDetailsVO> queryAccInfoByUserIds(List<Long> userIds) {
        return accInfoMapper.queryAccInfoByUserIds(userIds);
    }
    @Override
    public AccInfoDetailsVO queryAccInfoByUserId(Long userId) {
        return accInfoMapper.queryAccInfoByUserId(userId);
    }
    @Override
    public void checkAccStatus(AccInfoDetailsVO accInfoVO) {
        if (ObjectUtil.isNull(accInfoVO) || DelFlagEnum.DEL_TRUE.key().equals(accInfoVO.getDelFlag())) {
            throw new ServiceException("账户不存在");
        }

        AccStatusEnum statusEnum = AccStatusEnum.getEnum(accInfoVO.getAccountStatus());
        if (ObjectUtil.isNull(statusEnum)) {
            throw new ServiceException("账户状态异常");
        }

        if (statusEnum == AccStatusEnum.DEACTIVATE) {
            throw new ServiceException("账户已停用");
        }
    }


    protected void setAccInfoVODetailList(AccInfoVo accInfoVo, List<AccWalletInfo> walletInfoList) {
        if (ObjectUtil.isNotEmpty(walletInfoList)) {
            accInfoVo.setWalletBal(walletInfoList.stream().filter(o -> o.getWalletType().intValue() == AccWalletTypeEnum.WALLET.getKey())
                    .map(AccWalletInfo::getWalletBal).reduce(BigDecimal.ZERO, BigDecimal::add));
            accInfoVo.setSubsidyBal(walletInfoList.stream().filter(o -> o.getWalletType().intValue() == AccWalletTypeEnum.SUBSIDY.getKey())
                    .map(AccWalletInfo::getWalletBal).reduce(BigDecimal.ZERO, BigDecimal::add));
            accInfoVo.setAccountAllBal(accInfoVo.getWalletBal().add(accInfoVo.getSubsidyBal()));
        }
    }
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public void individualAccRecharge(AccRechargeParam param) {
        AccInfoDetailsVO accInfoVO = queryAccInfoByUserId(param.getUserId());
        if(Objects.isNull(accInfoVO)) {
            throw new ServiceException("账户不存在");
        }
        checkAccStatus(accInfoVO);
        WalletUpdateDTO walletUpdateDTO = new WalletUpdateDTO();
        walletUpdateDTO.setAmount(param.getAmount());
        walletUpdateDTO.setUserId(param.getUserId());
        walletUpdateDTO.setAccWalletTypeEnum(AccWalletTypeEnum.WALLET);
        walletUpdateDTO.setAccTradeTypeEnum(AccTradeTypeEnum.RECHARGE);
        walletUpdateDTO.setPayChannelEnum(PayChannelEnum.GC_SYSTEM);
        walletUpdateDTO.setPayTypeEnum(PayTypeEnum.CASH);
        accWalletInfoService.addAccWalletBalance(walletUpdateDTO);
        try {
            DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(param.getUserId()),"update");
            String jsonString = JacksonUtil.writeValueAsString(bean);
            log.info("账户变动发送mq内容：{}", jsonString);
            MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
        } catch (Exception e) {
            log.error("发送MQ消息失败", e);
        }
    }
    public void individualAccWithdraw(AccWithdrawParam param) {
        AccInfoDetailsVO accInfoVO = queryAccInfoByUserId(param.getUserId());
        if(Objects.isNull(accInfoVO)) {
            throw new ServiceException("账户不存在");
        }
        checkAccStatus(accInfoVO);
        WalletUpdateDTO walletUpdateDTO = new WalletUpdateDTO();
        walletUpdateDTO.setAmount(param.getAmount());
        walletUpdateDTO.setUserId(param.getUserId());
        walletUpdateDTO.setAccWalletTypeEnum(AccWalletTypeEnum.WALLET);
        walletUpdateDTO.setAccTradeTypeEnum(AccTradeTypeEnum.WITHDRAW);
        walletUpdateDTO.setPayChannelEnum(PayChannelEnum.GC_SYSTEM);
        walletUpdateDTO.setPayTypeEnum(PayTypeEnum.MEAL_CARD);
        accWalletInfoService.clearAccWalletBalance(walletUpdateDTO);
        try {
            DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(param.getUserId()),"update");
            String jsonString = JacksonUtil.writeValueAsString(bean);
            log.info("账户变动发送mq内容：{}", jsonString);
            MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
        } catch (Exception e) {
            log.error("发送MQ消息失败", e);
        }
    }
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public void individualAccLeaveClear(AccLeaveClearParam param) {
        AccInfoDetailsVO accInfoVO = queryAccInfoByUserId(param.getUserId());
        if(Objects.isNull(accInfoVO)) {
            throw new ServiceException("账户不存在");
        }
        checkAccStatus(accInfoVO);
        List<AccWalletInfo> walletInfoList = accWalletInfoService.selectAccWalletInfoByUserId(accInfoVO.getUserId());
        if(CollUtil.isEmpty(walletInfoList)) {
            throw new ServiceException("补贴钱包不存在");
        }
        for(AccWalletInfo walletInfo : walletInfoList){
            WalletUpdateDTO walletUpdateDTO = new WalletUpdateDTO();
            walletUpdateDTO.setAmount(walletInfo.getWalletBal());
            walletUpdateDTO.setUserId(param.getUserId());
            walletUpdateDTO.setAccWalletTypeEnum(AccWalletTypeEnum.getEnum(walletInfo.getWalletType()));
            walletUpdateDTO.setAccTradeTypeEnum(AccTradeTypeEnum.CLEAR);
            walletUpdateDTO.setPayChannelEnum(PayChannelEnum.GC_SYSTEM);
            walletUpdateDTO.setPayTypeEnum(PayTypeEnum.MEAL_CARD);
            accWalletInfoService.clearAccWalletBalance(walletUpdateDTO);
            try {
                DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(param.getUserId()),"update");
                String jsonString = JacksonUtil.writeValueAsString(bean);
                log.info("账户变动发送mq内容：{}", jsonString);
                MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
            } catch (Exception e) {
                log.error("发送MQ消息失败", e);
            }
        }
    }
    @Override
    public List<AccOperationListVO> queryAccPersonalWalOperationAddList(AccWalletOperationQueryParam param) {
        List<AccOperationListVO> operationListVOList = accTradeService.queryAccOperationList(param);
        if(CollUtil.isNotEmpty(operationListVOList)) {
            for(AccOperationListVO vo: operationListVOList) {
                vo.setPhoneNumber(SM4EncryptUtils.sm4Decrypt(vo.getPhoneNumber()));
            }
        }
        return operationListVOList ;
    }
}
