package com.fjhx.wms.service.stock.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fjhx.common.entity.AvailableStockBo;
import com.fjhx.common.entity.InOutBo;
import com.fjhx.common.enums.InOutType;
import com.fjhx.common.utils.Assert;
import com.fjhx.item.entity.product.po.ProductClassify;
import com.fjhx.item.entity.product.po.ProductInfo;
import com.fjhx.item.entity.product.po.ProductStockInfo;
import com.fjhx.item.enums.ProductAvailableRecordType;
import com.fjhx.item.service.product.ProductClassifyService;
import com.fjhx.item.service.product.ProductInfoService;
import com.fjhx.item.service.product.ProductStockInfoService;
import com.fjhx.wms.entity.stock.dto.StockDto;
import com.fjhx.wms.entity.stock.dto.StockJournalDetailsDto;
import com.fjhx.wms.entity.stock.dto.StockSelectDto;
import com.fjhx.wms.entity.stock.emums.JournalType;
import com.fjhx.wms.entity.stock.po.Stock;
import com.fjhx.wms.entity.stock.po.StockJournal;
import com.fjhx.wms.entity.stock.po.StockJournalDetails;
import com.fjhx.wms.entity.stock.vo.StockVo;
import com.fjhx.wms.entity.warehouse.po.Warehouse;
import com.fjhx.wms.mapper.stock.StockMapper;
import com.fjhx.wms.service.stock.StockJournalDetailsService;
import com.fjhx.wms.service.stock.StockJournalService;
import com.fjhx.wms.service.stock.StockService;
import com.fjhx.wms.service.warehouse.WarehouseService;
import com.fjhx.wms.utils.CodeEnum;
import com.ruoyi.common.core.domain.BaseIdPo;
import com.ruoyi.common.core.domain.BasePo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.wrapper.IWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * <p>
 * 库存 服务实现类
 * </p>
 *
 * @author
 * @since 2023-03-20
 */
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements StockService {
    @Autowired
    private StockJournalService stockJournalService;
    @Autowired
    private StockJournalDetailsService stockJournalDetailsService;
    @Autowired
    private ProductInfoService productInfoService;
    @Autowired
    private ProductClassifyService productClassifyService;
    @Autowired
    private WarehouseService warehouseService;
    @Autowired
    private ProductStockInfoService productStockInfoService;

    @Override
    public Page<StockVo> getPage(StockSelectDto dto) {
        IWrapper<Stock> wrapper = getWrapper();
        wrapper.eq(Stock::getWarehouseId, dto.getId());
        String keyword = dto.getKeyword();
        if (ObjectUtil.isNotEmpty(keyword)) {
            //根据 库存数量 产品自定义编码 产品名称 过滤
            List<Long> productIds = productInfoService.listObject(ProductInfo::getId,
                    q -> q.like(ProductInfo::getCustomCode, keyword).or().like(ProductInfo::getName, keyword));
            wrapper.and(q -> q.like(Stock::getQuantity, keyword).or().in(Stock::getProductId, productIds));
        }
        wrapper.in(Stock::getProductId, dto.getProductIds());
        wrapper.eq("pi.definition", dto.getDefinition());

        wrapper.eq("pi.product_classify_id", dto.getProductClassifyId());

        //权限过滤：库存-子公司看自己的,总公司看全部
        Long companyId = SecurityUtils.getCompanyId();
        if (!Objects.equals(companyId, 100L)) {
            wrapper.eq("s.company_id", companyId);
        } else {
            wrapper.eq("s.company_id", dto.getCompanyId());
        }

        wrapper.orderByDesc("s", Stock::getId);
        Page<StockVo> page = this.baseMapper.getPage(dto.getPage(), wrapper);
        List<StockVo> stockVos = page.getRecords();
        //赋值产品信息
        productInfoService.attributeAssign(stockVos, StockVo::getProductId, (item, productInfo) -> {
            item.setProductCode(productInfo.getCode());
            item.setProductUnit(productInfo.getUnit());
            item.setProductType(productInfo.getType());
            item.setProductName(productInfo.getName());
            item.setProductSpec(productInfo.getSpec());
            item.setProductClassifyId(productInfo.getProductClassifyId());
            item.setProductDefinition(productInfo.getDefinition());
            item.setProductCustomCode(productInfo.getCustomCode());

            item.setProductLength(productInfo.getLength());
            item.setProductWidth(productInfo.getWidth());
            item.setProductHeight(productInfo.getHeight());
        });
        //赋值产品分类
        productClassifyService.attributeAssign(stockVos, StockVo::getProductClassifyId, (item, productClassify) -> {
            item.setProductClassifyName(productClassify.getName());
        });
        //赋值产品分类树
        List<ProductClassify> productClassifyList = productClassifyService.list();
        Map<Long, ProductClassify> productClassifyMap = productClassifyList.stream().collect(Collectors.toMap(BaseIdPo::getId, Function.identity()));
        for (StockVo stockVo : stockVos) {
            Long productClassifyId = stockVo.getProductClassifyId();
            ProductClassify productClassify = productClassifyMap.get(productClassifyId);
            if (productClassify == null) {
                continue;
            }
            List<String> classifyNameGroup = new ArrayList<>();

            while (productClassify != null) {
                classifyNameGroup.add(0, productClassify.getName());
                productClassify = productClassifyMap.get(productClassify.getParentId());
            }
            stockVo.setProductClassifyNameGroup(classifyNameGroup);
            stockVo.setProductClassifyNames(classifyNameGroup.stream().collect(Collectors.joining(" / ")));
        }

        //赋值仓库名称
        warehouseService.attributeAssign(stockVos, StockVo::getWarehouseId, (item, warehouse) -> {
            item.setWarehouseName(warehouse.getName());
        });
        return page;
    }

    @Override
    public Stock detail(StockDto dto) {
        return getOne(q -> q.eq(Stock::getWarehouseId, dto.getWarehouseId()).eq(Stock::getProductId, dto.getProductId()));
    }

    @Override
    @DSTransactional
    public void add(StockDto stockDto) {
        //根据前端传的类型获取出入库流水类型
        JournalType journalType = JournalType.getByDetailType(stockDto.getType());

        InOutType inOutType = journalType.getType();

        Long warehouseId = stockDto.getWarehouseId();
        Warehouse warehouse = warehouseService.getById(warehouseId);
        if (ObjectUtil.isEmpty(warehouse)) {
            throw new ServiceException("查询不到仓库信息！");
        }
        Long companyId = warehouse.getCompanyId();

        //创建出入库记录
        StockJournal stockJournal = new StockJournal();
        stockJournal.setId(IdWorker.getId());
        stockJournal.setOpType(inOutType == InOutType.IN ? 1 : 2);
        stockJournal.setType(journalType.getDetailType());
        stockJournal.setCode(stockDto.getType() == 1 ? CodeEnum.SIN_CODE.getCode() : CodeEnum.SOUT_CODE.getCode());
        stockJournal.setWarehouseId(warehouseId);
        stockJournal.setBusinessId(stockDto.getBusinessId());
        stockJournal.setCompanyId(companyId);
        stockJournal.setRemarks(stockDto.getRemarks());
        stockJournal.setExWarehousePerson(stockDto.getExWarehousePerson());
        //赋值流水信息
        editStockJournalInfo(stockDto, stockJournal);
        //保存出入库记录
        stockJournalService.save(stockJournal);
        List<Stock> list = stockDto.getList();
        List<StockJournalDetails> stockJournalDetailsList = new ArrayList<>();
        for (Stock stock : list) {
            //创建出入库明细
            StockJournalDetails stockJournalDetails = new StockJournalDetailsDto();
            stockJournalDetails.setStockJournalId(stockJournal.getId());
            stockJournalDetails.setProductId(stock.getProductId());
            stockJournalDetails.setQuantity(stock.getQuantity());
            stockJournalDetails.setBusinessDetailsId(stock.getId());
            stockJournalDetails.setCompanyId(companyId);
            stockJournalDetailsList.add(stockJournalDetails);
        }
        //操作库存
        List<InOutBo> inOutBoList = BeanUtil.copyToList(list, InOutBo.class);
        changeStock(inOutBoList, warehouseId, journalType);

        //保存出入库明细
        stockJournalDetailsService.saveBatch(stockJournalDetailsList);

        //修改可用库存
        List<AvailableStockBo> AvailableStockBoList = BeanUtil.copyToList(list, AvailableStockBo.class);
        ProductAvailableRecordType productAvailableRecordType = inOutType.equals(InOutType.IN) ? ProductAvailableRecordType.HAND_IN : ProductAvailableRecordType.HAND_OUT;
        productInfoService.editAvailableQuantity(AvailableStockBoList, null, productAvailableRecordType, SecurityUtils.getCompanyId());
    }

    /**
     * 手动出入库参数校验赋值
     */
    private void editStockJournalInfo(StockDto stockDto, StockJournal stockJournal) {
        JournalType byDetailType = JournalType.getByDetailType(stockDto.getType());
        //生产打样,生产超领,退料入库,废料入库
        stockJournal.setProdOrderId(stockDto.getProdOrderId());

        //借用出库
        if (JournalType.BORROW_OUT.equals(byDetailType)) {
            Date expectRestitutionTime = stockDto.getExpectRestitutionTime();
            Assert.notEmpty(expectRestitutionTime, "预计归还时间不能为空");
            stockJournal.setExpectRestitutionTime(expectRestitutionTime);
        }
        //归还入库
        if (JournalType.RESTITUTION_IN.equals(byDetailType)) {
            Long borrowId = stockDto.getBorrowId();
            Assert.notEmpty(borrowId, "借用id不能为空");
            StockJournal borrowJournal = stockJournalService.getById(borrowId);
            Assert.notEmpty(borrowJournal, "查询不到借用信息");
            //赋值归还id
            borrowJournal.setRestitutionId(stockJournal.getId());
            borrowJournal.setRestitutionTime(new Date());
            stockJournalService.updateById(borrowJournal);
            //赋值借用id
            stockJournal.setBorrowId(borrowId);
        }
        //丢件寻回
        if (JournalType.LOSE_IN.equals(byDetailType)) {
            Long loseId = stockDto.getLoseId();
            Assert.notEmpty(loseId, "丢件id不能为空");
//            StockJournal loseIdJournal = stockJournalService.getById(loseId);
//            Assert.notEmpty(loseIdJournal, "查询不到丢件信息");
//            //赋值寻回Id
//            loseIdJournal.setSeekId(stockJournal.getId());
            //赋值丢件id
            stockJournal.setLoseId(loseId);
        }
    }

    @Override
    @DSTransactional
    public void edit(StockDto stockDto) {
        add(stockDto);
    }

    @Override
    public void delete(Long id) {
        this.removeById(id);
    }

    /**
     * 改变库存数量
     */
    @Override
    public synchronized void changeStock(List<? extends InOutBo> list, Long warehouseId, JournalType journalType) {
        Warehouse warehouse = warehouseService.getById(warehouseId);
        if (ObjectUtil.isEmpty(warehouse)) {
            throw new ServiceException("仓库不存在");
        }
        //获取归属公司id
        Long companyId = warehouse.getCompanyId();

        Map<Long, BigDecimal> map = list.stream().collect(Collectors.toMap(
                InOutBo::getProductId,
                InOutBo::getQuantity,
                BigDecimal::add
        ));

        if (journalType.getType().equals(InOutType.IN)) {
            map.forEach((productId, quantity) -> {
                Stock stock = getOne(q -> q.eq(Stock::getProductId, productId).eq(Stock::getWarehouseId, warehouseId).eq(Stock::getCompanyId, companyId));

                if (stock == null) {
                    stock = new Stock();
                    stock.setWarehouseId(warehouseId);
                    stock.setProductId(productId);
                    stock.setQuantity(quantity);
                    stock.setCompanyId(companyId);
                    save(stock);
                } else {
                    Long stockId = stock.getId();
                    update(q -> q
                            .eq(BaseIdPo::getId, stockId)
                            .setSql("quantity = quantity + " + quantity)
                    );
                }
            });
        } else {
            map.forEach((productId, quantity) -> {
                boolean update = update(q -> q
                        .setSql("quantity = quantity - '" + quantity + "'")
                        .eq(Stock::getProductId, productId)
                        .eq(Stock::getWarehouseId, warehouseId)
                        .eq(Stock::getCompanyId, companyId)
                        .apply("quantity - {0} >= 0", quantity)
                );

                if (!update) {
                    ProductInfo productInfo = productInfoService.getById(productId);
                    if (productInfo == null) {
                        throw new ServiceException("产品id：" + productId + "不存在");
                    }

                    String err = "产品 {} 库存不足，出库失败";
                    throw new ServiceException(StrUtil.format(err, productInfo.getName()));
                }
            });
        }
    }

    /**
     * 计算结存单价(在操作库存之前计算)
     */
    @Override
    public synchronized StockJournalDetails calculateUnitPrice(InOutType inOutType, Long productId, Long
            warehouseId, BigDecimal inOutQuantity, Integer purchaseArrival, BigDecimal price) {
        Warehouse warehouse = warehouseService.getById(warehouseId);
        if (ObjectUtil.isEmpty(warehouse)) {
            throw new ServiceException("查询不到仓库信息");
        }
        Long companyId = warehouse.getCompanyId();

        StockJournalDetails stockJournalDetails = new StockJournalDetails();
        stockJournalDetails.setProductId(productId);
        stockJournalDetails.setQuantity(inOutQuantity);

        //计算结存单价(在操作库存之前计算)
        ProductStockInfo productStockInfo = productStockInfoService.getOne(q -> q
                .eq(ProductStockInfo::getProductId, productId)
                .eq(ProductStockInfo::getCompanyId, companyId)
        );
        if (ObjectUtil.isEmpty(productStockInfo)) {
            throw new ServiceException("查询不到产品信息");
        }
        //在库结存单价(实时在库单价)
        BigDecimal unitPrice = productStockInfo.getUnitPrice();
        unitPrice = ObjectUtil.isEmpty(unitPrice) ? BigDecimal.ZERO : unitPrice;
        //不在库结存单价(实时不在库单价)
        BigDecimal outUnitPrice = productStockInfo.getOutUnitPrice();
        outUnitPrice = ObjectUtil.isEmpty(outUnitPrice) ? BigDecimal.ZERO : outUnitPrice;
        List<Stock> stockList = this.list(q -> q.eq(Stock::getProductId, productId).eq(Stock::getCompanyId, companyId));
        //在库总库存（所有仓库）
        BigDecimal stockQuantity = stockList.stream().map(Stock::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
        //不在库总库存（所有仓库）
        BigDecimal stockOutQuantity = stockList.stream().map(Stock::getOutQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取产品当前仓库信息
        Stock stock = this.getOne(q -> q.eq(Stock::getWarehouseId, warehouseId).eq(Stock::getProductId, productId).eq(Stock::getCompanyId, companyId));
        //库存不存在创建0库存记录
        if (ObjectUtil.isEmpty(stock)) {
            stock = new Stock();
            stock.setWarehouseId(warehouseId);
            stock.setProductId(productId);
            stock.setQuantity(BigDecimal.ZERO);
            stock.setCompanyId(companyId);
            this.save(stock);
        }
        Stock finalStock = stock;
        if (inOutType.equals(InOutType.IN)) {
            //入库
            //有单价【采购入库等】按单价算,没有按【其他入库】当前价值=实时不在库单价
            if (price == null) {
                price = outUnitPrice;
            }

            //【入库】实时在库单价=（库存*实时在库单价 + 当前入库数量*当前价值） / （库存+当前入库数量）
            BigDecimal multiply0 = stockQuantity.multiply(unitPrice);//库存*实时在库单价
            BigDecimal multiply1 = inOutQuantity.multiply(price);//当前入库数量*当前价值
            BigDecimal add0 = multiply0.add(multiply1);//（库存*实时在库单价 + 当前入库数量*当前价值）
            BigDecimal add1 = stockQuantity.add(inOutQuantity);//（库存+当前入库数量）
            BigDecimal newUnitPrice = add0.divide(add1, 4, RoundingMode.HALF_UP);

            //【其他入库】已出库（为负数时取0） = 已出库 - 当前入库数量
            if (purchaseArrival != 1) {
                String format = String.format("out_quantity = IF((out_quantity - %s)<0,0,(out_quantity - %s))", inOutQuantity, inOutQuantity);
                this.update(q -> q
                        .eq(Stock::getId, finalStock.getId())
                        .eq(Stock::getCompanyId, companyId)
                        .setSql(format)
                        .set(BasePo::getUpdateTime, new Date())
                        .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
                );
            }
            //修改产品中的库存单价
            productStockInfoService.update(q -> q
                    .eq(ProductStockInfo::getProductId, productId)
                    .eq(ProductStockInfo::getCompanyId, companyId)
                    .set(ProductStockInfo::getUnitPrice, newUnitPrice)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );

            //赋值流水信息
            stockJournalDetails.setPrice(price);
            stockJournalDetails.setStockPrice(newUnitPrice);
            //【入库】实时不在库单价=A物料最新流水的实时不在库单价（不用动直接获取产品的不在库单价）
            stockJournalDetails.setOutStockPrice(outUnitPrice);
        } else {
            //出库

            //有单价按单价算,没有按【出库】当前价值=实时在库单价
            if (price == null) {
                price = unitPrice;
            }

            //【出库】实时不在库单价=（不在库数量*实时不在库单价 + 当前出库数量*当前价值） / （不在库数量+当前出库数量）
            BigDecimal multiply0 = stockOutQuantity.multiply(outUnitPrice);//不在库数量*实时不在库单价
            BigDecimal multiply1 = inOutQuantity.multiply(price);//当前出库数量*当前价值
            BigDecimal add0 = multiply0.add(multiply1);//（不在库数量*实时不在库单价 + 当前出库数量*当前价值）
            BigDecimal add1 = stockOutQuantity.add(inOutQuantity);//（不在库数量+当前出库数量）
            BigDecimal newOutUnitPrice = add0.divide(add1, 4, RoundingMode.HALF_UP);

            //计算已出库库存-【出库】已出库 = 已出库+当前出库数量
            this.update(q -> q
                    .eq(Stock::getId, finalStock.getId())
                    .eq(Stock::getCompanyId, companyId)
                    .setSql("out_quantity = out_quantity + " + inOutQuantity)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );
            //修改产品中的已出库单价
            productStockInfoService.update(q -> q
                    .eq(ProductStockInfo::getProductId, productId)
                    .eq(ProductStockInfo::getCompanyId, companyId)
                    .set(ProductStockInfo::getOutUnitPrice, newOutUnitPrice)
                    .set(BasePo::getUpdateTime, new Date())
                    .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
            );

            //赋值流水信息
            stockJournalDetails.setPrice(price);
            //【出库】实时在库单价=A物料最新流水的实时在库单价（不用动直接获取产品的在库单价）
            stockJournalDetails.setStockPrice(unitPrice);
            stockJournalDetails.setOutStockPrice(newOutUnitPrice);
        }

        return stockJournalDetails;
    }

    @Override
    public Page<StockVo> stockWarningPage(StockSelectDto dto) {
        return baseMapper.stockWarningPage(dto.getPage(), SecurityUtils.getCompanyId());
    }

}
