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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.StringUtils;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.stock.constant.StockConstants;
import com.ruoyi.web.modules.stock.domain.BizStockLog;
import com.ruoyi.web.modules.stock.domain.BizStockOutbound;
import com.ruoyi.web.modules.stock.domain.BizStockOutboundItem;
import com.ruoyi.web.modules.stock.dto.StockOutboundCreateDTO;
import com.ruoyi.web.modules.stock.dto.StockOutboundCreateItemDTO;
import com.ruoyi.web.modules.stock.dto.StockOutboundQuery;
import com.ruoyi.web.modules.stock.mapper.BizStockOutboundItemMapper;
import com.ruoyi.web.modules.stock.mapper.BizStockOutboundMapper;
import com.ruoyi.web.modules.stock.service.IStockLogService;
import com.ruoyi.web.modules.stock.service.IStockOutboundService;
import com.ruoyi.web.modules.stock.support.StockSupport;
import com.ruoyi.web.modules.stock.vo.StockGoodsPickVO;
import com.ruoyi.web.modules.stock.vo.StockOperatorVO;
import com.ruoyi.web.modules.stock.vo.StockOutboundDetailVO;
import com.ruoyi.web.modules.stock.vo.StockOutboundItemVO;
import com.ruoyi.web.modules.stock.vo.StockOutboundListRowVO;

@Service
public class StockOutboundServiceImpl implements IStockOutboundService
{
    @Autowired
    private BizStockOutboundMapper outboundMapper;

    @Autowired
    private BizStockOutboundItemMapper outboundItemMapper;

    @Autowired
    private BizGoodsMapper goodsMapper;

    @Autowired
    private IStockLogService stockLogService;

    @Autowired
    private StockSupport stockSupport;

    @Override
    public List<StockOutboundListRowVO> selectList(Long shopId, StockOutboundQuery query)
    {
        List<StockOutboundListRowVO> rows = outboundMapper.selectList(shopId, query);
        for (StockOutboundListRowVO row : rows)
        {
            row.setOutboundTypeText(stockSupport.outboundTypeText(row.getOutboundType()));
        }
        return rows;
    }

