package com.haha.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.haha.common.constant.CommonConstants;
import com.haha.common.constant.MarketingConstants;
import com.haha.common.enums.CommonStatus;
import com.haha.common.enums.CouponStatusEnum;
import com.haha.common.enums.DistributeTypeEnum;
import com.haha.common.enums.TargetTypeEnum;
import com.haha.common.enums.ValidTypeEnum;
import com.haha.common.exception.BusinessException;
import com.haha.entity.CouponDistribute;
import com.haha.entity.CouponTemplate;
import com.haha.entity.User;
import com.haha.entity.UserCoupon;
import com.haha.entity.dto.CouponDistributeDTO;
import com.haha.mapper.CouponDistributeMapper;
import com.haha.mapper.CouponTemplateMapper;
import com.haha.mapper.UserCouponMapper;
import com.haha.mapper.UserMapper;
import com.haha.service.UserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements UserCouponService {

    private final UserCouponMapper userCouponMapper;
    private final CouponTemplateMapper templateMapper;
    private final CouponDistributeMapper distributeMapper;
    private final UserMapper userMapper;
    private final StringRedisTemplate redisTemplate;

    @Override
    public IPage<UserCoupon> getMyCoupons(Long userId, Integer status, int page, int pageSize) {
        log.debug("[优惠券服务] 查询用户优惠券列表 - userId: {}, status: {}, page: {}, pageSize: {}", 
                userId, status, page, pageSize);
        
        Page<UserCoupon> pageParam = new Page<>(page, pageSize);

        LambdaQueryWrapper<UserCoupon> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserCoupon::getUserId, userId);
        if (status != null) {
            wrapper.eq(UserCoupon::getStatus, status);
        }
        wrapper.orderByDesc(UserCoupon::getCreateTime);

        IPage<UserCoupon> result = this.page(pageParam, wrapper);
        fillCouponLabelsBatch(result.getRecords());

        log.debug("[优惠券服务] 查询结果 - 总数: {}, 当前页数量: {}", result.getTotal(), result.getRecords().size());

        return result;
    }

    @Override
    public List<UserCoupon> getAvailableCoupons(Long userId) {
        log.debug("[优惠券服务] 查询用户可用优惠券 - userId: {}", userId);
        LocalDateTime now = LocalDateTime.now();
        List<UserCoupon> coupons = userCouponMapper.selectByUserIdAndStatus(userId, CouponStatusEnum.UNUSED.getCode());
        log.debug("[优惠券服务] 用户可用优惠券数量 - userId: {}, count: {}", userId, coupons.size());
        return coupons;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserCoupon receiveCoupon(Long userId, Long templateId) {
        log.info("[优惠券服务] 领取优惠券 - userId: {}, templateId: {}", userId, templateId);
        
        String lockKey = MarketingConstants.REDIS_KEY_COUPON_RECEIVE_LOCK + userId + ":" + templateId;
        Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 1, TimeUnit.MINUTES);
        if (!acquired) {
            log.warn("[优惠券服务] 领取优惠券失败 - 重复领取, userId: {}, templateId: {}", userId, templateId);
            throw new BusinessException(400, "请勿重复领取");
        }

        try {
            CouponTemplate template = templateMapper.selectById(templateId);
            if (template == null || template.getDeleted() == CommonConstants.DELETED) {
                log.warn("[优惠券服务] 领取优惠券失败 - 模板不存在, templateId: {}", templateId);
                throw new BusinessException(404, "优惠券不存在");
            }
            if (template.getStatus() != CommonStatus.ENABLED.getCode()) {
                log.warn("[优惠券服务] 领取优惠券失败 - 模板已禁用, templateId: {}", templateId);
                throw new BusinessException(400, "优惠券已禁用");
            }
            if (template.getRemainCount() <= 0) {
                log.warn("[优惠券服务] 领取优惠券失败 - 库存不足, templateId: {}, remainCount: {}", templateId, template.getRemainCount());
                throw new BusinessException(400, "优惠券已领完");
            }

            if (template.getValidType() == ValidTypeEnum.FIXED_TIME.getCode()
                    && template.getValidEndTime() != null
                    && LocalDateTime.now().isAfter(template.getValidEndTime())) {
                log.warn("[优惠券服务] 领取优惠券失败 - 优惠券已过期, templateId: {}, validEndTime: {}", templateId, template.getValidEndTime());
                throw new BusinessException(400, "优惠券已过期");
            }

            int receivedCount = userCouponMapper.countByTemplateAndUser(templateId, userId);
            if (receivedCount >= template.getReceiveLimit()) {
                log.warn("[优惠券服务] 领取优惠券失败 - 超过领取上限, userId: {}, templateId: {}, receivedCount: {}, limit: {}", 
                        userId, templateId, receivedCount, template.getReceiveLimit());
                throw new BusinessException(400, "已达到领取上限");
            }

            if (templateMapper.decreaseRemainCount(templateId) <= 0) {
                log.error("[优惠券服务] 领取优惠券失败 - 扣减库存失败, templateId: {}", templateId);
                throw new BusinessException(400, "优惠券领取失败，请重试");
            }

            UserCoupon userCoupon = new UserCoupon();
            userCoupon.setCouponCode(generateCouponCode());
            userCoupon.setTemplateId(templateId);
            userCoupon.setUserId(userId);
            userCoupon.setStatus(CouponStatusEnum.UNUSED.getCode());
            userCoupon.setReceiveTime(LocalDateTime.now());
            userCoupon.setDiscountAmount(template.getDiscountValue());

            if (template.getValidType() == ValidTypeEnum.FIXED_TIME.getCode()) {
                userCoupon.setValidStartTime(template.getValidStartTime());
                userCoupon.setValidEndTime(template.getValidEndTime());
            } else {
                LocalDateTime now = LocalDateTime.now();
                userCoupon.setValidStartTime(now);
                userCoupon.setValidEndTime(now.plusDays(template.getValidDays()));
            }

            this.save(userCoupon);

            log.info("[优惠券服务] 领取优惠券成功 - userId: {}, templateId: {}, couponCode: {}, validTo: {}", 
                    userId, templateId, userCoupon.getCouponCode(), userCoupon.getValidEndTime());
            return userCoupon;

        } finally {
            redisTemplate.delete(lockKey);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void distributeCoupon(CouponDistributeDTO dto, Long operatorId, String operatorName) {
        log.info("[优惠券发放] 开始发放 - templateId={}, targetType={}, userIds={}", 
                dto.getTemplateId(), dto.getTargetType(), dto.getUserIds());
        
        CouponTemplate template = templateMapper.selectById(dto.getTemplateId());
        log.info("[优惠券发放] 查询模板结果 - templateId={}, template={}", 
                dto.getTemplateId(), template != null ? "存在" : "不存在");
        
        if (template == null || template.getDeleted() == CommonConstants.DELETED) {
            log.error("[优惠券发放] 模板不存在或已删除 - templateId={}, deleted={}",
                    dto.getTemplateId(), template != null ? template.getDeleted() : "N/A");
            throw new BusinessException(404, "优惠券模板不存在");
        }

        if (template.getValidType() == ValidTypeEnum.FIXED_TIME.getCode()
                && template.getValidEndTime() != null
                && LocalDateTime.now().isAfter(template.getValidEndTime())) {
            throw new BusinessException(400, "优惠券已过期，无法发放");
        }

        List<Long> targetUserIds = getTargetUserIds(dto.getTargetType(), dto.getUserIds());
        if (targetUserIds.isEmpty()) {
            throw new BusinessException(400, "没有目标用户");
        }

        if (targetUserIds.size() > template.getRemainCount()) {
            throw new BusinessException(400, "优惠券数量不足");
        }

        CouponDistribute distribute = new CouponDistribute();
        distribute.setTemplateId(dto.getTemplateId());
        distribute.setDistributeType(DistributeTypeEnum.TARGET_SEND.getCode());
        distribute.setTargetType(dto.getTargetType());
        distribute.setTargetCount(targetUserIds.size());
        distribute.setSuccessCount(0);
        distribute.setOperatorId(operatorId);
        distribute.setOperatorName(operatorName);

        distributeMapper.insert(distribute);

        int successCount = 0;
        List<UserCoupon> couponBatch = new ArrayList<>(targetUserIds.size());

        for (Long userId : targetUserIds) {
            try {
                UserCoupon userCoupon = new UserCoupon();
                userCoupon.setCouponCode(generateCouponCode());
                userCoupon.setTemplateId(dto.getTemplateId());
                userCoupon.setUserId(userId);
                userCoupon.setStatus(CouponStatusEnum.UNUSED.getCode());
                userCoupon.setReceiveTime(LocalDateTime.now());
                userCoupon.setDiscountAmount(template.getDiscountValue());

                if (template.getValidType() == ValidTypeEnum.FIXED_TIME.getCode()) {
                    userCoupon.setValidStartTime(template.getValidStartTime());
                    userCoupon.setValidEndTime(template.getValidEndTime());
                } else {
                    LocalDateTime now = LocalDateTime.now();
                    userCoupon.setValidStartTime(now);
                    userCoupon.setValidEndTime(now.plusDays(template.getValidDays()));
                }

                couponBatch.add(userCoupon);
                successCount++;
            } catch (Exception e) {
                log.error("构建优惠券失败: userId={}, templateId={}", userId, dto.getTemplateId(), e);
            }
        }

        // 批量保存（1 次批量 INSERT 替代 N 次逐条 INSERT）
        if (!couponBatch.isEmpty()) {
            this.saveBatch(couponBatch);
        }
        // 批量扣减库存（1 次 UPDATE 替代 N 次逐条 UPDATE）
        templateMapper.decreaseRemainCount(dto.getTemplateId(), successCount);

        distribute.setSuccessCount(successCount);
        distributeMapper.updateById(distribute);

        log.info("批量发放优惠券完成: templateId={}, targetCount={}, successCount={}", dto.getTemplateId(), targetUserIds.size(), successCount);
    }

    @Override
    public UserCoupon getDetail(Long id, Long userId) {
        UserCoupon userCoupon = userCouponMapper.selectWithTemplate(id);
        if (userCoupon == null) {
            throw new BusinessException(404, "优惠券不存在");
        }
        if (!userCoupon.getUserId().equals(userId)) {
            throw new BusinessException(403, "无权访问该优惠券");
        }
        fillCouponLabels(userCoupon);
        return userCoupon;
    }

    /**
     * 使用优惠券
     * 
     * 业务流程：
     * 1. 验证优惠券是否存在且属于当前用户
     * 2. 检查优惠券状态（必须未使用）
     * 3. 检查优惠券是否过期
     * 4. 更新优惠券状态为已使用
     * 5. 记录使用的订单ID和实际优惠金额
     * 
     * 注意：
     * - discountAmount 参数是调用方已经计算好的实际优惠金额
     * - 此方法只负责标记优惠券为已使用，不负责计算优惠
     * - 优惠金额的计算逻辑应该在订单结算时由调用方完成
     * 
     * @param id 用户优惠券ID
     * @param userId 用户ID
     * @param orderId 订单ID
     * @param discountAmount 实际优惠金额（由调用方计算）
     * @return 是否使用成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean useCoupon(Long id, Long userId, Long orderId, BigDecimal discountAmount) {
        // 1. 查询优惠券信息
        UserCoupon userCoupon = this.getById(id);
        if (userCoupon == null) {
            throw new BusinessException(404, "优惠券不存在");
        }
        
        // 2. 验证优惠券归属
        if (!userCoupon.getUserId().equals(userId)) {
            throw new BusinessException(403, "无权使用该优惠券");
        }
        
        // 3. 检查优惠券状态（必须未使用）
        if (!CouponStatusEnum.canUse(userCoupon.getStatus())) {
            throw new BusinessException(400, "优惠券不可用");
        }
        
        // 4. 检查优惠券是否在有效期内
        if (LocalDateTime.now().isBefore(userCoupon.getValidStartTime())) {
            throw new BusinessException(400, "优惠券尚未生效");
        }
        if (LocalDateTime.now().isAfter(userCoupon.getValidEndTime())) {
            throw new BusinessException(400, "优惠券已过期");
        }

        // 5. 更新优惠券状态为已使用
        userCoupon.setStatus(CouponStatusEnum.USED.getCode());
        userCoupon.setOrderId(orderId);                        // 关联订单ID
        userCoupon.setUseTime(LocalDateTime.now());            // 记录使用时间
        userCoupon.setDiscountAmount(discountAmount);          // 记录实际优惠金额

        boolean result = this.updateById(userCoupon);

        if (result) {
            log.info("使用优惠券成功: id={}, userId={}, orderId={}, discountAmount={}", id, userId, orderId, discountAmount);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void expireCoupons() {
        int count = userCouponMapper.updateExpiredCoupons(LocalDateTime.now());
        if (count > 0) {
            log.info("更新过期优惠券完成: count={}", count);
        }
    }

    @Override
    public int countAvailableCoupons(Long userId) {
        return userCouponMapper.countAvailableByUserId(userId, LocalDateTime.now());
    }

    @Override
    public Map<String, Object> getCouponStatistics(Long templateId) {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalReceived", userCouponMapper.countByTemplateId(templateId));
        stats.put("totalUsed", userCouponMapper.countUsedByTemplateId(templateId));
        return stats;
    }

    private String generateCouponCode() {
        return MarketingConstants.COUPON_CODE_PREFIX + IdUtil.getSnowflakeNextIdStr();
    }

    private List<Long> getTargetUserIds(Integer targetType, List<Long> userIds) {
        if (targetType == TargetTypeEnum.SPECIFIED_USER.getCode()) {
            return userIds != null ? userIds : new ArrayList<>();
        } else if (targetType == TargetTypeEnum.ALL_USER.getCode()) {
            List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>().select(User::getId));
            return users.stream().map(User::getId).toList();
        } else if (targetType == TargetTypeEnum.NEW_USER.getCode()) {
            LocalDateTime weekAgo = LocalDateTime.now().minusDays(7);
            List<User> users = userMapper.selectList(
                    new LambdaQueryWrapper<User>()
                            .select(User::getId)
                            .ge(User::getCreateTime, weekAgo));
            return users.stream().map(User::getId).toList();
        }
        return new ArrayList<>();
    }

    private void fillCouponLabelsBatch(List<UserCoupon> coupons) {
        if (coupons == null || coupons.isEmpty()) return;

        // 批量查询模板信息
        List<Long> templateIds = coupons.stream()
                .map(UserCoupon::getTemplateId)
                .filter(id -> id != null)
                .distinct()
                .collect(Collectors.toList());
        Map<Long, CouponTemplate> templateMap = new HashMap<>();
        if (!templateIds.isEmpty()) {
            List<CouponTemplate> templates = templateMapper.selectBatchIds(templateIds);
            if (templates != null) {
                templateMap = templates.stream()
                        .collect(Collectors.toMap(CouponTemplate::getId, t -> t));
            }
        }

        for (UserCoupon coupon : coupons) {
            fillCouponLabelsFromMap(coupon, templateMap);
        }
    }

    private void fillCouponLabelsFromMap(UserCoupon userCoupon, Map<Long, CouponTemplate> templateMap) {
        CouponTemplate template = templateMap.get(userCoupon.getTemplateId());
        if (template != null) {
            userCoupon.setCouponName(template.getCouponName());
            userCoupon.setCouponType(template.getCouponType());
            userCoupon.setMinAmount(template.getMinAmount());
            userCoupon.setCouponDesc(template.getCouponDesc());
            userCoupon.setApplyScope(template.getApplyScope());
            userCoupon.setDiscountValue(template.getDiscountValue());
        }
        com.haha.common.vo.StatusLabel label = CouponStatusEnum.getLabelByCode(userCoupon.getStatus());
        userCoupon.setStatusLabel(label.getLabel());
        userCoupon.setStatusColor(label.getColor());
    }

    private void fillCouponLabels(UserCoupon userCoupon) {
        CouponTemplate template = templateMapper.selectById(userCoupon.getTemplateId());
        Map<Long, CouponTemplate> templateMap = new HashMap<>();
        if (template != null) templateMap.put(template.getId(), template);
        fillCouponLabelsFromMap(userCoupon, templateMap);
    }

    @Override
    public IPage<UserCoupon> getUserCouponList(Long userId, String couponCode, Integer status, int page, int pageSize) {
        log.debug("[优惠券服务] 查询用户优惠券列表 - userId: {}, couponCode: {}, status: {}, page: {}, pageSize: {}",
                userId, couponCode, status, page, pageSize);

        Page<UserCoupon> pageParam = new Page<>(page, pageSize);

        LambdaQueryWrapper<UserCoupon> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(userId != null, UserCoupon::getUserId, userId);
        wrapper.eq(couponCode != null && !couponCode.isEmpty(), UserCoupon::getCouponCode, couponCode);
        wrapper.eq(status != null, UserCoupon::getStatus, status);
        wrapper.orderByDesc(UserCoupon::getCreateTime);

        IPage<UserCoupon> result = this.page(pageParam, wrapper);
        fillCouponLabelsBatch(result.getRecords());

        log.debug("[优惠券服务] 查询结果 - 总数: {}, 当前页数量: {}", result.getTotal(), result.getRecords().size());

        return result;
    }

    @Override
    public int countReceivedByUser(Long userId, Long templateId) {
        log.debug("[优惠券服务] 查询用户已领取优惠券数量 - userId: {}, templateId: {}", userId, templateId);
        LambdaQueryWrapper<UserCoupon> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserCoupon::getUserId, userId)
               .eq(UserCoupon::getTemplateId, templateId);
        return Math.toIntExact(this.count(wrapper));
    }
}
