package com.ruoyi.web.modules.goods.service.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.web.modules.category.constant.CategoryConstants;
import com.ruoyi.web.modules.category.domain.BizGoodsCategory;
import com.ruoyi.web.modules.category.facade.ICategoryFacade;
import com.ruoyi.web.modules.category.mapper.BizGoodsCategoryMapper;
import com.ruoyi.web.modules.category.support.SellerShopContext;
import com.ruoyi.web.modules.goodsservice.domain.BizGoodsService;
import com.ruoyi.web.modules.goodsservice.facade.IGoodsServiceFacade;
import com.ruoyi.web.modules.goodsservice.mapper.BizGoodsServiceMapper;
import com.ruoyi.web.modules.goods.constant.GoodsConstants;
import com.ruoyi.web.modules.freight.constant.FreightTemplateConstants;
import com.ruoyi.web.modules.freight.domain.BizShopFreightTemplate;
import com.ruoyi.web.modules.freight.facade.IFreightTemplateFacade;
import com.ruoyi.web.modules.freight.mapper.BizShopFreightTemplateMapper;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.domain.BizGoodsAttr;
import com.ruoyi.web.modules.goods.domain.BizGoodsFreight;
import com.ruoyi.web.modules.goods.domain.BizGoodsPic;
import com.ruoyi.web.modules.goods.domain.BizGoodsServiceSnapshot;
import com.ruoyi.web.modules.goods.dto.GoodsAuditDTO;
import com.ruoyi.web.modules.goods.dto.GoodsAttrItemDTO;
import com.ruoyi.web.modules.goods.dto.GoodsBatchIdsDTO;
import com.ruoyi.web.modules.goods.dto.GoodsOffShelfDTO;
import com.ruoyi.web.modules.goods.dto.GoodsSaveDTO;
import com.ruoyi.web.modules.goods.exception.GoodsBatchOperationException;
import com.ruoyi.web.modules.goods.facade.IGoodsOrderFacade;
import com.ruoyi.web.modules.goods.mapper.BizGoodsAttrMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsFreightMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsPicMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsServiceSnapshotMapper;
import com.ruoyi.web.modules.goods.service.IGoodsService;
import com.ruoyi.web.modules.goods.support.GoodsAttrSupport;
import com.ruoyi.web.modules.goods.support.GoodsSnGenerator;
import com.ruoyi.web.modules.goods.support.GoodsStatusUtils;
import com.ruoyi.web.modules.goods.vo.GoodsDetailVO;
import com.ruoyi.web.modules.goods.vo.GoodsListVO;
import com.ruoyi.web.modules.goods.vo.GoodsServiceSnapshotVO;
import com.ruoyi.web.modules.merchant.domain.BizMerchant;
import com.ruoyi.web.modules.merchant.mapper.BizMerchantMapper;
import com.ruoyi.web.modules.merchant.support.MerchantDisplayUtils;
import com.ruoyi.web.modules.store.constant.ShopConstants;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.facade.IShopGlobalConfigFacade;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;
import com.ruoyi.web.modules.stock.dto.AutoInboundCommand;
import com.ruoyi.web.modules.stock.dto.AutoInboundLine;
import com.ruoyi.web.modules.stock.dto.DeleteSpecOutboundCommand;
import com.ruoyi.web.modules.stock.dto.DeleteSpecOutboundLine;
import com.ruoyi.web.modules.stock.service.IStockInboundFacade;
import com.ruoyi.web.modules.stock.service.IStockOutboundFacade;
import com.ruoyi.web.modules.stock.support.StockSupport;
import com.ruoyi.web.modules.template.constant.AttrTemplateConstants;
import com.ruoyi.web.modules.template.domain.BizGoodsAttrTemplate;
import com.ruoyi.web.modules.template.mapper.BizGoodsAttrTemplateMapper;

@Service
public class GoodsServiceImpl implements IGoodsService
{
    @Autowired
    private BizGoodsMapper goodsMapper;

    @Autowired
    private BizGoodsServiceSnapshotMapper snapshotMapper;

    @Autowired
    private BizGoodsCategoryMapper categoryMapper;

    @Autowired
    private BizShopMapper shopMapper;

    @Autowired
    private BizGoodsServiceMapper goodsServiceMapper;

    @Autowired
    private ICategoryFacade categoryFacade;

    @Autowired
    private IGoodsServiceFacade goodsServiceFacade;

    @Autowired
    private GoodsSnGenerator goodsSnGenerator;

    @Autowired
    private BizMerchantMapper merchantMapper;

    @Autowired
    private IShopGlobalConfigFacade shopGlobalConfigFacade;