    @Override
    public StockOutboundDetailVO getDetail(Long shopId, Long outboundId)
    {
        StockOutboundDetailVO detail = outboundMapper.selectDetail(shopId, outboundId);
        if (detail == null)
        {
            throw new ServiceException(StockConstants.MSG_OUTBOUND_NOT_FOUND);
        }
        detail.setOutboundTypeText(stockSupport.outboundTypeText(detail.getOutboundType()));
        List<StockOutboundItemVO> items = outboundItemMapper.selectByOutboundId(outboundId);
        detail.setItems(items != null ? items : Collections.emptyList());
        return detail;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> createManual(Long shopId, StockOutboundCreateDTO dto, Long operatorId,
            String operatorName)
    {
        validateCreateDto(dto);
        Map<Long, Integer> merged = mergeItems(dto.getItems());
        List<Long> goodsIds = new ArrayList<>(merged.keySet());
        Collections.sort(goodsIds);

        Date now = new Date();
        String outboundType = dto.getOutboundType();
        String changeReason = stockSupport.changeReasonForOutboundType(outboundType);
        BizStockOutbound outbound = new BizStockOutbound();
        outbound.setOutboundNo(stockSupport.nextOutboundNo());
        outbound.setShopId(shopId);
        outbound.setOutboundType(outboundType);
        outbound.setRemark(StringUtils.trim(dto.getRemark()));
        outbound.setOperatorId(operatorId);
        outbound.setOperatorName(StringUtils.nvl(operatorName, ""));
        outbound.setOutboundTime(now);
        outbound.setCreateBy(operatorName);
        outbound.setCreateTime(now);
        outboundMapper.insert(outbound);

        List<BizStockOutboundItem> itemRows = new ArrayList<>();
        for (Long goodsId : goodsIds)
        {
            Integer qty = merged.get(goodsId);
            BizGoods goods = goodsMapper.selectByIdAndShopForUpdate(goodsId, shopId);
            if (goods == null)
            {
                throw new ServiceException(StockConstants.MSG_OUTBOUND_GOODS_INVALID);
            }
            int stockBefore = goods.getStock() != null ? goods.getStock() : 0;
            if (qty > stockBefore)
            {
                throw new ServiceException(StockConstants.MSG_OUTBOUND_STOCK_INSUFFICIENT);
            }
            int rows = goodsMapper.decrementStockForOutbound(goodsId, shopId, qty);
            if (rows <= 0)
            {
                throw new ServiceException(StockConstants.MSG_OUTBOUND_STOCK_INSUFFICIENT);
            }
            int stockAfter = stockBefore - qty;
            String specText = stockSupport.buildSpecText(goodsId);

            BizStockOutboundItem item = new BizStockOutboundItem();
            item.setOutboundId(outbound.getOutboundId());
            item.setGoodsId(goodsId);
            item.setGoodsName(goods.getGoodsName());
            item.setMainPic(StringUtils.nvl(goods.getMainPic(), ""));
            item.setSpecText(stockSupport.normalizeSpecText(specText));
            item.setQuantity(qty);
            item.setStockBefore(stockBefore);
            item.setStockAfter(stockAfter);
            itemRows.add(item);

            insertOutboundLog(shopId, goodsId, goods.getGoodsName(), specText, changeReason, qty, stockBefore,
                    stockAfter, outbound.getOutboundId(), outbound.getOutboundNo(), operatorId, operatorName, now);
        }
        outboundItemMapper.batchInsert(itemRows);

        Map<String, Object> result = new HashMap<>();
        result.put("outboundId", outbound.getOutboundId());
        result.put("outboundNo", outbound.getOutboundNo());
        return result;
    }

    @Override
    public List<StockOperatorVO> listOperators(Long shopId)
    {
        List<StockOperatorVO> rows = outboundMapper.selectDistinctOperators(shopId);
        return rows != null ? rows : Collections.emptyList();
    }

    @Override
    public List<StockGoodsPickVO> listGoodsOptions(Long shopId, String keyword)
    {
        List<BizGoods> goodsList = goodsMapper.selectOutboundPickList(shopId, keyword);
        return toPickVoList(goodsList);
    }

    @Override
    public List<StockGoodsPickVO> listGoodsByBarcode(Long shopId, String barcode)
    {
        if (StringUtils.isEmpty(barcode))
        {
            throw new ServiceException(StockConstants.MSG_BARCODE_REQUIRED);
        }
        List<BizGoods> goodsList = goodsMapper.selectByGoodsSnAndShop(shopId, barcode.trim());
        if (goodsList == null || goodsList.isEmpty())
        {
            throw new ServiceException(StockConstants.MSG_BARCODE_NOT_FOUND);
        }
        List<StockGoodsPickVO> result = new ArrayList<>();
        for (BizGoods goods : goodsList)
        {
            if (goods.getStock() == null || goods.getStock() <= 0)
            {
                continue;
            }
            result.add(toPickVo(goods));
        }
        if (result.isEmpty())
        {
            throw new ServiceException(StockConstants.MSG_OUTBOUND_NO_STOCK);
        }
        return result;
    }

    private List<StockGoodsPickVO> toPickVoList(List<BizGoods> goodsList)
    {
        if (goodsList == null || goodsList.isEmpty())
        {
            return Collections.emptyList();
        }
        List<StockGoodsPickVO> result = new ArrayList<>();
        for (BizGoods goods : goodsList)
        {
            result.add(toPickVo(goods));
        }
        return result;
    }

    private StockGoodsPickVO toPickVo(BizGoods goods)
    {
        StockGoodsPickVO vo = new StockGoodsPickVO();
        vo.setGoodsId(goods.getGoodsId());
        vo.setGoodsName(goods.getGoodsName());
        vo.setMainPic(goods.getMainPic());
        vo.setStock(goods.getStock());
        vo.setGoodsSn(goods.getGoodsSn());
        vo.setSpecText(stockSupport.normalizeSpecText(stockSupport.buildSpecText(goods.getGoodsId())));
        return vo;
    }

    private void validateCreateDto(StockOutboundCreateDTO dto)
    {
        if (dto == null || StringUtils.isEmpty(dto.getOutboundType()))
        {
            throw new ServiceException(StockConstants.MSG_OUTBOUND_TYPE_REQUIRED);
        }
        if (!StockConstants.OUTBOUND_TYPE_MANUAL_ORDER.equals(dto.getOutboundType())
                && !StockConstants.OUTBOUND_TYPE_EXPIRED.equals(dto.getOutboundType())
                && !StockConstants.OUTBOUND_TYPE_OTHER.equals(dto.getOutboundType()))
        {
            throw new ServiceException(StockConstants.MSG_OUTBOUND_TYPE_INVALID);
        }
        if (dto.getItems() == null || dto.getItems().isEmpty())
        {
            throw new ServiceException(StockConstants.MSG_OUTBOUND_ITEMS_REQUIRED);
        }
        String remark = dto.getRemark();
        if (StringUtils.isNotEmpty(remark) && remark.length() > StockConstants.MAX_REMARK_LENGTH)
        {
            throw new ServiceException(StockConstants.MSG_REMARK_TOO_LONG);
        }
        for (StockOutboundCreateItemDTO item : dto.getItems())
        {
            if (item == null || item.getGoodsId() == null)
            {
                throw new ServiceException(StockConstants.MSG_OUTBOUND_GOODS_INVALID);
            }
            if (item.getQuantity() == null || item.getQuantity() < 1)
            {
                throw new ServiceException(StockConstants.MSG_OUTBOUND_QTY_INVALID);
            }
        }
    }

    private Map<Long, Integer> mergeItems(List<StockOutboundCreateItemDTO> items)
    {
        Map<Long, Integer> merged = new LinkedHashMap<>();
        for (StockOutboundCreateItemDTO item : items)
        {
            merged.merge(item.getGoodsId(), item.getQuantity(), Integer::sum);
        }
        return merged;
    }

    static void insertOutboundLog(Long shopId, Long goodsId, String goodsName, String specText, String changeReason,
            Integer changeQty, Integer stockBefore, Integer stockAfter, Long refId, String refNo, Long operatorId,
            String operatorName, Date now, IStockLogService stockLogService, StockSupport stockSupport)
    {
        BizStockLog log = new BizStockLog();
        log.setShopId(shopId);
        log.setGoodsId(goodsId);
        log.setGoodsName(goodsName);
        log.setSpecText(stockSupport.normalizeSpecText(specText));
        log.setDirection(StockConstants.DIRECTION_OUT);
        log.setChangeReason(changeReason);
        log.setChangeQty(changeQty);
        log.setStockBefore(stockBefore);
        log.setStockAfter(stockAfter);
        log.setRefType(StockConstants.REF_TYPE_OUTBOUND);
        log.setRefId(refId);
        log.setRefNo(refNo);
        log.setOperatorId(operatorId);
        log.setOperatorName(StringUtils.nvl(operatorName, ""));
        log.setCreateTime(now);
        stockLogService.insertLog(log);
    }

    private void insertOutboundLog(Long shopId, Long goodsId, String goodsName, String specText, String changeReason,
            Integer changeQty, Integer stockBefore, Integer stockAfter, Long refId, String refNo, Long operatorId,
            String operatorName, Date now)
    {
        insertOutboundLog(shopId, goodsId, goodsName, specText, changeReason, changeQty, stockBefore, stockAfter,
                refId, refNo, operatorId, operatorName, now, stockLogService, stockSupport);
    }
}
