package shop.alien.store.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.alien.entity.store.PlatformShopMembership;
import shop.alien.entity.store.PlatformShopMembershipOrder;
import shop.alien.entity.store.PlatformShopMembershipPlan;
import shop.alien.entity.store.dto.PlatformShopMembershipOrderCreateRequest;
import shop.alien.entity.store.vo.PlatformShopMembershipOrderCreateResultVo;
import shop.alien.entity.store.vo.PlatformShopMembershipOrderItemVo;
import shop.alien.mapper.PlatformShopMembershipMapper;
import shop.alien.mapper.PlatformShopMembershipOrderMapper;
import shop.alien.store.platformmembership.PlatformShopMembershipDateUtil;
import shop.alien.store.service.PlatformShopMembershipOrderCreateDebounceService;
import shop.alien.store.service.PlatformShopMembershipOrderPaymentTimeoutService;
import shop.alien.store.service.PlatformShopMembershipOrderService;
import shop.alien.store.service.PlatformShopMembershipPlanService;
import shop.alien.util.common.UniqueRandomNumGenerator;

import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class PlatformShopMembershipOrderServiceImpl
        extends ServiceImpl<PlatformShopMembershipOrderMapper, PlatformShopMembershipOrder>
        implements PlatformShopMembershipOrderService {

    private final PlatformShopMembershipPlanService planService;
    private final PlatformShopMembershipMapper membershipMapper;
    private final PlatformShopMembershipOrderPaymentTimeoutService orderPaymentTimeoutService;
    private final PlatformShopMembershipOrderCreateDebounceService orderCreateDebounceService;

    /**
     * 创建待支付的会员卡订单（店铺端）。
     * <p>
     * <b>业务目标</b>：在方案有效的前提下，为指定门店生成一笔待支付订单，供后续拉起微信/支付宝等支付；
     * 同一门店 + 同一方案在 15 分钟内重复调用时，不重复落库，直接返回 Redis 中缓存的创建结果。
     * </p>
     * <p>
     * <b>防抖（Redis）</b>：
     * <ul>
     *   <li>Redis Key：{@code platform:membership:order:create:debounce:} + storeId + ":" + planId；TTL 15 分钟。</li>
     *   <li>命中缓存时仍会查库校验：订单存在、支付状态仍为「待支付」、门店与方案与请求一致；否则删除脏缓存并走新建流程。</li>
     *   <li>缓存由「新建成功」写入；支付成功、支付超时关单、或本方法内关闭同店其他待支付单时，会清除对应槽位（避免指向已关闭订单）。</li>
     * </ul>
     * </p>
     * <p>
     * <b>同店待支付互斥</b>：新建前会关闭该门店下所有其他「待支付」会员卡订单（避免多笔待支付并存），并清除这些订单在防抖 Redis 上的槽位。
     * </p>
     * <p>
     * <b>支付超时</b>：新建后为该订单注册 Redis 支付超时监听（15 分钟未支付则关单，依赖 Redis key 过期通知配置）。
     * </p>
     *
     * @param request 门店 ID、方案 ID、付款用户 ID（写入订单；防抖不按付款人区分）
     * @return 订单 ID、订单号、应付金额等，供前端发起支付
     * @throws IllegalArgumentException 方案不存在或已下架
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PlatformShopMembershipOrderCreateResultVo createPendingOrder(PlatformShopMembershipOrderCreateRequest request) {
        PlatformShopMembershipPlan plan = planService.getEnabledById(request.getPlanId());
        if (plan == null) {
            throw new IllegalArgumentException("会员卡方案不存在或已下架");
        }

        // 15 分钟内同店同方案：若 Redis 有有效待支付快照，直接返回，不再关单、不再插入新订单
        Optional<PlatformShopMembershipOrderCreateResultVo> cached =
                orderCreateDebounceService.getReusablePendingOrder(request.getStoreId(), request.getPlanId());
        if (cached.isPresent()) {
            return cached.get();
        }

        // 无可用缓存：关闭本店其他待支付单并清防抖槽位，再创建新订单
        closeOtherPendingOrders(request.getStoreId());

        PlatformShopMembershipOrder order = new PlatformShopMembershipOrder();
        order.setOrderSn("PM" + UniqueRandomNumGenerator.generateUniqueCode(16));
        order.setStoreId(request.getStoreId());
        order.setPlanId(plan.getId());
        order.setPlanName(plan.getPlanName());
        order.setDurationMonths(plan.getDurationMonths());
        order.setPayAmount(plan.getPrice().setScale(2, RoundingMode.HALF_UP));
        order.setPayStatus(PlatformShopMembershipOrder.PAY_STATUS_PENDING);
        order.setPayerUserId(request.getPayerUserId());
        order.setCreatedTime(new Date());
        order.setUpdatedTime(new Date());
        save(order);
        // 15 分钟未支付则通过 Redis 过期事件关单（需服务端开启 key 过期通知）
        orderPaymentTimeoutService.scheduleClosePendingOrder(order.getId());

        PlatformShopMembershipOrderCreateResultVo vo = new PlatformShopMembershipOrderCreateResultVo();
        vo.setOrderId(order.getId());
        vo.setOrderSn(order.getOrderSn());
        vo.setPayAmount(order.getPayAmount());
        vo.setPlanName(order.getPlanName());
        vo.setDurationMonths(order.getDurationMonths());
        // 写入防抖缓存，短时间内重复创建同店同方案将命中上文分支
        orderCreateDebounceService.rememberPendingOrder(request.getStoreId(), request.getPlanId(), vo);
        return vo;
    }

    @Override
    public PlatformShopMembershipOrder findPaidOrderForCurrentMembershipExpiry(Integer storeId, Date membershipExpiryDate) {
        if (storeId == null || membershipExpiryDate == null) {
            return null;
        }
        LambdaQueryWrapper<PlatformShopMembershipOrder> w = new LambdaQueryWrapper<>();
        w.eq(PlatformShopMembershipOrder::getStoreId, storeId);
        w.eq(PlatformShopMembershipOrder::getPayStatus, PlatformShopMembershipOrder.PAY_STATUS_PAID);
        w.eq(PlatformShopMembershipOrder::getValidityEnd, membershipExpiryDate);
        w.orderByDesc(PlatformShopMembershipOrder::getPayTime).orderByDesc(PlatformShopMembershipOrder::getId);
        w.last("LIMIT 1");
        PlatformShopMembershipOrder o = getBaseMapper().selectOne(w);
        if (o != null) {
            return o;
        }
        LambdaQueryWrapper<PlatformShopMembershipOrder> w2 = new LambdaQueryWrapper<>();
        w2.eq(PlatformShopMembershipOrder::getStoreId, storeId);
        w2.eq(PlatformShopMembershipOrder::getPayStatus, PlatformShopMembershipOrder.PAY_STATUS_PAID);
        w2.orderByDesc(PlatformShopMembershipOrder::getPayTime).orderByDesc(PlatformShopMembershipOrder::getId);
        w2.last("LIMIT 1");
        return getBaseMapper().selectOne(w2);
    }

    private void closeOtherPendingOrders(Integer storeId) {
        LambdaQueryWrapper<PlatformShopMembershipOrder> q = new LambdaQueryWrapper<>();
        q.eq(PlatformShopMembershipOrder::getStoreId, storeId);
        q.eq(PlatformShopMembershipOrder::getPayStatus, PlatformShopMembershipOrder.PAY_STATUS_PENDING);
        List<PlatformShopMembershipOrder> pendings = list(q);
        for (PlatformShopMembershipOrder o : pendings) {
            orderCreateDebounceService.clearPendingSlot(o.getStoreId(), o.getPlanId());
        }
        if (pendings.isEmpty()) {
            return;
        }
        LambdaUpdateWrapper<PlatformShopMembershipOrder> u = new LambdaUpdateWrapper<>();
        u.eq(PlatformShopMembershipOrder::getStoreId, storeId);
        u.eq(PlatformShopMembershipOrder::getPayStatus, PlatformShopMembershipOrder.PAY_STATUS_PENDING);
        u.set(PlatformShopMembershipOrder::getPayStatus, PlatformShopMembershipOrder.PAY_STATUS_CLOSED);
        u.set(PlatformShopMembershipOrder::getUpdatedTime, new Date());
        update(u);
    }

    @Override
    public IPage<PlatformShopMembershipOrderItemVo> pageHistory(Integer storeId, int pageNum, int pageSize) {
        Page<PlatformShopMembershipOrderItemVo> p = new Page<>(pageNum, pageSize);
        return baseMapper.selectHistoryPage(p, storeId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyPaySuccess(Integer membershipOrderId, Date payTime, String outTradeNo,
                                String tradeNo, String paymentMethod) {
        if (membershipOrderId == null || payTime == null) {
            return;
        }
        PlatformShopMembershipOrder order = getById(membershipOrderId);
        if (order == null || order.getPayStatus() == null) {
            return;
        }
        if (order.getPayStatus() != PlatformShopMembershipOrder.PAY_STATUS_PENDING) {
            return;
        }

        orderPaymentTimeoutService.cancelClosePendingOrder(membershipOrderId);
        orderCreateDebounceService.clearPendingSlot(order.getStoreId(), order.getPlanId());

        LambdaQueryWrapper<PlatformShopMembership> qw = new LambdaQueryWrapper<>();
        qw.eq(PlatformShopMembership::getStoreId, order.getStoreId());
        PlatformShopMembership mem = membershipMapper.selectOne(qw);

        Date oldExpiry = mem != null ? mem.getExpiryDate() : null;
        boolean stillActive = oldExpiry != null && !oldExpiry.before(payTime);

        Date validityStart;
        Date validityEnd;
        if (stillActive) {
            validityStart = oldExpiry;
            validityEnd = PlatformShopMembershipDateUtil.addMonths(oldExpiry, order.getDurationMonths());
        } else {
            validityStart = payTime;
            validityEnd = PlatformShopMembershipDateUtil.addMonths(payTime, order.getDurationMonths());
        }

        order.setValidityStart(validityStart);
        order.setValidityEnd(validityEnd);
        order.setPayStatus(PlatformShopMembershipOrder.PAY_STATUS_PAID);
        order.setPayTime(payTime);
        order.setPaymentMethod(paymentMethod);
        order.setOutTradeNo(outTradeNo);
        order.setTradeNo(tradeNo);
        order.setUpdatedTime(payTime);
        updateById(order);

        if (mem == null) {
            mem = new PlatformShopMembership();
            mem.setStoreId(order.getStoreId());
            mem.setFirstOpenTime(payTime);
            mem.setCurrentPeriodStart(validityStart);
            mem.setExpiryDate(validityEnd);
            mem.setCurrentPlanId(order.getPlanId());
            mem.setCreatedTime(payTime);
            mem.setUpdatedTime(payTime);
            membershipMapper.insert(mem);
        } else {
            if (mem.getFirstOpenTime() == null) {
                mem.setFirstOpenTime(payTime);
            }
            if (!stillActive) {
                mem.setCurrentPeriodStart(validityStart);
            }
            mem.setExpiryDate(validityEnd);
            mem.setCurrentPlanId(order.getPlanId());
            mem.setUpdatedTime(payTime);
            membershipMapper.updateById(mem);
        }
    }
}
