package com.tmerclub.cloud.product.manager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.tmerclub.cloud.api.product.bo.SkuStockLockBO;
import com.tmerclub.cloud.api.product.vo.SkuStockVO;
import com.tmerclub.cloud.common.cache.config.AofRedisConfig;
import com.tmerclub.cloud.common.cache.constant.CacheNames;
import com.tmerclub.cloud.common.cache.constant.LuaOperateEnum;
import com.tmerclub.cloud.common.cache.constant.ProductCacheNames;
import com.tmerclub.cloud.common.cache.util.RedisLuaUtil;
import com.tmerclub.cloud.common.cache.util.RedisUtil;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.product.vo.StockPointSkuVO;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.product.util.StockUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * 库存中心向外提供的业务功能的 真正实现
 *
 * @author lanhai
 */
@Component
public class StockManager {

    private static final Logger LOG = LoggerFactory.getLogger(StockManager.class);
    /**
     * 这里的redis用到AofRedis这个类的配置
     *
     * @see com.tmerclub.cloud.common.cache.config.AofRedisConfig
     */
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 默认的仓库id
     */
    public static final Long WAREHOUSE_ID = 0L;

    /**
     * 库存锁定转销量Lua
     */
    public static final byte[] ORDER_MAKER_USE_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/order/OrderMakerUseStock.lua");

    /**
     * 库存的扣减Lua
     */
    public static final byte[] ORDER_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/order/OrderStock.lua");

    /**
     * 库存锁定还原lua
     */
    public static final byte[] ORDER_UNLOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/order/OrderUnlockStock.lua");

    /**
     * 批量减少锁定库存，增加可售库存数量lua
     */
    public static final byte[] ORDER_BATCH_UNLOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/OrderBatchUnlockStock.lua");

    /**
     * 批量减少锁定库存，增加销量lua
     */
    public static final byte[] ORDER_BATCH_MAKER_USE_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/OrderBatchMakerUseStock.lua");

    /**
     * 库存锁定还原lua
     */
    public static final byte[] REFUND_INCREASE_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/RefundIncreaseStock.lua");

    /**
     * 采购订单锁定sku库存
     */
    public static final byte[] PURCHASE_LOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/purchase/PurchaseLockStock.lua");

    /**
     * 采购订单减少锁定sku库存
     */
    public static final byte[] PURCHASE_REDUCE_LOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/purchase/PurchaseReduceLuckStock.lua");

    /**
     * 调拨订单锁定sku库存
     */
    public static final byte[] ALLOT_LOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/allot/AllotLockStock.lua");

    /**
     * 调拨订单减少锁定sku库存
     */
    public static final byte[] ALLOT_REDUCE_LOCK_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/allot/AllotReduceLockStock.lua");

    /**
     * sku库存操作
     */
    public static final byte[] SKU_STOCK_BYTES = RedisLuaUtil.initByte("lua/product/SkuStock.lua");

    public StockManager(AofRedisConfig aofRedisConfig) {
        stringRedisTemplate = aofRedisConfig.getStringRedisTemplate();
    }


    /**
     * 销售订单下单的时候，锁定相应的sku的库存
     * <p>
     * 正常返回 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存,...
     * 返回值为空，代表已经执行一次了，不需要重复执行
     *
     * @return 返回解锁成功的库存信息  数据格式： orderId + skuId
     */
    public String makerUseStock(List<SkuStockLockBO> skuStockLocksParam) {
        // 正常返回 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存...
        // 返回值为空，代表已经执行一次了，不需要重复执行
        return executeBySkuStockLocks(skuStockLocksParam, ORDER_MAKER_USE_STOCK_BYTES);
    }


    /**
     * 销售订单下单的时候，锁定相应的sku的库存
     * <p>
     * 正常返回 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存,...
     * 异常返回 skuId
     */
    public String lockStock(List<SkuStockLockBO> skuStockLocksParam) {
        // 正常返回 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存,...
        // 异常返回 skuId
        return executeBySkuStockLocks(skuStockLocksParam, ORDER_STOCK_BYTES);
    }


