package com.zsElectric.boot.business.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zsElectric.boot.business.model.form.applet.AppGainCouponForm;
import com.zsElectric.boot.business.model.query.applet.AppCouponQuery;
import com.zsElectric.boot.business.model.vo.applet.AppCouponStatusNumVO;
import com.zsElectric.boot.business.service.CouponTemplateService;
import com.zsElectric.boot.core.exception.BusinessException;
import com.zsElectric.boot.core.exception.CouponException;
import com.zsElectric.boot.business.model.entity.CouponTemplate;
import com.zsElectric.boot.security.util.SecurityUtils;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsElectric.boot.business.mapper.CouponMapper;
import com.zsElectric.boot.business.service.CouponService;
import com.zsElectric.boot.business.model.entity.Coupon;
import com.zsElectric.boot.business.model.form.CouponForm;
import com.zsElectric.boot.business.model.query.CouponQuery;
import com.zsElectric.boot.business.model.vo.CouponVO;
import com.zsElectric.boot.business.converter.CouponConverter;
import org.springframework.data.redis.core.RedisTemplate;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import org.springframework.transaction.annotation.Transactional;

/**
 * 优惠劵服务实现类
 *
 * @author zsElectric
 * @since 2025-12-19 09:58
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {

    private final CouponConverter couponConverter;

    private final CouponTemplateService couponTemplateService;

    /**
     * 获取优惠劵分页列表
     *
     * @param queryParams 查询参数
     * @return {@link IPage<CouponVO>} 优惠劵分页列表
     */
    @Override
    public IPage<CouponVO> getCouponPage(CouponQuery queryParams) {
        Page<CouponVO> pageVO = this.baseMapper.getCouponPage(
                new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
                queryParams
        );
        return pageVO;
    }

    /**
     * 获取优惠劵表单数据
     *
     * @param id 优惠劵ID
     * @return 优惠劵表单数据
     */
    @Override
    public CouponForm getCouponFormData(Long id) {
        Coupon entity = this.getById(id);
        return couponConverter.toForm(entity);
    }

    /**
     * 新增优惠劵
     *
     * @param formData 优惠劵表单对象
     * @return 是否新增成功
     */
    @Override
    public boolean saveCoupon(CouponForm formData) {
        Coupon entity = couponConverter.toEntity(formData);
        return this.save(entity);
    }

    /**
     * 更新优惠劵
     *
     * @param id       优惠劵ID
     * @param formData 优惠劵表单对象
     * @return 是否修改成功
     */
    @Override
    public boolean updateCoupon(Long id, CouponForm formData) {
        Coupon entity = couponConverter.toEntity(formData);
        return this.updateById(entity);
    }

    /**
     * 删除优惠劵
     *
     * @param ids 优惠劵ID，多个以英文逗号(,)分割
     * @return 是否删除成功
     */
    @Override
    public boolean deleteCoupons(String ids) {
        Assert.isTrue(StrUtil.isNotBlank(ids), "删除的优惠劵数据为空");
        // 逻辑删除
        List<Long> idList = Arrays.stream(ids.split(","))
                .map(Long::parseLong)
                .toList();
        return this.removeByIds(idList);
    }

    /**
     * 领取优惠券
     *
     * @param templateId 优惠券模板ID
     * @param userId     用户ID
     * @param takeType   领取类型（1-用户领取，2-后台发放）
     * @return 优惠券ID
     */
    @Transactional(rollbackFor = Exception.class)
    public Long takeCoupon(Long templateId, Long userId, Integer takeType) {
        // 获取优惠券模板
        CouponTemplate template = couponTemplateService.getById(templateId);
        if (template == null) {
            throw new CouponException("优惠券模板不存在");
        }

        // 检查模板状态（1-上线，2-下线）
        if (template.getStatus() != 1) {
            throw new CouponException("优惠券模板已下线");
        }

        // 检查发放数量限制
        if (template.getTotalCount() != -1) { // -1表示不限制
            if (template.getTotalCountAll() != null && template.getTotalCountAll() >= template.getTotalCount()) {
                throw new CouponException("优惠券已领完");
            }
        }

        // 创建优惠券
        Coupon coupon = new Coupon();
        coupon.setTemplateId(templateId);
        coupon.setName(template.getName());
        coupon.setUserId(userId);
        coupon.setTakeType(takeType);
        coupon.setTakeTime(LocalDateTime.now());
        coupon.setStatus(1); // 未使用

        // 设置过期时间
        if (template.getFailureTime() != null && template.getFailureTime() > 0) {
            LocalDateTime expireTime = LocalDateTime.now().plusDays(template.getFailureTime());
            coupon.setExpireTime(expireTime);
        }

        // 保存优惠券
        this.save(coupon);

        // 更新模板已发放数量
        template.setTotalCountAll(template.getTotalCountAll() != null ? template.getTotalCountAll() + 1 : 1);
        couponTemplateService.updateById(template);

        return coupon.getId();
    }

    /**
     * 计算优惠金额
     *
     * @param coupon      优惠券
     * @param orderAmount 订单金额
     * @return 优惠金额
     */
    public BigDecimal calculateDiscount(Coupon coupon, BigDecimal orderAmount) {
        // 检查优惠券状态
        if (!isValidCoupon(coupon.getId())) {
            return BigDecimal.ZERO;
        }

        // 获取优惠券模板
        CouponTemplate template = couponTemplateService.getById(coupon.getTemplateId());
        if (template == null) {
            return BigDecimal.ZERO;
        }

        // 根据优惠券类型计算优惠金额
        switch (template.getType()) {
            case 1: // 折扣券
                return calculateDiscountCoupon(template, orderAmount);
            case 2: // 满减券
                return calculateReductionCoupon(template, orderAmount);
            case 3: // 无门槛券
                return calculateNoThresholdCoupon(template);
            default:
                return BigDecimal.ZERO;
        }
    }

    /**
     * 计算折扣券优惠金额
     *
     * @param template    优惠券模板
     * @param orderAmount 订单金额
     * @return 优惠金额
     */
    private BigDecimal calculateDiscountCoupon(CouponTemplate template, BigDecimal orderAmount) {
        // 检查是否满足使用条件
        if (template.getUsePrice() != null && orderAmount.compareTo(template.getUsePrice()) < 0) {
            return BigDecimal.ZERO;
        }

        // 计算折扣金额 = 订单金额 × 折扣率
        if (template.getDiscountPrice() != null) {
            return orderAmount.multiply(template.getDiscountPrice()).divide(BigDecimal.valueOf(100));
        }

        return BigDecimal.ZERO;
    }

    /**
     * 计算满减券优惠金额
     *
     * @param template    优惠券模板
     * @param orderAmount 订单金额
     * @return 优惠金额
     */
    private BigDecimal calculateReductionCoupon(CouponTemplate template, BigDecimal orderAmount) {
        // 检查是否满足使用条件
        if (template.getUsePrice() != null && orderAmount.compareTo(template.getUsePrice()) < 0) {
            return BigDecimal.ZERO;
        }

        // 返回减免金额
        return template.getDiscountPrice() != null ? template.getDiscountPrice() : BigDecimal.ZERO;
    }

    /**
     * 计算无门槛券优惠金额
     *
     * @param template 优惠券模板
     * @return 优惠金额
     */
    private BigDecimal calculateNoThresholdCoupon(CouponTemplate template) {
        // 直接返回减免金额
        return template.getDiscountPrice() != null ? template.getDiscountPrice() : BigDecimal.ZERO;
    }

    /**
     * 检查优惠券是否有效
     *
     * @param couponId 优惠券ID
     * @return 是否有效
     */
    @Override
    public boolean isValidCoupon(Long couponId) {
        Coupon coupon = this.getById(couponId);
        if (coupon == null) {
            return false;
        }

        // 检查优惠券状态（1-未使用，2-已使用，3-已过期）
        if (coupon.getStatus() != 1) {
            return false;
        }

        // 检查是否过期
        if (coupon.getExpireTime().isBefore(LocalDateTime.now())) {
            return false;
        }

        return true;
    }

    /**
     * 检查优惠券是否可以使用
     *
     * @param couponId    优惠券ID
     * @param orderAmount 订单金额
     * @return 是否可以使用
     */
    public boolean canUseCoupon(Long couponId, BigDecimal orderAmount) {
        // 获取优惠券
        Coupon coupon = this.getById(couponId);
        if (coupon == null) {
            return false;
        }

        // 检查优惠券状态
        if (coupon.getStatus() != 1) {
            return false;
        }

        // 检查是否过期
        if (coupon.getExpireTime().isBefore(LocalDateTime.now())) {
            return false;
        }

        // 获取优惠券模板
        CouponTemplate template = couponTemplateService.getById(coupon.getTemplateId());
        if (template == null) {
            return false;
        }

        // 根据优惠券类型检查使用条件
        switch (template.getType()) {
            case 1: // 折扣券
                // 检查是否满足使用条件
                if (template.getUsePrice() != null && orderAmount.compareTo(template.getUsePrice()) < 0) {
                    return false;
                }
                return true;
            case 2: // 满减券
                // 检查是否满足使用条件
                if (template.getUsePrice() != null && orderAmount.compareTo(template.getUsePrice()) < 0) {
                    return false;
                }
                return true;
            case 3: // 无门槛券
                return true;
            default:
                return false;
        }
    }

    /**
     * 使用优惠券
     *
     * @param couponId    优惠券ID
     * @param orderId     订单ID
     * @param orderAmount 订单金额
     * @return 实际优惠金额
     */
    @Transactional(rollbackFor = Exception.class)
    public BigDecimal useCoupon(Long couponId, Long orderId, BigDecimal orderAmount) {
        // 获取优惠券
        Coupon coupon = this.getById(couponId);
        if (coupon == null) {
            throw new CouponException("优惠券不存在");
        }

        // 检查优惠券状态
        if (coupon.getStatus() != 1) {
            throw new CouponException("优惠券状态不正确");
        }

        // 检查是否过期
        if (coupon.getExpireTime().isBefore(LocalDateTime.now())) {
            throw new CouponException("优惠券已过期");
        }

        // 计算优惠金额
        BigDecimal discountAmount = calculateDiscount(coupon, orderAmount);

        // 更新优惠券状态为已使用
        coupon.setStatus(2); // 已使用
        coupon.setUseOrderId(orderId);
        coupon.setUseTime(LocalDateTime.now());
        this.updateById(coupon);

        return discountAmount;
    }


    /**
     * 过期优惠券处理
     *
     * @param couponId 优惠券ID
     * @return 是否处理成功
     */
    @Override
    public boolean expireCoupon(Long couponId) {
        Coupon coupon = this.getById(couponId);
        if (coupon == null) {
            return false;
        }

        // 检查优惠券状态
        if (coupon.getStatus() != 1) {
            return false;
        }

        // 检查是否过期
        if (coupon.getExpireTime().isAfter(LocalDateTime.now())) {
            return false;
        }

        // 更新优惠券状态为已过期
        coupon.setStatus(3); // 已过期
        return this.updateById(coupon);
    }

    /**
     * 获取用户指定状态的优惠券列表
     *
     * @param userId 用户ID
     * @param status 优惠券状态
     * @return 优惠券列表
     */
    @Override
    public List<Coupon> getUserCouponsByStatus(Long userId, Integer status) {
        return this.list(Wrappers.lambdaQuery(Coupon.class)
                .eq(Coupon::getUserId, userId)
                .eq(Coupon::getStatus, status));
    }

    /**
     * 根据模板ID和用户ID获取用户持有的优惠券
     *
     * @param templateId 模板ID
     * @param userId     用户ID
     * @return 优惠券列表
     */
    @Override
    public List<Coupon> getCouponsByTemplateAndUser(Long templateId, Long userId) {
        return this.list(Wrappers.lambdaQuery(Coupon.class)
                .eq(Coupon::getTemplateId, templateId)
                .eq(Coupon::getUserId, userId));
    }

    @Override
    public IPage<CouponVO> getUserCouponPage(AppCouponQuery queryParams) {
        queryParams.setUserId(SecurityUtils.getUserId());
        Page<CouponVO> pageVO = this.baseMapper.getUserCouponPage(
                new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
                queryParams
        );
        return pageVO;
    }

    @Override
    public AppCouponStatusNumVO getCouponStatusNum(Long userId) {
        return this.baseMapper.getCouponStatusNum(userId);
    }

    @Resource
    private RedissonClient redissonClient;

    // 获取分布式锁
    private RLock getLock(String couponCode) {
        return redissonClient.getLock("lock:coupon:" + couponCode);
    }

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean gainCoupon(AppGainCouponForm formData) {
        RLock lock = getLock(formData.getCouponCode());
        try {
            // 尝试获取锁（等待3秒，自动续期10秒）
            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException("系统繁忙，请稍后重试！");
            }
            
            formData.setUserId(SecurityUtils.getUserId());
            
            // 查询优惠券模板是否存在且可用
            CouponTemplate template = couponTemplateService.getOne(Wrappers.lambdaQuery(CouponTemplate.class)
                    .eq(CouponTemplate::getCode, formData.getCouponCode()));
            if (ObjectUtil.isEmpty(template)) {
                throw new CouponException("优惠券不存在");
            }
            if (!couponTemplateService.isValidTemplate(template.getId())) {
                throw new CouponException("优惠券已失效");
            }
            
            // 判断当前用户是否已经领取过该优惠券
            Coupon existCoupon = this.getOne(Wrappers.lambdaQuery(Coupon.class)
                    .eq(Coupon::getUserId, formData.getUserId())
                    .eq(Coupon::getCouponCode, formData.getCouponCode())
            );
            if (ObjectUtil.isNotEmpty(existCoupon)) {
                throw new CouponException("当前用户已经领取过该优惠券");
            }

            // Redis和MySQL库存扣减
            String stockKey = "coupon:stock:" + template.getId();
            
            // 初始化库存到Redis（如果不存在）
            initStockInRedis(stockKey, template);
            
            // 使用Redis的decr命令进行原子性扣减库存操作
            Long stock = redisTemplate.opsForValue().decrement(stockKey);
            if (stock == null || stock < 0) {
                // 库存不足，回滚操作
                if (stock != null && stock < 0) {
                    redisTemplate.opsForValue().increment(stockKey);
                }
                throw new CouponException("优惠券库存不足");
            }
            
            // 同步更新MySQL中的库存（使用乐观锁）
            boolean mysqlUpdated = updateMysqlStock(template.getId());
            if (!mysqlUpdated) {
                // MySQL库存更新失败，回滚Redis库存
                redisTemplate.opsForValue().increment(stockKey);
                throw new CouponException("优惠券库存不足");
            }

            // 创建用户优惠券
            Coupon userCoupon = new Coupon();
            userCoupon.setTemplateId(template.getId());
            userCoupon.setName(template.getName());
            userCoupon.setCouponCode(template.getCode());
            userCoupon.setStatus(1);
            userCoupon.setDescription(template.getDescription());
            userCoupon.setUserId(formData.getUserId());
            userCoupon.setTakeType(1);

            LocalDateTime now = LocalDateTime.now();
            userCoupon.setTakeTime(now);
            userCoupon.setExpireTime(now.plusDays(template.getFailureTime()));
            
            return this.save(userCoupon);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("系统繁忙，请稍后重试！");
        } catch (CouponException e) {
            // 优惠券相关异常直接抛出
            throw e;
        } catch (BusinessException e) {
            // 业务异常直接抛出
            throw e;
        } catch (Exception e) {
            // 其他异常记录日志后抛出
            log.error("优惠券领取异常，用户ID：{}, 优惠券码：{}", formData.getUserId(), formData.getCouponCode(), e);
            throw new BusinessException("优惠券领取失败，请稍后重试！");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 初始化优惠券库存到Redis
     * @param stockKey 库存键
     * @param template 优惠券模板
     */
    private void initStockInRedis(String stockKey, CouponTemplate template) {
        // 使用Redis的SETNX命令，只有当key不存在时才设置，保证原子性
        Boolean success = redisTemplate.opsForValue().setIfAbsent(stockKey, 0);
        if (Boolean.TRUE.equals(success)) {
            // 首次初始化，计算剩余库存
            int totalCount = template.getTotalCount();
            int usedCount = template.getTotalCountAll() != null ? template.getTotalCountAll() : 0;
            
            // 如果是无限库存(-1)则设置一个大数值
            int stock = totalCount == -1 ? 999999 : Math.max(0, totalCount - usedCount);
            redisTemplate.opsForValue().set(stockKey, stock);
            
            // 设置过期时间，防止Redis数据永久存在（可选，根据业务需求调整）
            redisTemplate.expire(stockKey, 7, TimeUnit.DAYS);
        }
    }
    
    /**
     * 更新MySQL中的库存数量
     * @param templateId 模板ID
     * @return 是否更新成功
     */
    private boolean updateMysqlStock(Long templateId) {
        // 使用乐观锁更新已发放数量
        CouponTemplate template = couponTemplateService.getById(templateId);
        if (template == null) {
            return false;
        }
        
        // 检查是否有限制发放数量且已达到上限
        if (template.getTotalCount() != -1) { // -1表示不限制
            int currentCount = template.getTotalCountAll() != null ? template.getTotalCountAll() : 0;
            if (currentCount >= template.getTotalCount()) {
                return false;
            }
        }
        
        // 使用乐观锁更新已发放数量（version字段由MyBatis-Plus自动处理）
        return couponTemplateService.update(
                Wrappers.<CouponTemplate>lambdaUpdate()
                        .setSql("total_count_all = total_count_all + 1")
                        .eq(CouponTemplate::getId, templateId)
                        .eq(CouponTemplate::getVersion, template.getVersion())
        );
    }
}
