package com.czg.service.product.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.czg.constant.CacheConstant;
import com.czg.enums.DeleteEnum;
import com.czg.enums.YesNoEnum;
import com.czg.exception.CzgException;
import com.czg.product.dto.ProdConsRelationDTO;
import com.czg.product.dto.ProdSkuDTO;
import com.czg.product.dto.ProductDTO;
import com.czg.product.dto.ShopProdCategoryDTO;
import com.czg.product.entity.*;
import com.czg.product.enums.*;
import com.czg.product.param.*;
import com.czg.product.service.*;
import com.czg.product.vo.ProductStatisticsVo;
import com.czg.sa.StpKit;
import com.czg.service.RedisService;
import com.czg.service.product.mapper.*;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.DbChain;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;

import static com.czg.constant.CacheConstant.ADMIN_CLIENT_PRODUCT_LIST;
import static com.czg.product.entity.table.ProductTableDef.PRODUCT;
import static com.czg.product.entity.table.ShopProdCategoryTableDef.SHOP_PROD_CATEGORY;
import static com.czg.product.entity.table.ShopProdSpecTableDef.SHOP_PROD_SPEC;
import static com.czg.product.entity.table.ShopProdUnitTableDef.SHOP_PROD_UNIT;

/**
 * 商品
 *
 * @author Tankaikai tankaikai@aliyun.com
 * @since 1.0 2025-02-16
 */