    public Map<Long, Integer> unLockStock(List<SkuStockLockBO> skuStockLocks) {

        // 返回 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存,...
        String result = executeBySkuStockLocks(skuStockLocks, ORDER_UNLOCK_STOCK_BYTES);

        if (StrUtil.isBlank(result)) {
            return Collections.emptyMap();
        }


        // key: skuId value: 剩余库存
        Map<Long, Integer> stockMap = new HashMap<>();
        String[] unlockResults = result.split(StrUtil.COMMA);
        for (String unlockResult : unlockResults) {
            if (StrUtil.isBlank(unlockResult)) {
                continue;
            }

            // 订单id_仓库id_skuId_剩余库存
            String[] unlockResultInfo = unlockResult.split(String.valueOf(StrUtil.C_UNDERLINE));
            // 设置剩余库存,订单号
            stockMap.put(Long.valueOf(unlockResultInfo[2]), Integer.valueOf(unlockResultInfo[3]));
        }
        return stockMap;
    }


    public void increaseStock(List<SkuStockVO> skuStockLockList) {
        // skuId_退货数量,skuId_退货数量...
        List<String[]> list = new ArrayList<>();
        for (SkuStockVO skuStockLock : skuStockLockList) {
            String[] strArray = StockUtil.loadQueryArray(skuStockLock.getSkuId(), skuStockLock.getStock(), skuStockLock.getStockPointId());
            list.add(strArray);
        }
        // 返回 skuId_剩余库存_当前销量,skuId_剩余库存_当前销量...
        RedisLuaUtil.execute(stringRedisTemplate, REFUND_INCREASE_STOCK_BYTES, list);
    }

    /**
     * 改变sku库存
     *
     * @param dataList
     * @param checkStock 扣除库存时，是否需要检验sku的状态  -  下单时，检验sku是否为上架状态
     * @return
     */
    public String changeStock(List<String[]> dataList, Boolean checkStock) {
        List<String[]> stockDateList = new ArrayList<>(dataList.size());
        for (String[] strings : dataList) {
            String[] stockArray = Arrays.copyOf(strings, strings.length + 1);
            stockArray[strings.length] = BooleanUtil.isTrue(checkStock) ? "1" : "0";
            stockDateList.add(stockArray);
        }

        if (CollUtil.isEmpty(stockDateList)) {
            LOG.info("changeStock dataList is empty");
            return null;
        }
        // 传参 skuId_仓库id_stockNum,  skuId_仓库id_stockNum
        // 返回 skuId_剩余库存_当前销量,skuId_剩余库存_当前销量...
        return RedisLuaUtil.execute(stringRedisTemplate, SKU_STOCK_BYTES, stockDateList);
    }

    /**
     * 改变sku库存信息
     *
     * @param skuStocks      sku库存列表
     * @param luaOperateEnum 操作枚举 - 不是必传，如果skuStock.getOperateType()中有值就不用传此参数
     * @return
     */
    public String skuStock(List<SkuStockVO> skuStocks, LuaOperateEnum luaOperateEnum) {
        if (CollUtil.isEmpty(skuStocks)) {
            LOG.info("skuStock skuStocks is empty");
            return null;
        }

        List<String[]> dataList = new ArrayList<>(skuStocks.size());
        for (SkuStockVO skuStock : skuStocks) {
            // 插入操作类型
            if (Objects.nonNull(luaOperateEnum)) {
                skuStock.setOperateType(luaOperateEnum.value());
            }

            if (Objects.isNull(skuStock.getSkuId()) || Objects.isNull(skuStock.getOperateType())) {
                // 代码问题，skuId和操作类型必传，不能为null
                LOG.info("sku库存信息异常, SkuStock:{}", skuStock);
                throw new LuckException("sku库存信息异常");
            }
            // skuId_仓库id_库存变动数量_库存状态_库存操作
            dataList.add(StockUtil.loadQueryArray(skuStock.getSkuId(), skuStock.getStockPointId(),
                    skuStock.getStock(), skuStock.getStatus(), skuStock.getOperateType()));
        }

        // 传参 skuId_仓库id_库存变动数量_库存状态_库存操作, skuId_仓库id_库存变动数量_库存状态_库存操作
        // 返回 skuId_剩余库存_当前销量,skuId_剩余库存_当前销量...
        return executeLuk(dataList, SKU_STOCK_BYTES);

    }


