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

import cn.dev33.satoken.stp.StpUtil;
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.extension.plugins.pagination.Page;
import com.snack.server.common.ResultCode;
import com.snack.server.constant.RedisKey;
import com.snack.server.exception.BusinessException;
import com.snack.server.module.product.entity.Product;
import com.snack.server.module.product.entity.ProductSku;
import com.snack.server.module.product.mapper.ProductMapper;
import com.snack.server.module.product.mapper.ProductSkuMapper;
import com.snack.server.module.seckill.dto.req.SeckillActivitySaveReq;
import com.snack.server.module.seckill.dto.req.SeckillBuyReq;
import com.snack.server.module.seckill.dto.req.SeckillProductAddReq;
import com.snack.server.module.seckill.entity.SeckillActivity;
import com.snack.server.module.seckill.entity.SeckillOrder;
import com.snack.server.module.seckill.entity.SeckillProduct;
import com.snack.server.module.seckill.enums.SeckillActivityStatusEnum;
import com.snack.server.module.seckill.enums.SeckillResultEnum;
import com.snack.server.module.seckill.mapper.SeckillActivityMapper;
import com.snack.server.module.seckill.mapper.SeckillOrderMapper;
import com.snack.server.module.seckill.mapper.SeckillProductMapper;
import com.snack.server.module.seckill.service.SeckillService;
import com.snack.server.module.seckill.vo.SeckillActivityVO;
import com.snack.server.module.seckill.vo.SeckillProductVO;
import com.snack.server.module.seckill.vo.SeckillResultVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 抢购业务实现（Redis 防超卖 + MySQL 兜底）
 *
 * 关键流程：
 *   1. 活动预热：管理端开启活动时，把 seckill_stock 同步到 Redis
 *   2. 用户抢购：Lua 脚本原子完成"限购校验 + 库存扣减"
 *   3. 写库兜底：seckill_order 唯一索引防一人多单
 *   4. 异步落库：本示例同步落库，生产应改 MQ
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SeckillServiceImpl implements SeckillService {

    private final SeckillActivityMapper activityMapper;
    private final SeckillProductMapper productMapper;
    private final SeckillOrderMapper orderMapper;
    private final ProductMapper productEntityMapper;
    private final ProductSkuMapper productSkuMapper;
    private final StringRedisTemplate redisTemplate;

    /** 抢购限购记录在 Redis 中的过期时间（活动结束后 1 天） */
    @Value("${snack.seckill.record-ttl-seconds:86400}")
    private long recordTtl;

    /** Lua 脚本（启动时加载一次） */
    private final DefaultRedisScript<List> seckillScript = new DefaultRedisScript<>();

    @jakarta.annotation.PostConstruct
    public void init() {
        seckillScript.setScriptSource(new ResourceScriptSource(
                new ClassPathResource("lua/seckill.lua")));
        seckillScript.setResultType(List.class);
    }

    // ==================== 用户端 ====================

    @Override
    public List<SeckillActivityVO> listActiveActivities() {
        // 查"未结束"的活动：end_time > now
        List<SeckillActivity> activities = activityMapper.selectList(
                new LambdaQueryWrapper<SeckillActivity>()
                        .gt(SeckillActivity::getEndTime, LocalDateTime.now().minusHours(1)) // 兜底展示刚结束 1 小时的
                        .orderByAsc(SeckillActivity::getStartTime)
        );
        if (CollUtil.isEmpty(activities)) return new ArrayList<SeckillActivityVO>();
        return activities.stream().map(a -> enrichActivityVO(a, false)).toList();
    }

    @Override
    public SeckillActivityVO getActivityDetail(Long activityId) {
        SeckillActivity activity = activityMapper.selectById(activityId);
        if (activity == null) {
            throw new BusinessException(1000, "活动不存在");
        }
        return enrichActivityVO(activity, true);
    }

    /**
     * 装配活动 VO（含状态计算、倒计时、商品）
     */
    private SeckillActivityVO enrichActivityVO(SeckillActivity activity, boolean withProducts) {
        SeckillActivityVO vo = new SeckillActivityVO();
        BeanUtil.copyProperties(activity, vo);
        // 动态状态
        Integer status = SeckillActivityStatusEnum.calcStatus(activity.getStartTime(), activity.getEndTime());
        vo.setStatus(status);
        vo.setStatusText(SeckillActivityStatusEnum.values()[status].getDesc());
        // 倒计时（开始前：距开始；进行中：距结束）
        LocalDateTime now = LocalDateTime.now();
        if (status == SeckillActivityStatusEnum.NOT_STARTED.getCode()) {
            vo.setCountdownSeconds(Duration.between(now, activity.getStartTime()).getSeconds());
        } else if (status == SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
            vo.setCountdownSeconds(Duration.between(now, activity.getEndTime()).getSeconds());
        } else {
            vo.setCountdownSeconds(0L);
        }
        // 商品
        if (withProducts) {
            List<SeckillProduct> products = productMapper.selectList(
                    new LambdaQueryWrapper<SeckillProduct>()
                            .eq(SeckillProduct::getActivityId, activity.getId())
                            .orderByAsc(SeckillProduct::getSort)
            );
            if (CollUtil.isNotEmpty(products)) {
                // 批量取 SPU / SKU
                Set<Long> spuIds = products.stream().map(SeckillProduct::getProductId).collect(Collectors.toSet());
                Set<Long> skuIds = products.stream().map(SeckillProduct::getSkuId).collect(Collectors.toSet());
                Map<Long, Product> productMap = spuIds.isEmpty() ? java.util.Collections.emptyMap()
                        : productEntityMapper.selectList(
                                new LambdaQueryWrapper<Product>().in(Product::getId, spuIds))
                        .stream()
                        .collect(Collectors.toMap(Product::getId, p -> p));
                Map<Long, ProductSku> skuMap = skuIds.isEmpty() ? java.util.Collections.emptyMap()
                        : productSkuMapper.selectList(
                                new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIds))
                        .stream()
                        .collect(Collectors.toMap(ProductSku::getId, s -> s));

                vo.setProducts(products.stream().map(sp -> {
                    SeckillProductVO pvo = new SeckillProductVO();
                    BeanUtil.copyProperties(sp, pvo);
                    Product p = productMap.get(sp.getProductId());
                    ProductSku s = skuMap.get(sp.getSkuId());
                    if (p != null) {
                        pvo.setProductName(p.getName());
                        pvo.setProductImage(p.getMainImage());
                    }
                    if (s != null) {
                        pvo.setSkuName(s.getSkuName());
                    }
                    // 实时剩余库存：优先从 Redis 取，Redis 没有再查 MySQL
                    Integer redisRemain = getRedisStock(sp.getActivityId(), sp.getSkuId());
                    Integer remain = redisRemain != null ? redisRemain : sp.getRemainStock();
                    pvo.setRemainStock(remain);
                    // 已售百分比
                    int total = sp.getSeckillStock();
                    int sold = total - Math.max(remain, 0);
                    pvo.setSoldPercent(total == 0 ? 0 : (int) Math.round(sold * 100.0 / total));
                    // 折扣率
                    if (sp.getOriginPrice() != null && sp.getOriginPrice().compareTo(BigDecimal.ZERO) > 0) {
                        pvo.setDiscount(sp.getSeckillPrice()
                                .multiply(BigDecimal.TEN)
                                .divide(sp.getOriginPrice(), 1, RoundingMode.HALF_UP));
                    }
                    return pvo;
                }).toList());
            } else {
                vo.setProducts(new ArrayList<SeckillProductVO>());
            }
        }
        return vo;
    }

    @Override
    public String seckillBuy(SeckillBuyReq req) {
        Long userId = StpUtil.getLoginIdAsLong();

        // 1. 校验活动
        SeckillActivity activity = activityMapper.selectById(req.getActivityId());
        if (activity == null) {
            throw new BusinessException(ResultCode.SECKILL_NOT_STARTED, "活动不存在");
        }
        Integer nowStatus = SeckillActivityStatusEnum.calcStatus(activity.getStartTime(), activity.getEndTime());
        if (nowStatus != SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
            throw new BusinessException(Objects.equals(nowStatus, SeckillActivityStatusEnum.NOT_STARTED.getCode())
                    ? ResultCode.SECKILL_NOT_STARTED : ResultCode.SECKILL_ENDED);
        }

        // 2. 校验商品
        SeckillProduct sp = productMapper.selectOne(
                new LambdaQueryWrapper<SeckillProduct>()
                        .eq(SeckillProduct::getActivityId, req.getActivityId())
                        .eq(SeckillProduct::getSkuId, req.getSkuId())
        );
        if (sp == null) {
            throw new BusinessException(1000, "活动商品不存在");
        }

        int qty = req.getQuantity() == null ? 1 : req.getQuantity();
        if (qty <= 0 || qty > sp.getPerLimit()) {
            throw new BusinessException(ResultCode.SECKILL_LIMIT_EXCEEDED,
                    "每人限购 " + sp.getPerLimit() + " 件");
        }

        // 3. 预热检查（如果 Redis 没库存，先 fallback 到 MySQL 库存）
        String stockKey = String.format(RedisKey.SECKILL_STOCK, req.getActivityId(), req.getSkuId());
        String userKey = String.format(RedisKey.SECKILL_USER_RECORD, userId, req.getActivityId(), req.getSkuId());

        if (Boolean.FALSE.equals(redisTemplate.hasKey(stockKey))) {
            // Redis 未预热 → 用 MySQL 库存兜底
            log.warn("活动 {} SKU {} 未预热到 Redis，使用 MySQL 库存兜底", req.getActivityId(), req.getSkuId());
            if (sp.getRemainStock() == null || sp.getRemainStock() < qty) {
                throw new BusinessException(ResultCode.SECKILL_STOCK_EMPTY);
            }
        }

        // 4. 执行 Lua 脚本（核心防超卖）
        List<Long> result;
        try {
            result = redisTemplate.execute(
                    seckillScript,
                    Arrays.asList(stockKey, userKey),
                    String.valueOf(sp.getPerLimit()),
                    String.valueOf(qty),
                    String.valueOf(recordTtl)
            );
        } catch (Exception e) {
            log.error("Lua 脚本执行异常", e);
            throw new BusinessException(1000, "系统繁忙，请稍后再试");
        }

        if (result == null || result.isEmpty()) {
            throw new BusinessException(1000, "抢购失败");
        }
        long code = result.get(0);
        if (code == -2L) {
            throw new BusinessException(ResultCode.SECKILL_LIMIT_EXCEEDED);
        }
        if (code == -3L) {
            throw new BusinessException(ResultCode.SECKILL_STOCK_EMPTY);
        }
        if (code != 0L) {
            throw new BusinessException(1000, "抢购失败 code=" + code);
        }

        // 5. 抢购成功 → 落库（业务简化版同步落库，生产应 MQ 异步）
        try {
            return afterSeckillSuccess(userId, sp, qty);
        } catch (Exception e) {
            // MySQL 落库失败 → 回滚 Redis（释放库存 + 释放已购标记）
            log.error("抢购落库失败，回滚 Redis", e);
            rollbackRedis(stockKey, userKey, qty);
            throw e;
        }
    }

    /**
     * 抢购成功后的落库
     */
    @Transactional(rollbackFor = Exception.class)
    protected String afterSeckillSuccess(Long userId, SeckillProduct sp, int qty) {
        // 1. 写 seckill_order（唯一索引兜底一人一单）
        SeckillOrder order = new SeckillOrder();
        order.setUserId(userId);
        order.setActivityId(sp.getActivityId());
        order.setProductId(sp.getProductId());
        order.setSkuId(sp.getSkuId());
        order.setQuantity(qty);
        order.setSeckillPrice(sp.getSeckillPrice());
        order.setStatus(0); // 待支付
        try {
            orderMapper.insert(order);
        } catch (org.springframework.dao.DuplicateKeyException dup) {
            // 唯一索引兜底命中（说明 Redis 已被穿透）
            log.warn("用户 {} 在活动 {} SKU {} 已存在抢购记录", userId, sp.getActivityId(), sp.getSkuId());
            throw new BusinessException(ResultCode.SECKILL_LIMIT_EXCEEDED, "您已购买过该商品");
        }

        // 2. 扣减 MySQL 兜底库存
        productMapper.decrRemainStock(sp.getId(), qty);

        // 3. 累加 MySQL 销售数
        // （简化：直接更新 sales 字段）
        sp.setSales((sp.getSales() == null ? 0 : sp.getSales()) + qty);
        productMapper.updateById(sp);

        // 4. 生成"排队号"（这里直接用 seckill_order.id 作为订单号）
        String orderNo = "SK" + order.getId();
        // 写抢购结果到 Redis（用于轮询接口）
        redisTemplate.opsForValue().set(
                String.format(RedisKey.SECKILL_RESULT, orderNo),
                SeckillResultEnum.SUCCESS.getCode().toString(),
                Duration.ofMinutes(30)
        );

        log.info("用户 {} 抢购成功 seckillOrderId={} activityId={} skuId={} qty={}",
                userId, order.getId(), sp.getActivityId(), sp.getSkuId(), qty);
        return orderNo;
    }

    /**
     * 回滚 Redis
     */
    private void rollbackRedis(String stockKey, String userKey, int qty) {
        try {
            redisTemplate.opsForValue().increment(stockKey, qty);
            redisTemplate.opsForValue().decrement(userKey, qty);
        } catch (Exception e) {
            log.error("Redis 回滚失败，请人工介入：stockKey={} userKey={}", stockKey, userKey, e);
        }
    }

    @Override
    public SeckillResultVO queryResult(String orderNo) {
        String key = String.format(RedisKey.SECKILL_RESULT, orderNo);
        String status = redisTemplate.opsForValue().get(key);
        SeckillResultVO vo = new SeckillResultVO();
        vo.setOrderNo(orderNo);
        if (status == null) {
            vo.setStatus(SeckillResultEnum.PENDING.getCode());
            vo.setStatusText("排队中，请稍后查询");
            return vo;
        }
        int code = Integer.parseInt(status);
        vo.setStatus(code);
        vo.setStatusText(SeckillResultEnum.values()[code].getDesc());

        if (code == SeckillResultEnum.SUCCESS.getCode()) {
            // 查主订单 ID
            Long seckillId = parseSeckillIdFromOrderNo(orderNo);
            if (seckillId != null) {
                SeckillOrder so = orderMapper.selectById(seckillId);
                if (so != null) {
                    vo.setMainOrderId(so.getOrderId());
                }
            }
        }
        return vo;
    }

    private Long parseSeckillIdFromOrderNo(String orderNo) {
        if (orderNo == null || !orderNo.startsWith("SK")) return null;
        try {
            return Long.parseLong(orderNo.substring(2));
        } catch (NumberFormatException e) {
            return null;
        }
    }

    /**
     * 读 Redis 当前库存
     */
    private Integer getRedisStock(Long activityId, Long skuId) {
        String key = String.format(RedisKey.SECKILL_STOCK, activityId, skuId);
        String val = redisTemplate.opsForValue().get(key);
        return val == null ? null : Integer.parseInt(val);
    }

    // ==================== 管理端 ====================

    @Override
    public Page<SeckillActivity> pageActivities(Long current, Long size, Integer status) {
        Page<SeckillActivity> page = new Page<>(current, size);
        LambdaQueryWrapper<SeckillActivity> wrapper = new LambdaQueryWrapper<SeckillActivity>()
                .eq(status != null, SeckillActivity::getStatus, status)
                .orderByDesc(SeckillActivity::getId);
        return activityMapper.selectPage(page, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createActivity(SeckillActivitySaveReq req) {
        if (req.getEndTime().isBefore(req.getStartTime())) {
            throw new BusinessException(1000, "结束时间不能早于开始时间");
        }
        SeckillActivity activity = new SeckillActivity();
        BeanUtil.copyProperties(req, activity, "id");
        activity.setStatus(0); // 未开始
        activityMapper.insert(activity);
        log.info("创建抢购活动 id={} name={}", activity.getId(), activity.getName());
        return activity.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateActivity(SeckillActivitySaveReq req) {
        if (req.getId() == null) {
            throw new BusinessException(1000, "ID 不能为空");
        }
        SeckillActivity existing = activityMapper.selectById(req.getId());
        if (existing == null) {
            throw new BusinessException(1000, "活动不存在");
        }
        if (existing.getStatus() != null
                && existing.getStatus() == SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
            throw new BusinessException(1000, "进行中的活动不能修改");
        }
        SeckillActivity activity = new SeckillActivity();
        BeanUtil.copyProperties(req, activity);
        activityMapper.updateById(activity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteActivity(Long id) {
        SeckillActivity existing = activityMapper.selectById(id);
        if (existing == null) {
            throw new BusinessException(1000, "活动不存在");
        }
        // 删除活动商品
        productMapper.delete(
                new LambdaQueryWrapper<SeckillProduct>().eq(SeckillProduct::getActivityId, id)
        );
        // 清理 Redis 库存
        List<SeckillProduct> products = productMapper.selectList(
                new LambdaQueryWrapper<SeckillProduct>().eq(SeckillProduct::getActivityId, id)
        );
        for (SeckillProduct sp : products) {
            redisTemplate.delete(String.format(RedisKey.SECKILL_STOCK, id, sp.getSkuId()));
        }
        redisTemplate.delete(String.format(RedisKey.SECKILL_WARMUP, id));
        activityMapper.deleteById(id);
        log.info("删除活动 id={}", id);
    }

    @Override
    public void forceEndActivity(Long id) {
        int affected = activityMapper.forceEnd(id);
        if (affected == 0) {
            throw new BusinessException(1000, "活动不在进行中");
        }
        // 清理 Redis
        List<SeckillProduct> products = productMapper.selectList(
                new LambdaQueryWrapper<SeckillProduct>().eq(SeckillProduct::getActivityId, id)
        );
        for (SeckillProduct sp : products) {
            redisTemplate.delete(String.format(RedisKey.SECKILL_STOCK, id, sp.getSkuId()));
        }
        redisTemplate.delete(String.format(RedisKey.SECKILL_WARMUP, id));
        log.info("强制结束活动 id={}", id);
    }

    @Override
    public void warmupActivity(Long id) {
        SeckillActivity activity = activityMapper.selectById(id);
        if (activity == null) {
            throw new BusinessException(1000, "活动不存在");
        }
        // 同步所有活动商品的库存到 Redis
        List<SeckillProduct> products = productMapper.selectList(
                new LambdaQueryWrapper<SeckillProduct>().eq(SeckillProduct::getActivityId, id)
        );
        for (SeckillProduct sp : products) {
            String key = String.format(RedisKey.SECKILL_STOCK, id, sp.getSkuId());
            redisTemplate.opsForValue().set(key, sp.getRemainStock().toString());
        }
        // 标记已预热
        redisTemplate.opsForValue().set(String.format(RedisKey.SECKILL_WARMUP, id), "1");
        log.info("活动 {} 预热完成，商品数 {}", id, products.size());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addProduct(SeckillProductAddReq req) {
        // 校验活动
        SeckillActivity activity = activityMapper.selectById(req.getActivityId());
        if (activity == null) {
            throw new BusinessException(1000, "活动不存在");
        }
        if (activity.getStatus() != null
                && activity.getStatus() == SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
            throw new BusinessException(1000, "进行中的活动不能添加商品");
        }
        // 校验 SKU
        ProductSku sku = productSkuMapper.selectById(req.getSkuId());
        if (sku == null) {
            throw new BusinessException(1000, "SKU 不存在");
        }
        // 校验：同一活动同一 SKU 不能重复
        Long dup = productMapper.selectCount(
                new LambdaQueryWrapper<SeckillProduct>()
                        .eq(SeckillProduct::getActivityId, req.getActivityId())
                        .eq(SeckillProduct::getSkuId, req.getSkuId())
        );
        if (dup > 0) {
            throw new BusinessException(1000, "该 SKU 已在活动中");
        }

        SeckillProduct sp = new SeckillProduct();
        sp.setActivityId(req.getActivityId());
        sp.setProductId(sku.getProductId());
        sp.setSkuId(req.getSkuId());
        sp.setSeckillPrice(req.getSeckillPrice());
        sp.setOriginPrice(sku.getPrice());
        sp.setSeckillStock(req.getSeckillStock());
        sp.setRemainStock(req.getSeckillStock());
        sp.setPerLimit(req.getPerLimit() == null ? 1 : req.getPerLimit());
        sp.setSales(0);
        sp.setSort(req.getSort() == null ? 0 : req.getSort());
        productMapper.insert(sp);
        log.info("活动 {} 添加商品 seckillProductId={} skuId={}", req.getActivityId(), sp.getId(), req.getSkuId());
        return sp.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeProduct(Long seckillProductId) {
        SeckillProduct sp = productMapper.selectById(seckillProductId);
        if (sp == null) {
            throw new BusinessException(1000, "活动商品不存在");
        }
        // 检查活动是否已开始
        SeckillActivity activity = activityMapper.selectById(sp.getActivityId());
        if (activity != null
                && activity.getStatus() != null
                && activity.getStatus() == SeckillActivityStatusEnum.IN_PROGRESS.getCode()) {
            throw new BusinessException(1000, "进行中的活动不能移除商品");
        }
        productMapper.deleteById(seckillProductId);
        // 清理 Redis
        redisTemplate.delete(String.format(RedisKey.SECKILL_STOCK, sp.getActivityId(), sp.getSkuId()));
        log.info("移除活动商品 seckillProductId={}", seckillProductId);
    }
}