    @Autowired
    private IGoodsOrderFacade goodsOrderFacade;

    @Autowired
    private BizGoodsAttrTemplateMapper attrTemplateMapper;

    @Autowired
    private BizGoodsAttrMapper goodsAttrMapper;

    @Autowired
    private BizGoodsPicMapper goodsPicMapper;

    @Autowired
    private BizGoodsFreightMapper goodsFreightMapper;

    @Autowired
    private BizShopFreightTemplateMapper freightTemplateMapper;

    @Autowired
    private IFreightTemplateFacade freightTemplateFacade;

    @Autowired
    private IStockInboundFacade stockInboundFacade;

    @Autowired
    private IStockOutboundFacade stockOutboundFacade;

    @Autowired
    private StockSupport stockSupport;

    @Override
    public List<GoodsListVO> selectPlatformList(BizGoods query)
    {
        normalizePlatformQuery(query);
        List<GoodsListVO> rows = goodsMapper.selectPlatformList(query);
        fillStatusLabel(rows);
        return rows;
    }

    @Override
    public int countPlatformPending()
    {
        return goodsMapper.countPlatformPending();
    }

    @Override
    public List<GoodsListVO> selectSellerList(Long shopId, BizGoods query)
    {
        requireShopId(shopId);
        query.setShopId(shopId);
        List<GoodsListVO> rows = goodsMapper.selectSellerList(query);
        fillStatusLabel(rows);
        return rows;
    }

    @Override
    public GoodsDetailVO selectPlatformDetail(Long goodsId)
    {
        BizGoods goods = requireGoods(goodsId);
        if (GoodsConstants.STATUS_DRAFT.equals(goods.getGoodsStatus()))
        {
            throw new ServiceException(GoodsConstants.MSG_GOODS_NOT_FOUND);
        }
        return toDetailVo(goods, true);
    }