@Slf4j
@AllArgsConstructor
@DubboService
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {

    private final ProdSkuMapper prodSkuMapper;
    private final ProdConsRelationMapper prodConsRelationMapper;
    private final ConsInfoMapper consInfoMapper;
    private final ConsStockFlowMapper consStockFlowMapper;
    private final ProductStockFlowMapper productStockFlowMapper;
    private final ProductStockFlowService productStockFlowService;
    private final ConsStockFlowService consStockFlowService;
    private final SensitiveOperationService sensitiveOperationService;
    private final ProdGroupRelationMapper prodGroupRelationMapper;
    private final RedisService redisService;
    @Resource
    @Lazy
    private ShopProdCategoryService shopProdCategoryService;

    private QueryWrapper buildQueryWrapper(ProductDTO param) {
        QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
        if (ObjUtil.isNotNull(param.getId())) {
            queryWrapper.eq(Product::getId, param.getId());
        }
        if (StrUtil.isNotEmpty(param.getName())) {
            queryWrapper.like(Product::getName, param.getName());
        }
        if (ObjUtil.isNotNull(param.getCategoryId())) {
            queryWrapper.eq(Product::getCategoryId, param.getCategoryId());
        }
        if (ObjUtil.isNotNull(param.getSpecId())) {
            queryWrapper.eq(Product::getSpecId, param.getSpecId());
        }
        if (StrUtil.isNotEmpty(param.getType())) {
            queryWrapper.eq(Product::getType, param.getType());
        }
        if (StrUtil.isNotEmpty(param.getCreateBeginTime())) {
            queryWrapper.ge(Product::getCreateTime, param.getCreateBeginTime());
        }
        if (StrUtil.isNotEmpty(param.getCreateEndTime())) {
            queryWrapper.le(Product::getCreateTime, param.getCreateEndTime());
        }
        if (StrUtil.isNotEmpty(param.getStatus())) {
            if (ProductSaleStatusEnum.SOLD_OUT.getValue().equals(param.getStatus())) {
                queryWrapper.eq(Product::getIsSoldStock, YesNoEnum.YES.value());
            } else if (ProductSaleStatusEnum.ON_SALE.getValue().equals(param.getStatus())) {
                queryWrapper.eq(Product::getIsSoldStock, YesNoEnum.NO.value());
                queryWrapper.eq(Product::getIsSale, YesNoEnum.YES.value());
            } else if (ProductSaleStatusEnum.OFF_SALE.getValue().equals(param.getStatus())) {
                queryWrapper.eq(Product::getIsSale, YesNoEnum.NO.value());
            }
        }
        if (param.getShopId() == null) {
            Long shopId = StpKit.USER.getShopId(0L);
            param.setShopId(shopId);
        }
        queryWrapper.eq(Product::getShopId, param.getShopId());
        queryWrapper.eq(Product::getIsDel, DeleteEnum.NORMAL.value());
        queryWrapper.orderBy(Product::getIsSoldStock, true);
        queryWrapper.orderBy(Product::getIsSale, false);
        queryWrapper.orderBy(Product::getSort, false);
        queryWrapper.orderBy(Product::getId, false);
        return queryWrapper;
    }

    private QueryWrapper buildFullQueryWrapper(ProductDTO param) {
        QueryWrapper queryWrapper = buildQueryWrapper(param);
        queryWrapper.select(PRODUCT.DEFAULT_COLUMNS)
                .select(SHOP_PROD_UNIT.NAME.as(ProductDTO::getUnitName))
                .select(SHOP_PROD_CATEGORY.NAME.as(ProductDTO::getCategoryName))
                .select(SHOP_PROD_SPEC.NAME.as(ProductDTO::getSpecName), SHOP_PROD_SPEC.FULL_NAME.as(ProductDTO::getSpecFullName))
                .from(PRODUCT)
                .leftJoin(SHOP_PROD_UNIT).on(SHOP_PROD_UNIT.ID.eq(PRODUCT.UNIT_ID))
                .leftJoin(SHOP_PROD_CATEGORY).on(SHOP_PROD_CATEGORY.ID.eq(PRODUCT.CATEGORY_ID))
                .leftJoin(SHOP_PROD_SPEC).on(SHOP_PROD_SPEC.ID.eq(PRODUCT.SPEC_ID));
        return queryWrapper;
    }

    private void buildProductExtInfo(List<ProductDTO> records) {
        records.forEach(record -> {
            record.setIsSaleTime(calcIsSaleTime(record.getDays(), record.getStartTime(), record.getEndTime()));
            List<ProdSkuDTO> skuList = prodSkuMapper.selectListByQueryAs(query().eq(ProdSku::getProductId, record.getId()).eq(ProdSku::getIsDel, DeleteEnum.NORMAL.value()), ProdSkuDTO.class);
            if (CollUtil.isNotEmpty(skuList)) {
                Optional<BigDecimal> lowPriceIsPresent = skuList.stream().map(obj -> NumberUtil.nullToZero(obj.getSalePrice())).min(BigDecimal::compareTo);
                lowPriceIsPresent.ifPresent(record::setLowPrice);

                Optional<BigDecimal> lowMemberPriceIsPresent = skuList.stream().map(obj -> NumberUtil.nullToZero(obj.getMemberPrice())).min(BigDecimal::compareTo);
                lowMemberPriceIsPresent.ifPresent(record::setLowMemberPrice);
            }
            record.setSkuList(skuList);
            List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(record.getId());
            record.setConsList(consList);
            if (CollUtil.isNotEmpty(consList)) {
                List<Long> consIds = consList.stream().map(ProdConsRelationDTO::getConsInfoId).distinct().toList();
                String consName = consInfoMapper.selectOneByQueryAs(query().select("GROUP_CONCAT(con_name SEPARATOR '、')").in(ConsInfo::getId, consIds), String.class);
                record.setConsName(consName);
            }
        });
    }

    @Override
    public Page<ProductDTO> getProductPage(ProductDTO param) {
        QueryWrapper queryWrapper = buildFullQueryWrapper(param);
        Page<ProductDTO> page = super.pageAs(PageUtil.buildPage(), queryWrapper, ProductDTO.class);
        List<ProductDTO> records = page.getRecords();
        buildProductExtInfo(records);
        return page;
    }

    @Override
    public List<ProductDTO> getProductList(ProductDTO param) {
        QueryWrapper queryWrapper = buildFullQueryWrapper(param);
        //queryWrapper.eq(Product::getIsSale, YesNoEnum.YES.value());
        List<ProductDTO> records = super.listAs(queryWrapper, ProductDTO.class);
        buildProductExtInfo(records);
        return records;
    }

    @Override
    public List<ProductDTO> getProductCacheList(ProductDTO param) {
        Long shopId = param.getShopId();
        initProductCache(shopId);
        String prefix = ADMIN_CLIENT_PRODUCT_LIST + "::" + shopId + "::";
        List<ProductDTO> list;
        if (param.getCategoryId() == null) {
            list = new ArrayList<>();
            Set<String> keys = redisService.rightLikeKey(prefix);
            for (String key : keys) {
                List<ProductDTO> prodList = redisService.getJsonToBeanList(key, ProductDTO.class);
                list.addAll(prodList);
            }
        } else {
            String key = prefix + param.getCategoryId();
            list = redisService.getJsonToBeanList(key, ProductDTO.class);
        }
        if (StrUtil.isNotEmpty(param.getName())) {
            list = list.stream().filter(obj -> StrUtil.contains(obj.getName(), param.getName())).toList();
        }
        // 重新进行排序
        list = list.stream()
                .sorted(Comparator.comparingInt(ProductDTO::getIsSoldStock))
                .sorted(Comparator.comparingInt(ProductDTO::getIsSale).reversed())
                .sorted(Comparator.comparingInt(ProductDTO::getSort).reversed())
                .sorted(Comparator.comparingLong(ProductDTO::getId).reversed())
                .toList();
        return list;
    }

    /**
     * 初始化商品缓存数据
     */
    private void initProductCache(Long shopId) {
        ProductDTO param = new ProductDTO();
        param.setShopId(shopId);
        List<ProductDTO> productList = getProductList(param);
        Map<Long, List<ProductDTO>> categoryMap = productList.stream().filter(item -> item.getCategoryId() != null).collect(Collectors.groupingBy(ProductDTO::getCategoryId));
        ShopProdCategoryDTO dto = new ShopProdCategoryDTO();
        dto.setShopId(shopId);
        List<ShopProdCategoryDTO> categoryList = shopProdCategoryService.getShopProdCategoryList(dto);
        String prefix = ADMIN_CLIENT_PRODUCT_LIST + "::" + shopId + "::";
        for (ShopProdCategoryDTO category : categoryList) {
            String key = prefix + category.getId();
            boolean b = redisService.hasKey(key);
            if (!b) {
                List<ProductDTO> list = categoryMap.get(category.getId());
                if (CollUtil.isNotEmpty(list)) {
                    redisService.setJsonStr(key, list);
                } else {
                    List<ProductDTO> empty = new ArrayList<>();
                    redisService.setJsonStr(key, empty);
                }
            }
        }
    }

    /**
     * 清除某个商品分类的缓存
     */
    @Override
    public void clearProductCache(Long... categoryIds) {
        Long shopId = StpKit.USER.getShopId(0L);
        String prefix = ADMIN_CLIENT_PRODUCT_LIST + "::" + shopId + "::";
        for (Long categoryId : categoryIds) {
            redisService.del(prefix + categoryId);
        }
    }

    /**
     * 系统启动会一次性加载商品缓存数据，防止首次访问时加载缓慢的问题
     */
    @PostConstruct
    public void initProductListCache() {
        log.info("系统启动后初始化商品列表缓存，开始...");
        List<Long> shopIdList = DbChain.table("tb_shop_info").select("id").objListAs(Long.class);
        for (Long shopId : shopIdList) {
            log.info("商品列表缓存>>当前店铺：{}", shopId);
            ProductDTO dto = new ProductDTO();
            dto.setShopId(shopId);
            getProductCacheList(dto);
            log.info("商品列表缓存>>当前店铺：{}，初始化结束", shopId);
        }
        log.info("系统启动后初始化商品列表缓存，结束。");
    }

    /**
     * 计算是否在可售时间内
     *
     * @param days      星期几，例如 "Monday,Tuesday"
     * @param startTime 起售时间
     * @param endTime   停售时间
     * @return 是否可售时间 1-是，0-否
     */
    private Integer calcIsSaleTime(String days, LocalTime startTime, LocalTime endTime) {
        if (StrUtil.isBlank(days) || ObjUtil.isNull(startTime) || ObjUtil.isNull(endTime)) {
            return YesNoEnum.NO.value();
        }
        String today = getWeekDayEnName();
        List<String> dayList = StrUtil.split(days, ",");
        LocalTime now = LocalTime.now().withNano(0);
        if (CollUtil.contains(dayList, today) && now.isAfter(startTime) && now.isBefore(endTime)) {
            return YesNoEnum.YES.value();
        }
        return YesNoEnum.NO.value();
    }

    /**
     * 获取当前日期是星期几的英文名称
     *
     * @return 星期几的英文名称，例如 "Monday" 或 "Friday"
     */
    private String getWeekDayEnName() {
        // 获取当前日期
        LocalDate currentDate = LocalDate.now();
        // 获取当前日期是星期几，返回一个 DayOfWeek 枚举类型的实例
        DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
        return dayOfWeek.getDisplayName(TextStyle.FULL, Locale.ENGLISH);
    }

    @Override
    public ProductDTO getProductById(Long id) {
        ProductDTO param = new ProductDTO();
        param.setId(id);
        QueryWrapper queryWrapper = buildFullQueryWrapper(param);
        ProductDTO dto = super.getOneAs(queryWrapper, ProductDTO.class);
        if (dto == null) {
            return null;
        }
        List<ProdSkuDTO> skuList = prodSkuMapper.selectListByQueryAs(query().eq(ProdSku::getProductId, id).eq(ProdSku::getIsDel, DeleteEnum.NORMAL.value()), ProdSkuDTO.class);
        dto.setSkuList(skuList);
        List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(dto.getId());
        dto.setConsList(consList);
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {CacheConstant.USER_CLIENT_HOTS_PRODUCT, CacheConstant.USER_CLIENT_GROUPS_PRODUCT}, key = "#dto.shopId", allEntries = true)
    public void addProduct(ProductDTO dto) {
        Long shopId = StpKit.USER.getShopId(0L);
        boolean exists = super.exists(query().eq(Product::getName, dto.getName()).eq(Product::getShopId, shopId).eq(Product::getIsDel, DeleteEnum.NORMAL.value()));
        if (exists) {
            throw new CzgException("名称已存在");
        }

        Product entity = BeanUtil.copyProperties(dto, Product.class);
        entity.setImages(JSON.toJSONString(dto.getImages(), JSONWriter.Feature.WriteMapNullValue));
        entity.setGroupSnap("[]");
        //套餐
        if (ProductTypeEnum.PACKAGE.value().equals(dto.getType())) {
            //套餐内容
            if (CollUtil.isNotEmpty(dto.getProGroupVo())) {
                String groupSnap = JSON.toJSONString(dto.getProGroupVo(), JSONWriter.Feature.WriteMapNullValue);
                entity.setGroupSnap(groupSnap);
            }
        }
        entity.setIsDel(DeleteEnum.NORMAL.value());
        entity.setShopId(shopId);
        super.save(entity);
        dto.setId(entity.getId());
        // 清除商品分类列表缓存
        clearProductCache(entity.getCategoryId());
        List<ProdSkuDTO> skuList = dto.getSkuList();
        if (CollUtil.isNotEmpty(skuList)) {
            List<ProdSku> prodSkuList = new ArrayList<>();
            for (ProdSkuDTO prodSkuDTO : skuList) {
                ProdSku prodSku = BeanUtil.copyProperties(prodSkuDTO, ProdSku.class);
                prodSku.setShopId(entity.getShopId());
                prodSku.setProductId(entity.getId());
                prodSku.setRealSalesNumber(BigDecimal.ZERO);
                prodSku.setIsPauseSale(YesNoEnum.NO.value());
                prodSku.setIsGrounding(YesNoEnum.YES.value());
                prodSku.setIsDel(DeleteEnum.NORMAL.value());
                prodSkuList.add(prodSku);
            }
            prodSkuMapper.insertBatch(prodSkuList);
        }
        if (entity.getIsStock() == YesNoEnum.NO.value()) {
            return;
        }
        // 记录商品库存流水
        ProductStockFlow flow = new ProductStockFlow();
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        flow.setCreateUserId(createUserId);
        flow.setCreateUserName(createUserName);
        flow.setShopId(shopId);
        flow.setProductId(entity.getId());
        flow.setProductName(entity.getName());
        flow.setBeforeNumber(BigDecimal.ZERO);
        flow.setInOutNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
        flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
        flow.setInOutType(InOutTypeEnum.IN.value());
        flow.setInOutItem(InOutItemEnum.WIN_IN.value());
        productStockFlowService.saveFlow(flow);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {CacheConstant.USER_CLIENT_HOTS_PRODUCT, CacheConstant.USER_CLIENT_GROUPS_PRODUCT}, key = "#dto.shopId", allEntries = true)
    public void updateProduct(ProductDTO dto) {
        Long shopId = StpKit.USER.getShopId(0L);
        dto.setShopId(shopId);
        boolean exists = super.exists(query().eq(Product::getName, dto.getName()).eq(Product::getShopId, shopId).eq(Product::getIsDel, DeleteEnum.NORMAL.value()).ne(Product::getId, dto.getId()));
        if (exists) {
            throw new CzgException("名称已存在");
        }
        Product old = super.getById(dto.getId());
        Product entity = BeanUtil.copyProperties(dto, Product.class);
        if ("weight".equals(entity.getType()) && (entity.getWeight() == null || entity.getWeight().compareTo(BigDecimal.ZERO) <= 0)) {
            throw new CzgException("称重商品重量必须大于0");
        }
        entity.setImages(JSON.toJSONString(dto.getImages(), JSONWriter.Feature.WriteMapNullValue));
        entity.setGroupSnap("[]");
        //套餐
        if (ProductTypeEnum.PACKAGE.value().equals(dto.getType())) {
            //套餐内容
            if (CollUtil.isNotEmpty(dto.getProGroupVo())) {
                entity.setGroupSnap(JSON.toJSONString(dto.getProGroupVo(), JSONWriter.Feature.WriteMapNullValue));
            }
        }
        entity.setIsDel(DeleteEnum.NORMAL.value());
        entity.setShopId(shopId);
        if (!ProductTypeEnum.SKU.value().equals(entity.getType())) {
            UpdateChain.of(Product.class).set(Product::getSpecId, null).eq(Product::getId, dto.getId()).update();
        }
        super.updateById(entity);
        // 清除商品分类列表缓存
        clearProductCache(old.getCategoryId());
        if (!old.getCategoryId().equals(dto.getCategoryId())) {
            clearProductCache(entity.getCategoryId());
        }
        if (ObjUtil.defaultIfNull(dto.getIsStock(), old.getIsStock()) == YesNoEnum.NO.value()) {
            redisService.del(StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, shopId, dto.getId()));
            Boolean b = redisService.hasKey(StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, shopId, dto.getId()));
            log.info("删除商品库存缓存:{}-{}", dto.getId(), b);
        } else {
            redisService.set(StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, shopId, dto.getId()), dto.getStockNumber());
            log.info("设置商品库存缓存:{}-{}", dto.getId(), dto.getStockNumber());
        }
        List<ProdSkuDTO> skuList = dto.getSkuList();
        // 商品SKU-ID列表
        List<Long> skuIdList = prodSkuMapper.selectListByQueryAs(query().select(ProdSku::getId).eq(ProdSku::getProductId, dto.getId()), Long.class);
        dto.setSkuList(skuList);
        if (CollUtil.isNotEmpty(skuList)) {
            for (ProdSkuDTO prodSkuDTO : skuList) {
                ProdSku prodSku = BeanUtil.copyProperties(prodSkuDTO, ProdSku.class);
                if (prodSku.getId() == null) {
                    prodSku.setShopId(entity.getShopId());
                    prodSku.setProductId(entity.getId());
                    prodSku.setRealSalesNumber(BigDecimal.ZERO);
                    prodSku.setIsPauseSale(YesNoEnum.NO.value());
                    prodSku.setIsGrounding(YesNoEnum.YES.value());
                    prodSku.setIsDel(DeleteEnum.NORMAL.value());
                    prodSkuMapper.insert(prodSku);
                } else {
                    prodSkuMapper.update(prodSku);
                }
            }
            // 实际提交的商品SKU-ID列表
            List<Long> list = skuList.stream().map(ProdSkuDTO::getId).filter(Objects::nonNull).distinct().toList();
            skuIdList.removeAll(list);
        }
        if (CollUtil.isNotEmpty(skuIdList)) {
            // 逻辑删除无用的SKU数据
            UpdateChain.of(ProdSku.class)
                    .set(ProdSku::getIsDel, DeleteEnum.DELETED.value())
                    .in(ProdSku::getId, skuIdList)
                    .eq(ProdSku::getShopId, shopId)
                    .update();
        }
        if (entity.getIsStock() == YesNoEnum.NO.value()) {
            return;
        }
        // 记录商品库存流水
        ProductStockFlow flow = new ProductStockFlow();
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        flow.setCreateUserId(createUserId);
        flow.setCreateUserName(createUserName);
        flow.setShopId(shopId);
        flow.setProductId(old.getId());
        flow.setProductName(old.getName());
        flow.setBeforeNumber(NumberUtil.toBigDecimal(old.getStockNumber()));
        BigDecimal inOutNumber = NumberUtil.sub(NumberUtil.toBigDecimal(entity.getStockNumber()), flow.getBeforeNumber());
        // 盘亏
        if (NumberUtil.isLess(inOutNumber, BigDecimal.ZERO)) {
            flow.setInOutNumber(inOutNumber);
            flow.setInOutType(InOutTypeEnum.OUT.value());
            flow.setInOutItem(InOutItemEnum.LOSS_OUT.value());
        }
        // 盘盈
        if (NumberUtil.isGreater(inOutNumber, BigDecimal.ZERO)) {
            flow.setInOutNumber(inOutNumber);
            flow.setInOutType(InOutTypeEnum.IN.value());
            flow.setInOutItem(InOutItemEnum.WIN_IN.value());
        }
        // 盘平
        if (NumberUtil.equals(inOutNumber, BigDecimal.ZERO)) {
            return;
        }
        flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
        productStockFlowService.saveFlow(flow);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProductStock(ProductModifyStockParam param) {
        Product entity = super.getById(param.getId());
        if (entity.getIsStock() == YesNoEnum.NO.value()) {
            throw new CzgException("该商品未开启库存管理，暂不支持修改库存");
        }
        if (!entity.getShopId().equals(param.getShopId())) {
            return;
        }
        UpdateChain.of(Product.class)
                .set(Product::getStockNumber, param.getStockNumber())
                .set(Product::getShopId, param.getShopId())
                .eq(Product::getId, param.getId())
                .update();
        // 记录商品库存流水
        ProductStockFlow flow = new ProductStockFlow();
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        flow.setCreateUserId(createUserId);
        flow.setCreateUserName(createUserName);
        flow.setShopId(param.getShopId());
        flow.setProductId(entity.getId());
        flow.setProductName(entity.getName());
        flow.setBeforeNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
        BigDecimal inOutNumber = NumberUtil.sub(NumberUtil.toBigDecimal(param.getStockNumber()), flow.getBeforeNumber());
        // 盘亏
        if (NumberUtil.isLess(inOutNumber, BigDecimal.ZERO)) {
            flow.setInOutNumber(inOutNumber);
            flow.setInOutType(InOutTypeEnum.OUT.value());
            flow.setInOutItem(InOutItemEnum.LOSS_OUT.value());
        }
        // 盘盈
        if (NumberUtil.isGreater(inOutNumber, BigDecimal.ZERO)) {
            flow.setInOutNumber(inOutNumber);
            flow.setInOutType(InOutTypeEnum.IN.value());
            flow.setInOutItem(InOutItemEnum.WIN_IN.value());
        }
        // 盘平
        if (NumberUtil.equals(inOutNumber, BigDecimal.ZERO)) {
            return;
        }
        flow.setAfterNumber(NumberUtil.toBigDecimal(entity.getStockNumber()));
        flow.setRemark(param.getRemark());
        productStockFlowService.saveFlow(flow);
    }

    @Override
    @CacheEvict(value = {CacheConstant.USER_CLIENT_HOTS_PRODUCT, CacheConstant.USER_CLIENT_GROUPS_PRODUCT}, key = "#shopId", allEntries = true)
    public void deleteProduct(Long shopId, Long id) {
        Product old = getById(id);
        Long categoryId = old.getCategoryId();
        UpdateChain.of(Product.class)
                .set(Product::getIsDel, DeleteEnum.DELETED.value())
                .eq(Product::getId, id)
                .eq(Product::getShopId, shopId)
                .update();
        prodGroupRelationMapper.deleteByQuery(query().eq(ProdGroupRelation::getProductId, id));
        prodConsRelationMapper.deleteByQuery(query().eq(ProdConsRelation::getProductId, id));
        // 清除商品分类列表缓存
        clearProductCache(categoryId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {CacheConstant.USER_CLIENT_HOTS_PRODUCT, CacheConstant.USER_CLIENT_GROUPS_PRODUCT}, key = "#param.shopId", allEntries = true, beforeInvocation = true)
    public void onOffProduct(ProductIsSaleParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        String type = param.getType();
        Long id = param.getId();
        Integer isSale = param.getIsSale();
        String sensitiveOperation = "";
        if (isSale == 1) {
            sensitiveOperation = "上架";
        } else {
            sensitiveOperation = "下架";
        }
        Long prodId = null;
        if (ProductIsSaleTypeEnum.SKU.value().equals(type)) {
            ProdSku prodSku = prodSkuMapper.selectOneById(id);
            if (prodSku == null) {
                throw new CzgException("SKU不存在");
            }
            prodSku.setIsGrounding(isSale);
            prodSkuMapper.update(prodSku);
            long normalCount = prodSkuMapper.selectCountByQuery(QueryWrapper.create()
                    .eq(ProdSku::getProductId, prodSku.getProductId())
                    .eq(ProdSku::getIsGrounding, YesNoEnum.NO.value())
                    .eq(ProdSku::getIsDel, DeleteEnum.NORMAL.value())
            );
            if (normalCount == 0) {
                UpdateChain.of(Product.class)
                        .set(Product::getIsSale, isSale)
                        .eq(Product::getId, prodSku.getProductId())
                        .eq(Product::getShopId, shopId)
                        .update();
                prodId = prodSku.getProductId();
            }
            Long productId = prodSku.getProductId();
            Product product = mapper.selectOneById(productId);
            sensitiveOperation = sensitiveOperation + "商品：" + product.getName() + " 规格：" + prodSku.getSpecInfo();
        } else if (ProductIsSaleTypeEnum.PRODUCT.value().equals(type)) {
            UpdateChain.of(Product.class)
                    .set(Product::getIsSale, isSale)
                    .eq(Product::getId, id)
                    .eq(Product::getShopId, shopId)
                    .update();
            UpdateChain.of(ProdSku.class)
                    .set(ProdSku::getIsGrounding, isSale)
                    .eq(ProdSku::getProductId, id)
                    .eq(ProdSku::getShopId, shopId)
                    .update();
            Product product = mapper.selectOneById(id);
            prodId = product.getId();
            sensitiveOperation = sensitiveOperation + "商品：" + product.getName();
        }
        sensitiveOperationService.send(sensitiveOperation);
        // 清除商品分类列表缓存
        Product old = getById(prodId);
        Long categoryId = old.getCategoryId();
        clearProductCache(categoryId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {CacheConstant.USER_CLIENT_HOTS_PRODUCT, CacheConstant.USER_CLIENT_GROUPS_PRODUCT}, key = "#param.shopId", allEntries = true, beforeInvocation = true)
    public void markProductIsSoldOut(ProductIsSoldOutParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        String type = param.getType();
        Long id = param.getId();
        Integer isSoldOut = param.getIsSoldOut();
        Long prodId = null;
        if (ProductIsSaleTypeEnum.SKU.value().equals(type)) {
            ProdSku prodSku = prodSkuMapper.selectOneById(id);
            if (prodSku == null) {
                throw new CzgException("SKU不存在");
            }
            prodSku.setIsPauseSale(isSoldOut);
            prodSkuMapper.update(prodSku);
            long normalCount = prodSkuMapper.selectCountByQuery(QueryWrapper.create()
                    .eq(ProdSku::getProductId, prodSku.getProductId())
                    .eq(ProdSku::getIsPauseSale, YesNoEnum.NO.value())
                    .eq(ProdSku::getIsDel, DeleteEnum.NORMAL.value())
            );
            if (normalCount == 0) {
                UpdateChain.of(Product.class)
                        .set(Product::getIsSoldStock, isSoldOut)
                        .eq(Product::getId, prodSku.getProductId())
                        .eq(Product::getShopId, shopId)
                        .update();
            }
            prodId = prodSku.getProductId();
        } else if (ProductIsSaleTypeEnum.PRODUCT.value().equals(type)) {
            UpdateChain.of(Product.class)
                    .set(Product::getIsSoldStock, isSoldOut)
                    .eq(Product::getId, id)
                    .eq(Product::getShopId, shopId)
                    .update();
            UpdateChain.of(ProdSku.class)
                    .set(ProdSku::getIsPauseSale, isSoldOut)
                    .eq(ProdSku::getProductId, id)
                    .eq(ProdSku::getShopId, shopId)
                    .update();
            prodId = id;
        }
        // 清除商品分类列表缓存
        Product old = getById(prodId);
        Long categoryId = old.getCategoryId();
        clearProductCache(categoryId);
    }

    @Override
    public void refundToStock(ProdRefundToStockParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Product entity = super.getOne(query().eq(Product::getId, param.getId()).eq(Product::getShopId, shopId));
        if (entity == null) {
            throw new CzgException("商品不存在");
        }
        entity.setIsRefundStock(param.getIsReturn());
        super.updateById(entity);
    }

    @Override
    public void stockWarning(Integer warnLine) {
        Long shopId = StpKit.USER.getShopId(0L);
        UpdateChain.of(Product.class)
                .set(Product::getWarnLine, warnLine)
                .eq(Product::getShopId, shopId)
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reportDamage(ProductReportDamageParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        Product product = mapper.selectOneById(param.getProductId());
        if (product == null) {
            throw new CzgException("商品不存在");
        }
        if (product.getIsStock() == YesNoEnum.NO.value()) {
            throw new CzgException("商品未开启库存不支持报损操作");
        }
        // 商品现有库存数量
        Integer stockNumber = product.getStockNumber();
        product.setStockNumber(stockNumber - param.getNumber());
        if (product.getStockNumber() < 0) {
            throw new CzgException("商品库存不足，无法报损");
        }
        super.updateById(product);
        // 记录商品库存流水
        ProductStockFlow flow = new ProductStockFlow();
        flow.setCreateUserId(createUserId);
        flow.setCreateUserName(createUserName);
        flow.setShopId(shopId);
        flow.setProductId(product.getId());
        flow.setProductName(product.getName());
        flow.setBeforeNumber(NumberUtil.toBigDecimal(stockNumber));
        flow.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, NumberUtil.toBigDecimal(param.getNumber())));
        flow.setAfterNumber(NumberUtil.toBigDecimal(product.getStockNumber()));
        flow.setInOutType(InOutTypeEnum.OUT.value());
        flow.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
        flow.setImgUrls(JSON.toJSONString(param.getImgUrls()));
        productStockFlowService.saveFlow(flow);
        // 如果绑定了耗材，则同步更新耗材库存
        List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(param.getProductId());
        if (CollUtil.isEmpty(consList)) {
            return;
        }
        for (ProdConsRelationDTO consInfo : consList) {
            ConsStockFlow entity = new ConsStockFlow();
            entity.setCreateUserId(createUserId);
            entity.setCreateUserName(createUserName);
            entity.setShopId(shopId);
            entity.setConId(consInfo.getConsInfoId());
            entity.setConName(consInfo.getConName());
            entity.setPurchasePrice(consInfo.getPrice());
            BigDecimal inOutNumber = NumberUtil.mul(param.getNumber(), consInfo.getSurplusStock());
            BigDecimal balance = NumberUtil.sub(consInfo.getStockNumber(), inOutNumber);
            if (NumberUtil.isLess(balance, BigDecimal.ZERO)) {
                throw new CzgException(StrUtil.format("耗材{}存在发生变动，请刷新后重试", entity.getConName()));
            }
            entity.setBeforeNumber(consInfo.getStockNumber());
            entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, inOutNumber));
            entity.setAfterNumber(balance);
            entity.setInOutType(InOutTypeEnum.OUT.value());
            entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
            entity.setSubTotal(NumberUtil.mul(inOutNumber, consInfo.getPrice()));
            entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
            entity.setRemark("【商品报损，自动报损相关耗材】" + StrUtil.nullToDefault(param.getRemark(), ""));
            consStockFlowService.saveFlow(entity);
            consInfo.setStockNumber(entity.getAfterNumber());
            UpdateChain.of(ConsInfo.class)
                    .set(ConsInfo::getStockNumber, consInfo.getStockNumber())
                    .eq(ConsInfo::getId, consInfo.getConsInfoId())
                    .update();
        }
    }

    @Override
    public ProductStatisticsVo getProductStatistics(ProductInfoParam param) {
        ProductStatisticsVo data = productStockFlowMapper.getProductStatistics(param);
        if (data == null) {
            data = new ProductStatisticsVo();
        }
        return data;
    }

    @Override
    public Page<ProductStockFlow> findProductStockFlowPage(ProductStockFlowParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        param.setShopId(shopId);
        PageHelper.startPage(PageUtil.buildPageHelp());
        if (InOutItemEnum.ORDER_IN.value().equals(param.getInOutItem())) {
            param.setInOutType(InOutTypeEnum.OUT.value());
            param.setInOutItem(InOutItemEnum.ORDER_OUT.value());
            param.setIsGreaterZero(YesNoEnum.YES.value());
        }
        return PageUtil.convert(new PageInfo<>(productStockFlowMapper.getProductStockFlowList(param)));
    }

    @Override
    public void refreshProductStock(ProductDTO param, List<ProductDTO> records) {
        QueryWrapper queryWrapper = buildQueryWrapper(param);
        queryWrapper.select(PRODUCT.ID, PRODUCT.STOCK_NUMBER);
        List<Product> list = super.list(queryWrapper);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        Map<Long, Integer> stock = list.stream().collect(Collectors.toMap(Product::getId, Product::getStockNumber));
        records.parallelStream().forEach(record -> {
            record.setStockNumber(stock.getOrDefault(record.getId(), 0));
            if (record.getIsStock() == YesNoEnum.YES.value()) {
                refreshRedisStock(record.getShopId(), record.getId(), record.getStockNumber());
            } else {
                redisService.del(StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, record.getShopId(), record.getId()));
            }
        });
    }

    private void refreshRedisStock(Long shopId, Long productId, Integer stockNumber) {
        String key = StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, shopId, productId);
        redisService.set(key, stockNumber);
    }
}
