package cn.ysk.cashier.mybatis.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapProxy;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.ysk.cashier.dto.points.OrderDeductionPointsDTO;
import cn.ysk.cashier.exception.BadRequestException;
import cn.ysk.cashier.mybatis.entity.TbMemberPoints;
import cn.ysk.cashier.mybatis.entity.TbMemberPointsLog;
import cn.ysk.cashier.mybatis.entity.TbPointsBasicSetting;
import cn.ysk.cashier.mybatis.mapper.TbMemberPointsLogMapper;
import cn.ysk.cashier.mybatis.mapper.TbMemberPointsMapper;
import cn.ysk.cashier.mybatis.mapper.TbOrderInfoMapper;
import cn.ysk.cashier.mybatis.mapper.TbPointsBasicSettingMapper;
import cn.ysk.cashier.mybatis.service.TbMemberPointsService;
import cn.ysk.cashier.mybatis.service.TbPointsBasicSettingService;
import cn.ysk.cashier.pojo.order.TbOrderInfo;
import cn.ysk.cashier.utils.PageUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.Map;

/**
 * 会员积分
 *
 * @author Tankaikai tankaikai@aliyun.com
 * @since 2.0 2024-10-25
 */
@Service
public class TbMemberPointsServiceImpl extends ServiceImpl<TbMemberPointsMapper, TbMemberPoints> implements TbMemberPointsService {

    @Resource
    private TbMemberPointsLogMapper tbMemberPointsLogMapper;
    @Resource
    private TbPointsBasicSettingMapper tbPointsBasicSettingMapper;
    @Resource
    private TbOrderInfoMapper tbOrderInfoMapper;
    @Resource
    private TbPointsBasicSettingService tbPointsBasicSettingService;

    private LambdaQueryWrapper<TbMemberPoints> getWrapper(Map<String, Object> params) {
        //MapProxy mapProxy = MapProxy.create(params);
        TbMemberPoints param = BeanUtil.toBean(params, TbMemberPoints.class);
        LambdaQueryWrapper<TbMemberPoints> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(TbMemberPoints::getShopId, param.getShopId());
        wrapper.like(StrUtil.isNotEmpty(param.getMemberName()), TbMemberPoints::getMemberName, param.getMemberName());
        wrapper.like(StrUtil.isNotEmpty(param.getMobile()), TbMemberPoints::getMobile, param.getMobile());

        wrapper.orderByDesc(TbMemberPoints::getId);
        return wrapper;
    }

    @Override
    public Map<String, Object> page(Map<String, Object> params) {
        MapProxy mapProxy = MapProxy.create(params);
        int pageNum = mapProxy.getInt("page", 1);
        int pageSize = mapProxy.getInt("size", 10);
        LambdaQueryWrapper<TbMemberPoints> wrapper = getWrapper(params);
        Page<TbMemberPoints> page = super.page(new Page<>(pageNum, pageSize), wrapper);
        return PageUtil.toPlusPage(page.getRecords(), Convert.toInt(page.getTotal()));
    }

    @Override
    public TbMemberPoints getMemberPoints(Long memberId) {
        return initMemberPoints(memberId);
    }

    @Override
    public TbMemberPoints initMemberPoints(Long memberId) {
        TbMemberPoints entity = super.getOne(Wrappers.<TbMemberPoints>lambdaQuery().eq(TbMemberPoints::getMemberId, memberId));
        if (entity == null) {
            throw new BadRequestException("会员信息不存在");
        }
        if (entity.getAccountPoints() == null) {
            entity.setAccountPoints(0);
        }
        super.updateById(entity);
        return entity;
    }

