package com.snack.server.module.coupon.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.snack.server.common.ResultCode;
import com.snack.server.exception.BusinessException;
import com.snack.server.module.coupon.dto.req.CouponPageReq;
import com.snack.server.module.coupon.dto.req.CouponSaveReq;
import com.snack.server.module.coupon.dto.req.UserCouponPageReq;
import com.snack.server.module.coupon.entity.Coupon;
import com.snack.server.module.coupon.entity.UserCoupon;
import com.snack.server.module.coupon.mapper.CouponMapper;
import com.snack.server.module.coupon.mapper.UserCouponMapper;
import com.snack.server.module.coupon.service.CouponService;
import com.snack.server.module.coupon.vo.CouponVO;
import com.snack.server.module.coupon.vo.UserCouponVO;
import com.snack.server.module.user.entity.User;
import com.snack.server.module.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 优惠券服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CouponServiceImpl implements CouponService {

    private final CouponMapper couponMapper;
    private final UserCouponMapper userCouponMapper;
    private final UserMapper userMapper;

    @Override
    public Page<CouponVO> pageCoupons(CouponPageReq req) {
        Page<Coupon> page = new Page<>(req.getCurrent(), req.getSize());
        LambdaQueryWrapper<Coupon> wrapper = new LambdaQueryWrapper<Coupon>()
                .like(StrUtil.isNotBlank(req.getKeyword()), Coupon::getName, req.getKeyword())
                .eq(req.getType() != null, Coupon::getType, req.getType())
                .eq(req.getStatus() != null, Coupon::getStatus, req.getStatus())
                .orderByDesc(Coupon::getId);

        Page<Coupon> result = couponMapper.selectPage(page, wrapper);
        Page<CouponVO> voPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
        voPage.setRecords(result.getRecords().stream().map(this::toCouponVO).toList());
        return voPage;
    }

    @Override
    public CouponVO getCouponById(Long id) {
        Coupon coupon = couponMapper.selectById(id);
        if (coupon == null) {
            throw new BusinessException(ResultCode.COUPON_NOT_EXIST);
        }
        return toCouponVO(coupon);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createCoupon(CouponSaveReq req) {
        validateCoupon(req);
        Coupon coupon = new Coupon();
        BeanUtil.copyProperties(req, coupon, "id");
        coupon.setMinAmount(defaultMoney(coupon.getMinAmount()));
        coupon.setMaxDiscount(defaultMoney(coupon.getMaxDiscount()));
        coupon.setPerLimit(coupon.getPerLimit() == null ? 1 : coupon.getPerLimit());
        coupon.setStatus(coupon.getStatus() == null ? 1 : coupon.getStatus());
        coupon.setRemain(coupon.getTotal() == -1 ? -1 : coupon.getTotal());
        couponMapper.insert(coupon);
        log.info("创建优惠券 id={} name={}", coupon.getId(), coupon.getName());
        return coupon.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCoupon(CouponSaveReq req) {
        if (req.getId() == null) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "ID 不能为空");
        }
        validateCoupon(req);
        Coupon existing = couponMapper.selectById(req.getId());
        if (existing == null) {
            throw new BusinessException(ResultCode.COUPON_NOT_EXIST);
        }

        Coupon coupon = new Coupon();
        BeanUtil.copyProperties(req, coupon);
        coupon.setMinAmount(defaultMoney(coupon.getMinAmount()));
        coupon.setMaxDiscount(defaultMoney(coupon.getMaxDiscount()));
        coupon.setPerLimit(coupon.getPerLimit() == null ? 1 : coupon.getPerLimit());
        coupon.setStatus(coupon.getStatus() == null ? existing.getStatus() : coupon.getStatus());
        coupon.setRemain(calcUpdatedRemain(existing, req.getTotal()));
        couponMapper.updateById(coupon);
        log.info("更新优惠券 id={}", coupon.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCoupon(Long id) {
        Coupon coupon = couponMapper.selectById(id);
        if (coupon == null) {
            throw new BusinessException(ResultCode.COUPON_NOT_EXIST);
        }
        Long received = userCouponMapper.selectCount(
                new LambdaQueryWrapper<UserCoupon>().eq(UserCoupon::getCouponId, id)
        );
        if (received > 0) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "该优惠券已有领取记录，不能删除");
        }
        couponMapper.deleteById(id);
        log.info("删除优惠券 id={}", id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStatus(Long id, Integer status) {
        if (id == null || status == null || status < 0 || status > 2) {
            throw new BusinessException(ResultCode.PARAM_ERROR);
        }
        Coupon coupon = couponMapper.selectById(id);
        if (coupon == null) {
            throw new BusinessException(ResultCode.COUPON_NOT_EXIST);
        }
        Coupon update = new Coupon();
        update.setId(id);
        update.setStatus(status);
        couponMapper.updateById(update);
    }

    @Override
    public Page<UserCouponVO> pageUserCoupons(UserCouponPageReq req) {
        refreshExpiredUserCoupons();

        List<Long> matchedUserIds = null;
        if (StrUtil.isNotBlank(req.getKeyword())) {
            matchedUserIds = userMapper.selectList(
                    new LambdaQueryWrapper<User>()
                            .like(User::getUsername, req.getKeyword())
                            .or().like(User::getNickname, req.getKeyword())
                            .or().like(User::getPhone, req.getKeyword())
            ).stream().map(User::getId).toList();
            if (CollUtil.isEmpty(matchedUserIds)) {
                Page<UserCouponVO> empty = new Page<>(req.getCurrent(), req.getSize(), 0);
                empty.setRecords(Collections.emptyList());
                return empty;
            }
        }

        Page<UserCoupon> page = new Page<>(req.getCurrent(), req.getSize());
        LambdaQueryWrapper<UserCoupon> wrapper = new LambdaQueryWrapper<UserCoupon>()
                .eq(req.getCouponId() != null, UserCoupon::getCouponId, req.getCouponId())
                .eq(req.getUserId() != null, UserCoupon::getUserId, req.getUserId())
                .eq(req.getStatus() != null, UserCoupon::getStatus, req.getStatus())
                .in(matchedUserIds != null, UserCoupon::getUserId, matchedUserIds)
                .orderByDesc(UserCoupon::getId);

        Page<UserCoupon> result = userCouponMapper.selectPage(page, wrapper);
        Page<UserCouponVO> voPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
        voPage.setRecords(toUserCouponVOList(result.getRecords()));
        return voPage;
    }

    @Override
    public List<CouponVO> listReceivableCoupons(Long userId) {
        LocalDateTime now = LocalDateTime.now();
        List<Coupon> coupons = couponMapper.selectList(
                new LambdaQueryWrapper<Coupon>()
                        .eq(Coupon::getStatus, 1)
                        .le(Coupon::getStartTime, now)
                        .ge(Coupon::getEndTime, now)
                        .and(w -> w.eq(Coupon::getTotal, -1).or().gt(Coupon::getRemain, 0))
                        .orderByDesc(Coupon::getId)
        );
        return coupons.stream().map(this::toCouponVO).toList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized Long receiveCoupon(Long couponId, Long userId) {
        Coupon coupon = couponMapper.selectById(couponId);
        if (coupon == null) {
            throw new BusinessException(ResultCode.COUPON_NOT_EXIST);
        }
        LocalDateTime now = LocalDateTime.now();
        if (!Objects.equals(coupon.getStatus(), 1)) {
            throw new BusinessException(ResultCode.COUPON_EXPIRED.getCode(), "优惠券未上线或已结束");
        }
        if (now.isBefore(coupon.getStartTime()) || now.isAfter(coupon.getEndTime())) {
            throw new BusinessException(ResultCode.COUPON_EXPIRED);
        }
        if (!Objects.equals(coupon.getTotal(), -1) && (coupon.getRemain() == null || coupon.getRemain() <= 0)) {
            throw new BusinessException(ResultCode.COUPON_STOCK_EMPTY);
        }

        Long received = userCouponMapper.selectCount(
                new LambdaQueryWrapper<UserCoupon>()
                        .eq(UserCoupon::getUserId, userId)
                        .eq(UserCoupon::getCouponId, couponId)
        );
        if (received >= coupon.getPerLimit()) {
            throw new BusinessException(ResultCode.COUPON_RECEIVE_LIMIT);
        }

        if (!Objects.equals(coupon.getTotal(), -1)) {
            int affected = couponMapper.update(null, new LambdaUpdateWrapper<Coupon>()
                    .eq(Coupon::getId, couponId)
                    .gt(Coupon::getRemain, 0)
                    .setSql("remain = remain - 1"));
            if (affected <= 0) {
                throw new BusinessException(ResultCode.COUPON_STOCK_EMPTY);
            }
        }

        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userId);
        userCoupon.setCouponId(couponId);
        userCoupon.setStatus(0);
        userCoupon.setReceiveTime(now);
        userCoupon.setExpireTime(coupon.getValidDays() != null && coupon.getValidDays() > 0
                ? now.plusDays(coupon.getValidDays())
                : coupon.getEndTime());
        userCouponMapper.insert(userCoupon);
        return userCoupon.getId();
    }

    @Override
    public List<UserCouponVO> listMyCoupons(Long userId, Integer status) {
        refreshExpiredUserCoupons();
        List<UserCoupon> list = userCouponMapper.selectList(
                new LambdaQueryWrapper<UserCoupon>()
                        .eq(UserCoupon::getUserId, userId)
                        .eq(status != null, UserCoupon::getStatus, status)
                        .orderByDesc(UserCoupon::getId)
        );
        return toUserCouponVOList(list);
    }

    private void validateCoupon(CouponSaveReq req) {
        if (req.getType() == null || req.getType() < 0 || req.getType() > 2) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "优惠券类型不正确");
        }
        if (req.getTotal() == null || req.getTotal() == 0 || req.getTotal() < -1) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "发放总量必须大于 0，或填 -1 表示不限量");
        }
        if (req.getPerLimit() != null && req.getPerLimit() <= 0) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "每人限领数量必须大于 0");
        }
        if (req.getStartTime() != null && req.getEndTime() != null && !req.getStartTime().isBefore(req.getEndTime())) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "领取开始时间必须早于截止时间");
        }
        if (req.getType() == 1 && req.getAmount().compareTo(BigDecimal.ONE) >= 0) {
            throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "折扣券折扣率应小于 1，例如 0.90 表示 9 折");
        }
        if (req.getType() == 2) {
            req.setMinAmount(BigDecimal.ZERO);
        }
    }

    private int calcUpdatedRemain(Coupon existing, Integer newTotal) {
        if (newTotal == -1) {
            return -1;
        }
        if (Objects.equals(existing.getTotal(), -1)) {
            return newTotal;
        }
        int used = Math.max(0, existing.getTotal() - existing.getRemain());
        return Math.max(0, newTotal - used);
    }

    private void refreshExpiredUserCoupons() {
        userCouponMapper.update(null, new LambdaUpdateWrapper<UserCoupon>()
                .eq(UserCoupon::getStatus, 0)
                .lt(UserCoupon::getExpireTime, LocalDateTime.now())
                .set(UserCoupon::getStatus, 2));
    }

    private CouponVO toCouponVO(Coupon coupon) {
        CouponVO vo = new CouponVO();
        BeanUtil.copyProperties(coupon, vo);
        vo.setTypeText(typeText(coupon.getType()));
        vo.setStatusText(couponStatusText(coupon.getStatus()));
        if (coupon.getTotal() != null && coupon.getTotal() == -1) {
            vo.setReceived(0);
        } else {
            int total = coupon.getTotal() == null ? 0 : coupon.getTotal();
            int remain = coupon.getRemain() == null ? 0 : coupon.getRemain();
            vo.setReceived(Math.max(0, total - remain));
        }
        return vo;
    }

    private List<UserCouponVO> toUserCouponVOList(List<UserCoupon> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<Long> couponIds = list.stream().map(UserCoupon::getCouponId).distinct().toList();
        List<Long> userIds = list.stream().map(UserCoupon::getUserId).distinct().toList();
        Map<Long, Coupon> couponMap = couponMapper.selectByIds(couponIds).stream()
                .collect(Collectors.toMap(Coupon::getId, Function.identity()));
        Map<Long, User> userMap = userMapper.selectByIds(userIds).stream()
                .collect(Collectors.toMap(User::getId, Function.identity()));

        return list.stream().map(uc -> {
            UserCouponVO vo = new UserCouponVO();
            BeanUtil.copyProperties(uc, vo);
            Coupon coupon = couponMap.get(uc.getCouponId());
            if (coupon != null) {
                vo.setCouponName(coupon.getName());
                vo.setCouponType(coupon.getType());
            }
            User user = userMap.get(uc.getUserId());
            if (user != null) {
                vo.setUsername(user.getUsername());
                vo.setNickname(user.getNickname());
            }
            vo.setStatusText(userCouponStatusText(uc.getStatus()));
            return vo;
        }).toList();
    }

    private BigDecimal defaultMoney(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }

    private String typeText(Integer type) {
        if (type == null) return "";
        return switch (type) {
            case 0 -> "满减券";
            case 1 -> "折扣券";
            case 2 -> "无门槛券";
            default -> "未知";
        };
    }

    private String couponStatusText(Integer status) {
        if (status == null) return "";
        return switch (status) {
            case 0 -> "未上线";
            case 1 -> "已上线";
            case 2 -> "已结束";
            default -> "未知";
        };
    }

    private String userCouponStatusText(Integer status) {
        if (status == null) return "";
        return switch (status) {
            case 0 -> "未使用";
            case 1 -> "已使用";
            case 2 -> "已过期";
            case 3 -> "已作废";
            default -> "未知";
        };
    }
}