    public String executeLuk(List<String[]> dataList, byte[] script) {
        if (CollUtil.isEmpty(dataList)) {
            LOG.info("executeLuk dataList is empty");
            return null;
        }
        try {
            return RedisLuaUtil.execute(stringRedisTemplate, script, dataList);
        } catch (Exception e) {
            LOG.error("executeLuk error， data: {}, message:{}", dataList, e.getMessage());
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }


    /**
     * 正常返回一个连接的字符串如下：
     * 订单id_仓库id_skuId_剩余库存,订单id_仓库id_skuId_剩余库存,...
     * 异常根据脚本内容进行返回
     */
    public String executeBySkuStockLocks(List<SkuStockLockBO> skuStockLocks, byte[] script) {
        // 订单id_仓库id_skuId_lockNum,  订单id_仓库id_skuId_lockNum...
        List<String[]> list = new ArrayList<>();
        for (SkuStockLockBO skuStockLock : skuStockLocks) {
            String[] strArray = StockUtil.loadQueryArray(skuStockLock.getOrderId(),
                    skuStockLock.getStockPointId(),
                    skuStockLock.getSkuId(),
                    skuStockLock.getStockCount(),
                    skuStockLock.getDefaultStockPointId());
            list.add(strArray);
        }
        return RedisLuaUtil.execute(stringRedisTemplate, script, list);
    }


    /*
      ===========================sku===================================
     */


    /**
     * 转换sku库存信息为对象
     *
     * @param skuId skuId列表
     */
    public SkuStockVO getSkuTotalStock(Long skuId) {
        Map<Long, SkuStockVO> stockVOMap = mapSkuTotalStock(Collections.singletonList(skuId));
        return stockVOMap.get(skuId);
    }


    /**
     * 转换sku库存信息为对象
     *
     * @param stockPointIdList skuId列表
     */
    public Map<String, SkuStockVO> skuStockMap(List<StockPointSkuVO> stockPointIdList) {
        return listCache(stockPointIdList);
    }

    /**
     * 获取sku总库存
     *
     * @param skuIds
     * @return
     */
    public Map<Long, SkuStockVO> mapSkuTotalStock(List<Long> skuIds) {
        List<String> keys = new ArrayList<>(skuIds.size());
        for (Long skuId : skuIds) {
            keys.add(CacheNames.SKU_SOCK + skuId);
        }
        Map<Long, SkuStockVO> stockMap = new HashMap<>(skuIds.size());
        List<String> results = this.mGet(keys);
        for (int i = 0; i < skuIds.size(); i++) {
            Long skuId = skuIds.get(i);
            SkuStockVO skuStockVO = StockUtil.getSkuStockVO(skuId, results.get(i));
            stockMap.put(skuId, skuStockVO);
        }
        return stockMap;
    }


    /**
     * 获取sku总库存
     *
     * @param skuStockList
     * @return
     */
    public Map<String, SkuStockVO> mapSkuStockBySkuStocks(List<SkuStockVO> skuStockList) {
        List<String> keys = new ArrayList<>(skuStockList.size());
        List<String> cacheNames = new ArrayList<>(skuStockList.size());
        for (SkuStockVO stockPointSkuVO : skuStockList) {
            String key = StockUtil.skuPointKey(stockPointSkuVO.getSkuId(), stockPointSkuVO.getStockPointId());
            if (keys.contains(key)) {
                continue;
            }
            keys.add(key);
            cacheNames.add(CacheNames.SKU_SOCK + key);
        }
        List<String> values = mGet(cacheNames);
        Map<String, SkuStockVO> stockVOMap = new HashMap<>(keys.size());

        for (int i = 0; i <keys.size(); i++) {
            String key = keys.get(i);
            stockVOMap.put(key, StockUtil.getSkuStockVO(key, values.get(i)));
        }
        return stockVOMap;
    }

    /**
     * 转换sku库存信息为对象
     *
     * @param stockPointIdList skuId列表
     */
    public List<SkuStockVO> convertSkuStock(List<StockPointSkuVO> stockPointIdList) {
        Map<String, SkuStockVO> map = listCache(stockPointIdList);
        if (MapUtil.isEmpty(map)) {
            return Collections.emptyList();
        }
        return map.values().stream().toList();
    }

    public String changeSkuStatus(List<SkuStockVO> skuStockList) {
        List<String[]> dataList = new ArrayList<>();
        for (SkuStockVO skuStockVO : skuStockList) {
            dataList.add(StockUtil.loadQueryArray(skuStockVO.getSkuId(), StockManager.WAREHOUSE_ID, skuStockVO.getStock(), skuStockVO.getStatus(), LuaOperateEnum.SKU_STATUS.value()));
        }
        return this.executeLuk(dataList, SKU_STOCK_BYTES);
    }

    /*
     * ============================ sku库存记录 ===========================
     */

    public Map<String, SkuStockVO> listCache(List<StockPointSkuVO> stockPointList) {
        if (CollUtil.isEmpty(stockPointList)) {
            return new HashMap<>(0);
        }
        List<String> keys = new ArrayList<>(stockPointList.size());
        List<String> cacheNames = new ArrayList<>(stockPointList.size());
        for (StockPointSkuVO stockPointSkuVO : stockPointList) {
            String key =  StockUtil.skuPointKey(stockPointSkuVO.getSkuId(), stockPointSkuVO.getStockPointId());
            keys.add(key);
            cacheNames.add(CacheNames.SKU_SOCK + key);
        }
        List<String> values = mGet(cacheNames);
        Map<String, SkuStockVO> stockVOMap = new HashMap<>(keys.size());

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            stockVOMap.put(key, StockUtil.getSkuStockVO(key, values.get(i)));
        }
        return stockVOMap;
    }