    @Override
    public OrderDeductionPointsDTO getMemberUsablePoints(Long memberId, BigDecimal orderAmount) {
        TbMemberPoints entity = initMemberPoints(memberId);
        Long shopId = entity.getShopId();
        Integer accountPoints = entity.getAccountPoints();
        OrderDeductionPointsDTO dto = new OrderDeductionPointsDTO();
        dto.setAccountPoints(accountPoints);
        dto.setUsable(false);
        dto.setMaxDeductionAmount(BigDecimal.ZERO);
        dto.setMinDeductionAmount(BigDecimal.ZERO);
        TbPointsBasicSetting basic = tbPointsBasicSettingMapper.selectOne(Wrappers.<TbPointsBasicSetting>lambdaQuery().eq(TbPointsBasicSetting::getShopId, shopId));
        if (basic == null) {
            dto.setUnusableReason("商家未启用积分抵扣功能");
            return dto;
        }
        if (basic.getEnableDeduction() == 0) {
            dto.setUnusableReason("商家未启用积分抵扣功能");
            return dto;
        }
        if ("vip".equals(basic.getDeductionGroup()) && ObjectUtil.defaultIfNull(entity.getVip(), 0) != 1) {
            dto.setUnusableReason("仅VIP用户可用");
            return dto;
        }
        dto.setMinPaymentAmount(basic.getMinPaymentAmount());
        dto.setOrderAmount(orderAmount);
        if (NumberUtil.isLess(orderAmount, basic.getMinPaymentAmount())) {
            dto.setUnusableReason(StrUtil.format("实付金额不足￥{}元，无法进行抵扣", basic.getMinPaymentAmount()));
            return dto;
        }
        dto.setEquivalentPoints(basic.getEquivalentPoints());
        // 最低抵扣积分 = 1元等值积分 = 1.0元
        dto.setMinDeductionPoints(basic.getEquivalentPoints());
        // 计算抵扣门槛=?元
        dto.setMinDeductionAmount(BigDecimal.ONE);
        if (accountPoints == 0 || accountPoints < dto.getMinDeductionPoints()) {
            dto.setUnusableReason("积分不足或小于最低使用门槛" + dto.getMinDeductionPoints());
            return dto;
        }
        // 下单抵扣积分比例 1元=?积分
        Integer equivalentPoints = basic.getEquivalentPoints();
        // 计算账户积分=?元
        BigDecimal accountYuan = NumberUtil.div(accountPoints, equivalentPoints);
        // 下单最高抵扣比例
        BigDecimal maxDeductionRatio = basic.getMaxDeductionRatio();
        // 计算订单最多可以抵扣多少元，向下取整
        BigDecimal orderYuan = NumberUtil.roundDown(NumberUtil.mul(orderAmount, NumberUtil.div(maxDeductionRatio, new BigDecimal("100"))), 0);
        // 积分余额足够
        if (NumberUtil.isGreaterOrEqual(accountYuan, orderYuan)) {
            dto.setMaxDeductionAmount(orderYuan);
        } else {
            dto.setMaxDeductionAmount(NumberUtil.roundDown(accountYuan, 0));
        }
        if (NumberUtil.isLess(dto.getMaxDeductionAmount(), BigDecimal.ONE)) {
            dto.setUnusableReason("积分不足1元，无法进行抵扣");
            return dto;
        }
        dto.setUsable(true);
        // 计算最多可抵扣的积分
        BigDecimal mul = NumberUtil.mul(dto.getMaxDeductionAmount(), equivalentPoints);
        BigDecimal round = NumberUtil.round(mul, 0, RoundingMode.CEILING);
        dto.setMaxUsablePoints(round.intValue());
        return dto;
    }

    @Override
    public int calcUsedPoints(Long memberId, BigDecimal orderAmount, BigDecimal deductionAmount) {
        OrderDeductionPointsDTO core = getMemberUsablePoints(memberId, orderAmount);
        if (!core.getUsable()) {
            throw new BadRequestException(core.getUnusableReason());
        }
        if (NumberUtil.isGreater(deductionAmount, core.getMaxDeductionAmount())) {
            throw new BadRequestException(StrUtil.format("抵扣金额不能超过最大抵扣金额{}元", core.getMaxDeductionAmount()));
        }
        if (NumberUtil.isGreater(deductionAmount, orderAmount)) {
            throw new BadRequestException(StrUtil.format("抵扣金额不能超过订单金额{}元", orderAmount));
        }
        // 计算可抵扣的积分
        BigDecimal mul = NumberUtil.mul(deductionAmount, core.getEquivalentPoints());
        BigDecimal round = NumberUtil.round(mul, 0, RoundingMode.CEILING);
        return round.intValue();
    }