    @Override
    public GoodsDetailVO selectSellerDetail(Long shopId, Long goodsId)
    {
        requireShopId(shopId);
        BizGoods goods = requireGoodsOfShop(goodsId, shopId);
        return toDetailVo(goods, false);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long insertSellerGoods(Long shopId, GoodsSaveDTO dto, String operator)
    {
        requireShopId(shopId);
        rejectGoodsStatusInBody(dto);
        validateSaveFields(dto, true);
        assertPlatformCategoryLevel2(dto.getCategoryId());
        assertShopCategoryIfPresent(shopId, dto.getShopCategoryId());
        assertAttrTemplateIfPresent(shopId, dto.getAttrTemplateId());
        validateFreightConfig(shopId, dto);
        BizGoods goods = buildGoodsFromDto(dto);
        goods.setShopId(shopId);
        goods.setGoodsSn(goodsSnGenerator.nextSn());
        goods.setGoodsStatus(GoodsConstants.STATUS_DRAFT);
        goods.setSalesCount(0);
        goods.setDelFlag(GoodsConstants.DEL_NORMAL);
        goods.setCreateBy(operator);
        goodsMapper.insert(goods);
        saveServiceSnapshots(goods.getGoodsId(), dto.getServiceIds());
        saveGoodsAttrs(goods.getGoodsId(), dto.getAttributes(), dto.getSpecs());
        saveGoodsSubPics(goods.getGoodsId(), dto.getSubPics(), operator);
        saveGoodsFreight(goods.getGoodsId(), dto);
        triggerAutoInboundIfNeeded(shopId, goods, operator);
        return goods.getGoodsId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSellerGoods(Long shopId, GoodsSaveDTO dto, String operator)
    {
        requireShopId(shopId);
        rejectGoodsStatusInBody(dto);
        if (dto.getGoodsId() == null)
        {
            throw new ServiceException("商品ID不能为空");
        }
        requireGoodsOfShop(dto.getGoodsId(), shopId);
        validateSaveFields(dto, false);
        if (dto.getCategoryId() != null)
        {
            assertPlatformCategoryLevel2(dto.getCategoryId());
        }
        assertShopCategoryIfPresent(shopId, dto.getShopCategoryId());
        assertAttrTemplateIfPresent(shopId, dto.getAttrTemplateId());
        validateFreightConfig(shopId, dto);
        BizGoods update = buildGoodsFromDto(dto);
        update.setGoodsId(dto.getGoodsId());
        update.setUpdateBy(operator);
        goodsMapper.updateSeller(update);
        saveServiceSnapshots(dto.getGoodsId(), dto.getServiceIds());
        saveGoodsAttrs(dto.getGoodsId(), dto.getAttributes(), dto.getSpecs());
        saveGoodsSubPics(dto.getGoodsId(), dto.getSubPics(), operator);
        saveGoodsFreight(dto.getGoodsId(), dto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitGoods(Long shopId, Long goodsId, String operator)
    {
        requireShopId(shopId);
        BizGoods goods = requireGoodsOfShop(goodsId, shopId);
        String current = goods.getGoodsStatus();
        if (!GoodsConstants.STATUS_DRAFT.equals(current)
                && !GoodsConstants.STATUS_REJECTED.equals(current)
                && !GoodsConstants.STATUS_OFF_SHELF.equals(current))
        {
            throw new ServiceException(GoodsConstants.MSG_SUBMIT_INVALID);
        }
        BizShop shop = shopMapper.selectById(shopId);
        if (shop == null)
        {
            throw new ServiceException("店铺不存在");
        }
        String targetStatus = ShopConstants.AUDIT_PASS_YES.equals(shopGlobalConfigFacade.getDefaultAuditPass())
                ? GoodsConstants.STATUS_ON_SALE : GoodsConstants.STATUS_PENDING;
        boolean firstSubmit = goods.getSubmitTime() == null;
        Date submitTime = firstSubmit ? new Date() : goods.getSubmitTime();
        goodsMapper.updateSubmitStatus(goodsId, targetStatus, submitTime, firstSubmit, operator);
        Map<String, Object> data = new HashMap<>();
        data.put("goodsId", goodsId);
        data.put("goodsStatus", targetStatus);
        data.put("goodsStatusLabel", GoodsStatusUtils.label(targetStatus));
        return data;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitGoodsBatch(Long shopId, GoodsBatchIdsDTO dto, String operator)
    {
        requireShopId(shopId);
        if (dto.getGoodsIds() == null || dto.getGoodsIds().isEmpty())
        {
            throw new ServiceException(GoodsConstants.MSG_SELECT_GOODS);
        }
        List<String> reasons = new ArrayList<>();
        for (Long goodsId : dto.getGoodsIds())
        {
            BizGoods goods = goodsMapper.selectByIdAndShop(goodsId, shopId);
            if (goods == null)
            {
                reasons.add(GoodsConstants.MSG_NOT_OWNER);
                continue;
            }
            if (!canSubmitStatus(goods.getGoodsStatus()))
            {
                reasons.add("商品不可提交上架：" + goods.getGoodsSn());
            }
        }
        if (!reasons.isEmpty())
        {
            throw new GoodsBatchOperationException(GoodsConstants.MSG_BATCH_FAIL, reasons);
        }
        for (Long goodsId : dto.getGoodsIds())
        {
            submitGoods(shopId, goodsId, operator);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditGoods(GoodsAuditDTO dto, String operator)
    {
        if (dto.getGoodsIds() == null || dto.getGoodsIds().isEmpty())
        {
            throw new ServiceException("请选择要审核的商品");
        }
        if (!dto.isPass() && StringUtils.isEmpty(dto.getRejectReason()))
        {
            throw new ServiceException(GoodsConstants.MSG_REJECT_REASON);
        }
        List<String> reasons = new ArrayList<>();
        for (Long goodsId : dto.getGoodsIds())
        {
            BizGoods goods = goodsMapper.selectById(goodsId);
            if (goods == null)
            {
                reasons.add("商品不存在：" + goodsId);
                continue;
            }
            if (!GoodsConstants.STATUS_PENDING.equals(goods.getGoodsStatus()))
            {
                reasons.add("商品非待审核状态：" + goods.getGoodsSn());
            }
        }
        if (!reasons.isEmpty())
        {
            throw new GoodsBatchOperationException(GoodsConstants.MSG_BATCH_FAIL, reasons);
        }
        for (Long goodsId : dto.getGoodsIds())
        {
            int rows;
            if (dto.isPass())
            {
                rows = goodsMapper.updateAuditPass(goodsId, operator);
            }
            else
            {
                rows = goodsMapper.updateAuditReject(goodsId, dto.getRejectReason(), operator);
            }
            if (rows == 0)
            {
                throw new ServiceException(GoodsConstants.MSG_AUDIT_INVALID);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offShelfPlatform(GoodsOffShelfDTO dto, String operator)
    {
        offShelfBatch(dto, null, operator);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offShelfSeller(Long shopId, Long goodsId, String operator)
    {
        requireShopId(shopId);
        GoodsOffShelfDTO dto = new GoodsOffShelfDTO();
        List<Long> ids = new ArrayList<>();
        ids.add(goodsId);
        dto.setGoodsIds(ids);
        offShelfBatch(dto, shopId, operator);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offShelfSellerBatch(Long shopId, GoodsOffShelfDTO dto, String operator)
    {
        requireShopId(shopId);
        offShelfBatch(dto, shopId, operator);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteSellerGoods(Long shopId, Long goodsId, String operator)
    {
        requireShopId(shopId);
        GoodsBatchIdsDTO dto = new GoodsBatchIdsDTO();
        dto.setGoodsIds(java.util.Collections.singletonList(goodsId));
        deleteSellerGoodsBatch(shopId, dto, operator);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteSellerGoodsBatch(Long shopId, GoodsBatchIdsDTO dto, String operator)
    {
        requireShopId(shopId);
        if (dto.getGoodsIds() == null || dto.getGoodsIds().isEmpty())
        {
            throw new ServiceException(GoodsConstants.MSG_SELECT_GOODS);
        }
        List<String> reasons = new ArrayList<>();
        List<DeleteSpecOutboundLine> outboundLines = new ArrayList<>();
        for (Long goodsId : dto.getGoodsIds())
        {
            BizGoods goods = goodsMapper.selectByIdAndShop(goodsId, shopId);
            if (goods == null)
            {
                reasons.add(GoodsConstants.MSG_NOT_OWNER);
                continue;
            }
            if (!canDeleteStatus(goods.getGoodsStatus()))
            {
                reasons.add(GoodsConstants.MSG_DELETE_INVALID + "：" + goods.getGoodsSn());
            }
            DeleteSpecOutboundLine line = new DeleteSpecOutboundLine();
            line.setGoodsId(goodsId);
            line.setGoodsName(goods.getGoodsName());
            line.setMainPic(goods.getMainPic());
            line.setSpecText(stockSupport.buildSpecText(goodsId));
            line.setStockBefore(goods.getStock() != null ? goods.getStock() : 0);
            outboundLines.add(line);
        }
        if (!reasons.isEmpty())
        {
            throw new GoodsBatchOperationException(GoodsConstants.MSG_BATCH_FAIL, reasons);
        }
        goodsMapper.logicDeleteByIds(dto.getGoodsIds(), shopId, operator);
        for (Long goodsId : dto.getGoodsIds())
        {
            snapshotMapper.deleteByGoodsId(goodsId);
            goodsAttrMapper.deleteByGoodsId(goodsId);
            goodsPicMapper.deleteByGoodsId(goodsId);
            goodsFreightMapper.deleteByGoodsId(goodsId);
        }
        triggerDeleteSpecOutbound(shopId, outboundLines, operator);
    }

    private void offShelfBatch(GoodsOffShelfDTO dto, Long shopId, String operator)
    {
        if (dto.getGoodsIds() == null || dto.getGoodsIds().isEmpty())
        {
            throw new ServiceException("请选择要下架的商品");
        }
        List<String> reasons = new ArrayList<>();
        for (Long goodsId : dto.getGoodsIds())
        {
            BizGoods goods = goodsMapper.selectById(goodsId);
            if (goods == null)
            {
                reasons.add("商品不存在");
                continue;
            }
            if (shopId != null && !shopId.equals(goods.getShopId()))
            {
                reasons.add(GoodsConstants.MSG_NOT_OWNER);
                continue;
            }
            if (!GoodsConstants.STATUS_ON_SALE.equals(goods.getGoodsStatus()))
            {
                reasons.add(GoodsConstants.MSG_OFF_SHELF_INVALID);
                continue;
            }
            if (goodsOrderFacade.hasUnfinishedOrderByGoodsId(goodsId))
            {
                reasons.add(GoodsConstants.MSG_UNFINISHED_ORDER + "：" + goods.getGoodsSn());
            }
        }
        if (!reasons.isEmpty())
        {
            throw new GoodsBatchOperationException(GoodsConstants.MSG_BATCH_FAIL, reasons);
        }
        for (Long goodsId : dto.getGoodsIds())
        {
            if (goodsMapper.updateOffShelf(goodsId, operator) == 0)
            {
                throw new ServiceException(GoodsConstants.MSG_OFF_SHELF_INVALID);
            }
        }
    }

    private GoodsDetailVO toDetailVo(BizGoods goods, boolean platform)
    {
        GoodsDetailVO vo = new GoodsDetailVO();
        vo.setGoodsId(goods.getGoodsId());
        vo.setShopId(goods.getShopId());
        vo.setCategoryId(goods.getCategoryId());
        vo.setShopCategoryId(goods.getShopCategoryId());
        vo.setAttrTemplateId(goods.getAttrTemplateId());
        vo.setGoodsSn(goods.getGoodsSn());
        vo.setGoodsName(goods.getGoodsName());
        vo.setMainPic(goods.getMainPic());
        vo.setDetailContent(goods.getDetailContent());
        vo.setSalePrice(goods.getSalePrice());
        vo.setStock(goods.getStock());
        vo.setSalesCount(goods.getSalesCount());
        vo.setGoodsStatus(goods.getGoodsStatus());
        vo.setGoodsStatusLabel(GoodsStatusUtils.label(goods.getGoodsStatus()));
        vo.setRejectReason(goods.getRejectReason());
        vo.setSubmitTime(goods.getSubmitTime());
        vo.setAuditTime(goods.getAuditTime());
        vo.setOffShelfTime(goods.getOffShelfTime());
        vo.setCategoryPath(categoryFacade.getCategoryPath(goods.getCategoryId()));
        if (goods.getShopCategoryId() != null)
        {
            vo.setShopCategoryPath(categoryFacade.getCategoryPath(goods.getShopCategoryId()));
        }
        if (goods.getAttrTemplateId() != null)
        {
            BizGoodsAttrTemplate template = attrTemplateMapper.selectById(goods.getAttrTemplateId(), goods.getShopId());
            if (template != null)
            {
                vo.setAttrTemplateName(template.getTemplateName());
            }
        }
        vo.setCanAudit(GoodsConstants.STATUS_PENDING.equals(goods.getGoodsStatus()));
        vo.setCanOffShelf(GoodsConstants.STATUS_ON_SALE.equals(goods.getGoodsStatus()));
        vo.setCanSubmit(canSubmitStatus(goods.getGoodsStatus()));
        vo.setCanDelete(canDeleteStatus(goods.getGoodsStatus()));
        vo.setCanEdit(true);
        List<GoodsServiceSnapshotVO> services = snapshotMapper.selectVisibleByGoodsId(goods.getGoodsId());
        vo.setServices(services);
        List<BizGoodsAttr> attrRows = goodsAttrMapper.selectByGoodsId(goods.getGoodsId());
        vo.setAttributes(GoodsAttrSupport.groupByItem(attrRows, AttrTemplateConstants.ITEM_TYPE_ATTR));
        vo.setSpecs(GoodsAttrSupport.groupByItem(attrRows, AttrTemplateConstants.ITEM_TYPE_SPEC));
        List<String> subPics = goodsPicMapper.selectPicUrlsByGoodsId(goods.getGoodsId());
        vo.setSubPics(subPics != null ? subPics : new ArrayList<>());
        fillFreightDetail(vo, goods);
        if (platform)
        {
            fillShopMerchantName(vo, goods.getShopId());
        }
        return vo;
    }

    private void fillShopMerchantName(GoodsDetailVO vo, Long shopId)
    {
        BizShop shop = shopMapper.selectById(shopId);
        if (shop == null)
        {
            return;
        }
        vo.setShopName(shop.getShopName());
        BizMerchant merchant = merchantMapper.selectById(shop.getMerchantId());
        if (merchant != null)
        {
            String name = StringUtils.isNotEmpty(merchant.getMerchantName())
                    ? merchant.getMerchantName() : MerchantDisplayUtils.unitName(merchant);
            vo.setMerchantName(name);
        }
    }

    private void fillStatusLabel(List<GoodsListVO> rows)
    {
        for (GoodsListVO row : rows)
        {
            row.setGoodsStatusLabel(GoodsStatusUtils.label(row.getGoodsStatus()));
        }
    }

    private void normalizePlatformQuery(BizGoods query)
    {
        if (query == null)
        {
            return;
        }
        if (StringUtils.isNotEmpty(query.getGoodsStatus()) && StringUtils.isEmpty(query.getGoodsStatusQuery()))
        {
            query.setGoodsStatusQuery(query.getGoodsStatus());
        }
    }

    private void saveServiceSnapshots(Long goodsId, List<Long> serviceIds)
    {
        snapshotMapper.deleteByGoodsId(goodsId);
        if (serviceIds == null || serviceIds.isEmpty())
        {
            return;
        }
        Set<Long> enabledIds = new HashSet<>();
        goodsServiceFacade.listAllEnabled().forEach(o -> enabledIds.add(o.getServiceId()));
        List<BizGoodsServiceSnapshot> list = new ArrayList<>();
        for (Long serviceId : serviceIds)
        {
            if (!enabledIds.contains(serviceId))
            {
                throw new ServiceException(GoodsConstants.MSG_SERVICE_INVALID);
            }
            BizGoodsService svc = goodsServiceMapper.selectById(serviceId);
            if (svc == null)
            {
                continue;
            }
            BizGoodsServiceSnapshot snap = new BizGoodsServiceSnapshot();
            snap.setGoodsId(goodsId);
            snap.setServiceId(serviceId);
            snap.setServiceName(svc.getServiceName());
            snap.setServiceIntro(svc.getServiceIntro());
            snap.setServiceIcon(svc.getServiceIcon());
            snap.setSortNo(svc.getSortNo());
            list.add(snap);
        }
        if (!list.isEmpty())
        {
            snapshotMapper.batchInsert(list);
        }
    }

    private void saveGoodsAttrs(Long goodsId, List<GoodsAttrItemDTO> attributes, List<GoodsAttrItemDTO> specs)
    {
        goodsAttrMapper.deleteByGoodsId(goodsId);
        if ((attributes == null || attributes.isEmpty()) && (specs == null || specs.isEmpty()))
        {
            return;
        }
        List<BizGoodsAttr> rows = GoodsAttrSupport.flatten(goodsId, attributes, specs);
        if (!rows.isEmpty())
        {
            goodsAttrMapper.batchInsert(rows);
        }
    }

    private void saveGoodsSubPics(Long goodsId, List<String> subPics, String operator)
    {
        goodsPicMapper.deleteByGoodsId(goodsId);
        List<String> normalized = normalizeSubPics(subPics);
        if (normalized.isEmpty())
        {
            return;
        }
        Date now = new Date();
        List<BizGoodsPic> rows = new ArrayList<>();
        for (int i = 0; i < normalized.size(); i++)
        {
            BizGoodsPic row = new BizGoodsPic();
            row.setGoodsId(goodsId);
            row.setPicUrl(normalized.get(i));
            row.setSortNo(i);
            row.setDelFlag(GoodsConstants.DEL_NORMAL);
            row.setCreateBy(operator);
            row.setCreateTime(now);
            rows.add(row);
        }
        goodsPicMapper.batchInsert(rows);
    }

    private List<String> normalizeSubPics(List<String> subPics)
    {
        if (subPics == null || subPics.isEmpty())
        {
            return Collections.emptyList();
        }
        List<String> result = new ArrayList<>();
        for (String pic : subPics)
        {
            if (StringUtils.isEmpty(pic))
            {
                continue;
            }
            String url = pic.trim();
            if (StringUtils.isEmpty(url))
            {
                continue;
            }
            if (url.length() > 512)
            {
                throw new ServiceException(GoodsConstants.MSG_SUB_PIC_TOO_LONG);
            }
            result.add(url);
        }
        if (result.size() > GoodsConstants.MAX_SUB_PIC_COUNT)
        {
            throw new ServiceException(GoodsConstants.MSG_SUB_PIC_TOO_MANY);
        }
        return result;
    }

    private void validateSubPics(List<String> subPics)
    {
        normalizeSubPics(subPics);
    }

    private BizGoods buildGoodsFromDto(GoodsSaveDTO dto)
    {
        BizGoods goods = new BizGoods();
        goods.setCategoryId(dto.getCategoryId());
        goods.setShopCategoryId(dto.getShopCategoryId());
        goods.setAttrTemplateId(dto.getAttrTemplateId());
        goods.setGoodsName(dto.getGoodsName());
        goods.setMainPic(dto.getMainPic());
        goods.setDetailContent(dto.getDetailContent());
        goods.setSalePrice(dto.getSalePrice());
        goods.setStock(dto.getStock());
        return goods;
    }

    private void validateSaveFields(GoodsSaveDTO dto, boolean insert)
    {
        if (insert)
        {
            if (dto.getCategoryId() == null)
            {
                throw new ServiceException("请选择分类");
            }
        }
        if (StringUtils.isNotEmpty(dto.getGoodsName()))
        {
            if (dto.getGoodsName().length() > 200)
            {
                throw new ServiceException("商品名称过长");
            }
        }
        else if (insert)
        {
            throw new ServiceException("请输入商品名称");
        }
        if (insert && StringUtils.isEmpty(dto.getMainPic()))
        {
            throw new ServiceException("请上传商品主图");
        }
        if (insert && StringUtils.isEmpty(dto.getDetailContent()))
        {
            throw new ServiceException("请填写商品详情");
        }
        if (dto.getSalePrice() != null && dto.getSalePrice().compareTo(BigDecimal.ZERO) < 0)
        {
            throw new ServiceException("销售价不能为负数");
        }
        if (insert && dto.getSalePrice() == null)
        {
            throw new ServiceException("请输入销售价");
        }
        if (dto.getStock() != null && dto.getStock() < 0)
        {
            throw new ServiceException("库存不能为负数");
        }
        if (insert && dto.getStock() == null)
        {
            throw new ServiceException("请输入库存");
        }
        validateSubPics(dto.getSubPics());
        validateFreightFields(dto);
    }

    private void validateFreightFields(GoodsSaveDTO dto)
    {
        if (StringUtils.isEmpty(dto.getFreightType()))
        {
            throw new ServiceException(GoodsConstants.MSG_FREIGHT_TYPE_REQUIRED);
        }
    }

    private void validateFreightConfig(Long shopId, GoodsSaveDTO dto)
    {
        validateFreightFields(dto);
        if (FreightTemplateConstants.FREIGHT_TYPE_FIXED.equals(dto.getFreightType()))
        {
            if (dto.getFixedFreightAmount() == null)
            {
                throw new ServiceException(GoodsConstants.MSG_FIXED_FREIGHT_REQUIRED);
            }
            if (dto.getFixedFreightAmount().compareTo(BigDecimal.ZERO) < 0)
            {
                throw new ServiceException(GoodsConstants.MSG_FIXED_FREIGHT_NEGATIVE);
            }
            return;
        }
        if (FreightTemplateConstants.FREIGHT_TYPE_TEMPLATE.equals(dto.getFreightType()))
        {
            if (dto.getFreightTemplateId() == null)
            {
                throw new ServiceException(GoodsConstants.MSG_FREIGHT_TEMPLATE_REQUIRED);
            }
            assertFreightTemplateBelongsToShop(shopId, dto.getFreightTemplateId());
            return;
        }
        throw new ServiceException(GoodsConstants.MSG_FREIGHT_TYPE_REQUIRED);
    }

    private void assertFreightTemplateBelongsToShop(Long shopId, Long freightTemplateId)
    {
        BizShopFreightTemplate template = freightTemplateMapper.selectById(freightTemplateId, shopId);
        if (template == null)
        {
            throw new ServiceException(GoodsConstants.MSG_FREIGHT_TEMPLATE_INVALID);
        }
    }

    private void saveGoodsFreight(Long goodsId, GoodsSaveDTO dto)
    {
        BizGoodsFreight row = new BizGoodsFreight();
        row.setGoodsId(goodsId);
        row.setFreightType(dto.getFreightType());
        if (FreightTemplateConstants.FREIGHT_TYPE_FIXED.equals(dto.getFreightType()))
        {
            row.setFixedAmount(dto.getFixedFreightAmount());
            row.setFreightTemplateId(null);
        }
        else
        {
            row.setFixedAmount(null);
            row.setFreightTemplateId(dto.getFreightTemplateId());
        }
        BizGoodsFreight existing = goodsFreightMapper.selectByGoodsId(goodsId);
        if (existing == null)
        {
            row.setDelFlag(GoodsConstants.DEL_NORMAL);
            goodsFreightMapper.insert(row);
        }
        else
        {
            goodsFreightMapper.update(row);
        }
    }

    private void fillFreightDetail(GoodsDetailVO vo, BizGoods goods)
    {
        BizGoodsFreight freight = goodsFreightMapper.selectByGoodsId(goods.getGoodsId());
        if (freight == null)
        {
            return;
        }
        vo.setFreightType(freight.getFreightType());
        if (FreightTemplateConstants.FREIGHT_TYPE_FIXED.equals(freight.getFreightType()))
        {
            vo.setFixedFreightAmount(freight.getFixedAmount());
            if (freight.getFixedAmount() != null)
            {
                vo.setFreightDesc("运费 ¥" + freight.getFixedAmount().setScale(2, java.math.RoundingMode.HALF_UP));
            }
            return;
        }
        vo.setFreightTemplateId(freight.getFreightTemplateId());
        if (freight.getFreightTemplateId() != null)
        {
            BizShopFreightTemplate template = freightTemplateMapper.selectById(freight.getFreightTemplateId(),
                    goods.getShopId());
            if (template != null)
            {
                vo.setFreightTemplateName(template.getTemplateName());
            }
            vo.setFreightDesc(freightTemplateFacade.buildFreightDesc(freight.getFreightTemplateId(), goods.getShopId()));
        }
    }

    private void assertPlatformCategoryLevel2(Long categoryId)
    {
        BizGoodsCategory cat = categoryMapper.selectById(categoryId, null);
        if (cat == null || !CategoryConstants.LEVEL_TWO.equals(cat.getCategoryLevel()))
        {
            throw new ServiceException(GoodsConstants.MSG_CATEGORY_INVALID);
        }
    }

    private void assertShopCategoryIfPresent(Long shopId, Long shopCategoryId)
    {
        if (shopCategoryId == null)
        {
            return;
        }
        BizGoodsCategory cat = categoryMapper.selectById(shopCategoryId, shopId);
        if (cat == null || !CategoryConstants.LEVEL_TWO.equals(cat.getCategoryLevel()))
        {
            throw new ServiceException(GoodsConstants.MSG_SHOP_CATEGORY_INVALID);
        }
    }

    private void assertAttrTemplateIfPresent(Long shopId, Long attrTemplateId)
    {
        if (attrTemplateId == null)
        {
            return;
        }
        BizGoodsAttrTemplate template = attrTemplateMapper.selectById(attrTemplateId, shopId);
        if (template == null)
        {
            throw new ServiceException(GoodsConstants.MSG_ATTR_TEMPLATE_INVALID);
        }
    }

    private boolean canSubmitStatus(String status)
    {
        return GoodsConstants.STATUS_DRAFT.equals(status)
                || GoodsConstants.STATUS_REJECTED.equals(status)
                || GoodsConstants.STATUS_OFF_SHELF.equals(status);
    }

    private boolean canDeleteStatus(String status)
    {
        return GoodsConstants.STATUS_DRAFT.equals(status)
                || GoodsConstants.STATUS_REJECTED.equals(status)
                || GoodsConstants.STATUS_OFF_SHELF.equals(status);
    }

    private void rejectGoodsStatusInBody(GoodsSaveDTO dto)
    {
        if (StringUtils.isNotEmpty(dto.getGoodsStatus()))
        {
            if (GoodsConstants.STATUS_DRAFT.equals(dto.getGoodsStatus())
                    && dto.getGoodsId() != null)
            {
                BizGoods existing = goodsMapper.selectById(dto.getGoodsId());
                if (existing != null && !GoodsConstants.STATUS_DRAFT.equals(existing.getGoodsStatus()))
                {
                    throw new ServiceException(GoodsConstants.MSG_CANNOT_REVERT_DRAFT);
                }
            }
            throw new ServiceException(GoodsConstants.MSG_ILLEGAL_STATUS_CHANGE);
        }
    }

    private void requireShopId(Long shopId)
    {
        Long ctx = SellerShopContext.getShopId();
        if (shopId == null)
        {
            shopId = ctx;
        }
        if (shopId == null || shopId <= 0)
        {
            throw new ServiceException(GoodsConstants.MSG_NO_SHOP);
        }
    }

    private BizGoods requireGoods(Long goodsId)
    {
        BizGoods goods = goodsMapper.selectById(goodsId);
        if (goods == null)
        {
            throw new ServiceException(GoodsConstants.MSG_GOODS_NOT_FOUND);
        }
        return goods;
    }

    private BizGoods requireGoodsOfShop(Long goodsId, Long shopId)
    {
        BizGoods goods = goodsMapper.selectByIdAndShop(goodsId, shopId);
        if (goods == null)
        {
            throw new ServiceException(GoodsConstants.MSG_NOT_OWNER);
        }
        return goods;
    }

    private void triggerAutoInboundIfNeeded(Long shopId, BizGoods goods, String operator)
    {
        if (goods.getStock() == null || goods.getStock() <= 0)
        {
            return;
        }
        AutoInboundLine line = new AutoInboundLine();
        line.setGoodsId(goods.getGoodsId());
        line.setGoodsName(goods.getGoodsName());
        line.setMainPic(goods.getMainPic());
        line.setSpecText(stockSupport.buildSpecText(goods.getGoodsId()));
        line.setQuantity(goods.getStock());
        line.setStockBefore(0);
        line.setStockAfter(goods.getStock());

        AutoInboundCommand cmd = new AutoInboundCommand();
        cmd.setShopId(shopId);
        cmd.setOperatorId(resolveOperatorId());
        cmd.setOperatorName(operator);
        cmd.setLines(Collections.singletonList(line));
        stockInboundFacade.createAutoInbound(cmd);
    }

    private void triggerDeleteSpecOutbound(Long shopId, List<DeleteSpecOutboundLine> lines, String operator)
    {
        if (lines == null || lines.isEmpty())
        {
            return;
        }
        DeleteSpecOutboundCommand cmd = new DeleteSpecOutboundCommand();
        cmd.setShopId(shopId);
        cmd.setOperatorId(resolveOperatorId());
        cmd.setOperatorName(operator);
        cmd.setLines(lines);
        stockOutboundFacade.createDeleteSpecOutbound(cmd);
    }

    private Long resolveOperatorId()
    {
        try
        {
            return SecurityUtils.getUserId();
        }
        catch (Exception e)
        {
            return 0L;
        }
    }
}