    /**
     * 仅用于初始化库存
     * @param skuIds
     * @param autofill
     * @return
     */
    public Map<Long, String> listInitCache(List<Long> skuIds, Boolean autofill) {
        Set<Long> skuIdSet = new HashSet<>(skuIds);
        List<String> keys = new ArrayList<>(skuIdSet.size());
        List<Long> ids = new ArrayList<>(skuIdSet.size());
        for (Long skuId : skuIdSet) {
            // 目前的仓库id都是0
            keys.add(ProductCacheNames.SKU_SOCK + StockManager.WAREHOUSE_ID + StrUtil.C_UNDERLINE + skuId);
            ids.add(skuId);
        }
        List<String> cacheList = mGet(keys);
        Map<Long, String> stockMap = new HashMap<>();
        int index = 0;
        for (Long id : ids) {
            String value = cacheList.get(index++);
            if (!autofill && StrUtil.isBlank(value)) {
                continue;
            }
            stockMap.put(id, value);
        }
        return stockMap;
    }

    // ================ mset  ================


    public List<String> mGet(Collection<String> keys) {
        for (String key : keys) {
            if (key.contains(StrUtil.SPACE)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        return stringRedisTemplate.opsForValue().multiGet(keys);
    }


    /**
     * list缓存放入并设置时间
     *
     * @param cacheMap 缓存的数据集合
     */
    public void mSet(Map<String, String> cacheMap) {
        if (MapUtil.isEmpty(cacheMap)) {
            return;
        }
        for (String key : cacheMap.keySet()) {
            if (key.contains(StrUtil.SPACE)) {
                throw new LuckException(ResponseEnum.EXCEPTION);
            }
        }
        // 没有设置失效时间
        stringRedisTemplate.opsForValue().multiSet(cacheMap);
    }


    /**
     * list缓存放入并设置时间
     *
     * @param keys 缓存的数据集合
     */
    public void mDel(List<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            return;
        }
        stringRedisTemplate.delete(keys);
    }

    public List<SkuStockVO> scanLockStockOrderLog() {
        List<String> list = RedisUtil.hScan(stringRedisTemplate, CacheNames.ORDER_SKU_LOCK);
        List<String> valueList = this.mGet(list);
        List<SkuStockVO> skuStockList = new ArrayList<>(list.size());
        for (String key : list) {
            String orderInfo = key.replace(CacheNames.ORDER_SKU_LOCK, "");
            String[] split = orderInfo.split(Constant.UNDERLINE);
            SkuStockVO skuStockVO = new SkuStockVO(Long.parseLong(split[2]));
            skuStockVO.setOrderId(Long.parseLong(split[0]));
            String stock = valueList.remove(0);
            skuStockVO.setStock(Integer.parseInt(stock));
            skuStockVO.setZoneNo(Integer.parseInt(split[1]));
            if (split.length > 3) {
                skuStockVO.setStatus(Integer.parseInt(split[3]));
            }
            skuStockList.add(skuStockVO);
        }
        return skuStockList;
    }

    /**
     * 根据库存点id获取对应商品库存
     * @param stockPointIds 库存点id
     * @return 库存映射map
     */
    public Map<String, SkuStockVO> mapSkuStockByStockPointIds(List<Long> stockPointIds) {
        if (CollectionUtils.isEmpty(stockPointIds)) {
            return new HashMap<>(0);
        }
        Map<String, SkuStockVO> stockMap = new HashMap<>(Constant.INITIAL_CAPACITY);
        Set<String> keys;
        SkuStockVO skuStockVO;
        for (Long stockPointId : stockPointIds) {
            // 找出匹配库存点的keys以及对应value
            keys = this.scanWithPatten(CacheNames.SKU_SOCK + "*" + Constant.UNDERLINE + stockPointId);
            if (CollectionUtils.isEmpty(keys)) {
                continue;
            }
            List<String> values = this.mGet(keys);
            // 将value转成skuStockVO，并存入Map
            int i=0;
            for (String key : keys) {
                key = key.substring(CacheNames.SKU_SOCK.length());
                skuStockVO = StockUtil.getSkuStockVO(key, values.get(i));
                stockMap.put(key, skuStockVO);
                i++;
            }
        }
        return stockMap;
    }


    public Map<String, String> scan(String key) {
        Map<String, String> stockMap = Collections.emptyMap();
        Cursor<String> cursor = null;
        try {
            ScanOptions options = ScanOptions.scanOptions().count(1000).match(key + "*").build();
            RedisSerializer<String> redisSerializer = (RedisSerializer<String>) stringRedisTemplate.getKeySerializer();
            cursor = stringRedisTemplate.executeWithStickyConnection(redisConnection ->
                    new ConvertingCursor<>(redisConnection.keyCommands().scan(options), redisSerializer::deserialize));
            if(Objects.isNull(cursor)){
                return stockMap;
            }
            List<String> keys = new ArrayList<>(Constant.INITIAL_CAPACITY);
            while (cursor.hasNext()) {
                keys.add(cursor.next());
            }
            if (keys.isEmpty()) {
                return stockMap;
            }
            stockMap = new HashMap<>(keys.size());
            List<String> values = stringRedisTemplate.opsForValue().multiGet(keys);
            for (int j = 0; j < keys.size(); j++) {
                String stock = values.get(j);
                if (StrUtil.isBlank(stock)) {
                    continue;
                }
                stockMap.put(keys.get(j), stock);
            }
        } finally {
            if(Objects.nonNull(cursor)) {
                cursor.close();
            }
        }
        return stockMap;
    }

    /**
     * 获取符合匹配条件的关键词（非阻塞）
     * @param patten 匹配条件
     * @return 关键词集合
     */
    public Set<String> scanWithPatten(String patten) {
        Set<String> keys = new HashSet<>(16);
        ScanOptions options = ScanOptions.scanOptions()
                .match(patten)
                .count(1000)
                .build();
        try (Cursor<String> cursor = stringRedisTemplate.scan(options)) {
            while (cursor.hasNext()) {
                keys.add(cursor.next());
            }
        }
        return keys;
    }
}