    @Override
    public BigDecimal calcDeductionAmount(Long memberId, BigDecimal orderAmount, int points) {
        OrderDeductionPointsDTO core = getMemberUsablePoints(memberId, orderAmount);
        if (!core.getUsable()) {
            throw new BadRequestException(core.getUnusableReason());
        }
        if (points < core.getMinDeductionPoints()) {
            throw new BadRequestException(StrUtil.format("使用积分不能低于使用门槛(每次最少使用{}积分)", core.getMinDeductionPoints()));
        }
        if (points > core.getMaxUsablePoints()) {
            throw new BadRequestException(StrUtil.format("使用积分不能超过最大使用限制{}", core.getMaxUsablePoints()));
        }
        BigDecimal money = NumberUtil.mul(points, NumberUtil.div(BigDecimal.ONE, core.getEquivalentPoints()));
        BigDecimal maxDeductionAmount = NumberUtil.roundDown(money, 0);
        if (NumberUtil.isGreater(maxDeductionAmount, core.getMaxDeductionAmount())) {
            return core.getMaxDeductionAmount();
        }
        return maxDeductionAmount;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deductPoints(Long memberId, int points, String content, Long orderId) {
        TbMemberPoints entity = initMemberPoints(memberId);
        // 扣除账户积分
        entity.setAccountPoints(entity.getAccountPoints() - points);
        entity.setLastPointsChangeTime(new Date());
        entity.setLastFloatPoints(-points);

        // 记录积分变动记录
        TbMemberPointsLog log = new TbMemberPointsLog();
        log.setShopId(entity.getShopId());
        log.setMemberId(entity.getMemberId());
        log.setMemberName(entity.getMemberName());
        log.setAvatarUrl(entity.getAvatarUrl());
        log.setMobile(entity.getMobile());
        log.setContent(content);
        log.setFloatType("subtract");
        log.setFloatPoints(-points);
        log.setCreateTime(new Date());
        // 有关联订单的需要回置订单表的相关积分使用字段
        if (orderId != null) {
            TbOrderInfo orderInfo = tbOrderInfoMapper.selectById(orderId);
            if (orderInfo != null) {
                log.setOrderNo(orderInfo.getOrderNo());
                // TODO 是否需要回执“使用的积分数量(points_num)”和“积分抵扣金额(points_discount_amount)”,目前不清楚是创建订单的时候置入还是支付完成后回调时置入需要商议后决定
            }
        }
        super.updateById(entity);
        tbMemberPointsLogMapper.insert(log);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addPoints(Long memberId, int points, String content, Long orderId) {
        TbMemberPoints entity = initMemberPoints(memberId);
        // 增加账户积分
        entity.setAccountPoints(entity.getAccountPoints() + points);
        entity.setLastPointsChangeTime(new Date());
        entity.setLastFloatPoints(points);
        // 记录积分变动记录
        TbMemberPointsLog log = new TbMemberPointsLog();
        log.setShopId(entity.getShopId());
        log.setMemberId(entity.getMemberId());
        log.setMemberName(entity.getMemberName());
        log.setAvatarUrl(entity.getAvatarUrl());
        log.setMobile(entity.getMobile());
        log.setContent(content);
        log.setFloatType("add");
        log.setFloatPoints(points);
        log.setCreateTime(new Date());
        // 有关联订单的需要回置订单表的相关积分使用字段
        if (orderId != null) {
            TbOrderInfo orderInfo = tbOrderInfoMapper.selectById(orderId);
            if (orderInfo != null) {
                log.setOrderNo(orderInfo.getOrderNo());
            }
        }
        super.updateById(entity);
        tbMemberPointsLogMapper.insert(log);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void consumeAwardPoints(Long memberId, Long orderId) {
        TbOrderInfo orderInfo = tbOrderInfoMapper.selectById(orderId);
        if (orderInfo == null) {
            throw new BadRequestException("订单不存在");
        }
        BigDecimal payAmount = orderInfo.getPayAmount();
        if (NumberUtil.isLessOrEqual(payAmount, BigDecimal.ZERO)) {
            return;
        }
        TbPointsBasicSetting basicSetting = tbPointsBasicSettingService.getByShopId(Convert.toLong(orderInfo.getShopId()));
        if (basicSetting == null) {
            return;
        }
        Integer enableRewards = basicSetting.getEnableRewards();
        if (enableRewards == 0) {
            return;
        }
        String rewardsGroup = basicSetting.getRewardsGroup();
        TbMemberPoints entity;
        try {
            entity = initMemberPoints(memberId);
        } catch (Exception e) {
            return;
        }
        Integer vip = entity.getVip();
        if ("vip".equals(rewardsGroup) && ObjectUtil.defaultIfNull(vip, 0) != 1) {
            return;
        }
        BigDecimal consumeAmount = basicSetting.getConsumeAmount();
        if (consumeAmount == null) {
            return;
        }
        if (NumberUtil.isLessOrEqual(consumeAmount, BigDecimal.ZERO)) {
            return;
        }
        BigDecimal awardPoints = NumberUtil.roundDown(NumberUtil.div(payAmount, consumeAmount), 0);
        addPoints(memberId, awardPoints.intValue(), StrUtil.format("消费￥{}送{}积分", payAmount, awardPoints.intValue()), orderId);
    }

}