package com.hys.app.service.erp.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hys.app.framework.context.user.AdminUserContext;
import com.hys.app.framework.database.WebPage;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.framework.rabbitmq.MessageSender;
import com.hys.app.framework.rabbitmq.MqMessage;
import com.hys.app.framework.util.BeanUtils;
import com.hys.app.framework.util.DateUtil;
import com.hys.app.framework.util.PageConvert;
import com.hys.app.framework.util.StringUtil;
import com.hys.app.mapper.erp.StockDamageReportMapper;
import com.hys.app.mapper.erp.WarehouseOutMapper;
import com.hys.app.model.base.rabbitmq.AmqpExchange;
import com.hys.app.model.erp.dos.*;
import com.hys.app.model.erp.dto.*;
import com.hys.app.model.erp.dto.message.StockDamageAuditPassMessage;
import com.hys.app.model.erp.enums.*;
import com.hys.app.model.erp.vo.GoodsVO;
import com.hys.app.model.erp.vo.ProductVO;
import com.hys.app.model.erp.vo.StockDamageReportStatistics;
import com.hys.app.model.erp.vo.StockDamageReportVO;
import com.hys.app.service.erp.*;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 库存报损单业务接口实现类
 *
 * @author dmy
 * 2023-12-05
 */
@Service
@AllArgsConstructor
public class StockDamageReportManagerImpl extends ServiceImpl<StockDamageReportMapper, StockDamageReport> implements StockDamageReportManager {

    private NoGenerateManager noGenerateManager;
    private StockDamageReportProductManager stockDamageReportProductManager;
    private StockDamageReportMapper stockDamageReportMapper;
    private MessageSender messageSender;
    private WarehouseEntryBatchManager warehouseEntryBatchManager;
    private ProductStockManager productStockManager;
    private GoodsManager goodsManager;
    private ProductManager productManager;
    private WarehouseOutMapper warehouseOutMapper;
    private WarehouseOutItemManager warehouseOutItemManager;
    private LockedStockDetailManager lockedStockDetailManager;

    /**
     * 查询库存报损单分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage list(StockDamageReportParam params) {
        Page<StockDamageReport> page = page(new Page<>(params.getPageNo(), params.getPageSize()), new LambdaQueryWrapper<StockDamageReport>()
                .eq(ObjectUtil.isNotNull(params.getWarehouseId()), StockDamageReport::getWarehouseId, params.getWarehouseId())
                .eq(ObjectUtil.isNotNull(AdminUserContext.getCompanyId()), StockDamageReport::getCompanyId, AdminUserContext.getCompanyId())
                .between(ObjectUtil.isNotNull(params.getStartTime()) && ObjectUtil.isNotNull(params.getEndTime()), StockDamageReport::getReportTime, params.getStartTime(), params.getEndTime()));
        return PageConvert.convert(page);
    }

    /**
     * 新增库存报损单
     *
     * @param stockDamageReportDTO 库存报损单信息
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void add(StockDamageReportDTO stockDamageReportDTO) {
        //报损数量不能大于库存数量
        List<Long> collect = stockDamageReportDTO.getProductList().stream().map(StockDamageReportProductDTO::getProductId).collect(Collectors.toList());
        List<ProductStockDO> list = productStockManager.list(new LambdaQueryWrapper<ProductStockDO>()
                .eq(ProductStockDO::getWarehouseId, stockDamageReportDTO.getWarehouseId())
                .in(ProductStockDO::getProductId, collect));
        StockDamageReport report = BeanUtil.copyProperties(stockDamageReportDTO, StockDamageReport.class);
        //生成库存报损单编号
        String sn = noGenerateManager.generate(NoBusinessTypeEnum.StockDamageReport);
        report.setSn(sn);
        save(report);

        List<StockDamageReportProduct> bean = BeanUtils.toBean(stockDamageReportDTO.getProductList(), StockDamageReportProduct.class);
        bean.forEach(item ->item.setReportId(report.getId()));
        stockDamageReportProductManager.saveBatch(bean);


        List<StockDTO> arrayList = new ArrayList<>();
        //保存凍結库存明细
        List<LockedStockDetailDO> detailDOS = new ArrayList<>();
        for (StockDamageReportProduct dto : bean) {
            ProductStockDO aDo = list.stream().filter(item -> item.getProductId().equals(dto.getProductId())).findFirst().get();
            if (aDo.getActualStock() < dto.getReportNum()) {
                throw new ServiceException("报损数量不能能大于可用库存数量");
            }
            StockDTO stockDTO = new StockDTO();
            stockDTO.setId(dto.getProductStockId());
            stockDTO.setNum(dto.getReportNum());
            arrayList.add(stockDTO);
            LockedStockDetailDO detailDO = new LockedStockDetailDO();
            detailDO.setProductStockId(dto.getProductStockId());
            detailDO.setOrderId(report.getId());
            detailDO.setOrderSn(report.getSn());
            detailDO.setOrderItemId(dto.getId());
            detailDO.setOrderType(DetailOrderTypeEnum.DAMAGE);
            detailDO.setLockedNum(dto.getReportNum());
            detailDO.setLockedTime(new Date());
            detailDOS.add(detailDO);
        }
        lockedStockDetailManager.saveBatch(detailDOS);
        productStockManager.updateLockedStock(arrayList);
        //生成出库单
        WarehouseOutDO outDO = new WarehouseOutDO();
        outDO.setType(WarehouseOutTypeEnum.DAMAGE_OUT);
        outDO.setOrderIdList(report.getId());
        outDO.setWarehouseId(report.getWarehouseId());
        outDO.setNotes("报损单生成的出货单");
        outDO.setSn(noGenerateManager.generate(NoBusinessTypeEnum.WarehouseOut, 66L));
        warehouseOutMapper.insert(outDO);
        //保存出库单明细
        List<WarehouseOutItemDO> beanList = BeanUtils.toBean(bean, WarehouseOutItemDO.class);
        beanList.forEach(item ->{
            item.setOrderItemId(item.getId());
            item.setOrderId(report.getId());
            item.setWarehouseOutId(outDO.getId());
            Integer num = stockDamageReportDTO.getProductList().stream().filter(it -> it.getProductId().equals(item.getProductId())).findFirst().get().getReportNum();
            item.setOutNum(num);
        });
        warehouseOutItemManager.saveBatch(beanList);



    }


    /**
     * 获取库存报损单详情
     *
     * @param id 主键ID
     * @return
     */
    @Override
    public StockDamageReportVO getDetail(Long id) {
        StockDamageReport report = this.getById(id);
        StockDamageReportVO reportVO = BeanUtil.copyProperties(report, StockDamageReportVO.class);
        //获取库存盘点单商品信息
        List<StockDamageReportProduct> goodsList = this.stockDamageReportProductManager.list(id);
        List<Long> collect = goodsList.stream().map(StockDamageReportProduct::getProductId).collect(Collectors.toList());
        List<ProductStockDO> page = productStockManager.list(new LambdaQueryWrapper<ProductStockDO>()
                .eq(ProductStockDO::getWarehouseId, report.getWarehouseId())
                .in(ProductStockDO::getProductId, collect));
        List<StockDamageReportProductDTO> beanList = BeanUtils.toBean(goodsList, StockDamageReportProductDTO.class);
        for (StockDamageReportProductDTO dto : beanList) {
            ProductStockDO aDo = page.stream().filter(item -> item.getProductId().equals(dto.getProductId())).findFirst().get();
            GoodsVO goodsVO = BeanUtil.copyProperties(goodsManager.getById(aDo.getGoodsId()), GoodsVO.class);
            dto.setGoodsVO(goodsVO);
            dto.setUsableStock(aDo.getUsableStock());
            dto.setProductVO(BeanUtil.copyProperties(productManager.getById(aDo.getProductId()), ProductVO.class));
        }
        reportVO.setProductList(beanList);
        return reportVO;
    }


    /**
     * 更改原订单状态
     *
     * @param id
     */
    @Override
    public void syncStock(Long id) {
        StockDamageReport report = getById(id);
        report.setStatus(DamageinventoryEnum.HAS_HANDLE);
        updateById(report);
        List<StockDamageReportProduct> productList = stockDamageReportProductManager.list(id);
        List<Long> collect = productList.stream().map(StockDamageReportProduct::getProductId).collect(Collectors.toList());
        List<ProductStockDO> list = productStockManager.list(new LambdaQueryWrapper<ProductStockDO>()
                .eq(ProductStockDO::getWarehouseId, report.getWarehouseId())
                .in(ProductStockDO::getProductId, collect));

        for (ProductStockDO stockDO : list) {
            StockDamageReportProduct product = productList.stream().filter(item -> item.getProductId().equals(stockDO.getProductId())).findFirst().get();
            stockDO.setActualStock(stockDO.getActualStock() - product.getReportNum());
            stockDO.setUsableStock(stockDO.getUsableStock() - product.getReportNum());
        }
        productStockManager.updateBatchById(list);
    }


    /**
     * 编辑库存报损单
     *
     * @param id                   主键ID
     * @param stockDamageReportDTO 库存报损单信息
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void edit(Long id, StockDamageReportDTO stockDamageReportDTO) {
        //获取库存报损单旧数据
        StockDamageReport stockDamageReport = this.getById(id);
        //复制修改后的数据
        //修改库存报损单信息
    }

    /**
     * 删除库存报损单
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要删除的库存报损单不存在");
        }
        //验证库存报损单状态是否可以删除
        for (StockDamageReport report : reports) {
            throw new ServiceException("只有状态为未提交或审核驳回的库存报损单才可进行删除操作");
        }
        //批量删除库存报损单
        this.removeByIds(ids);
        //删除库存报损单商品信息
        this.stockDamageReportProductManager.deleteProduct(ids);
    }


    /**
     * 库存报损单提交审核
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void submit(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要提交审核的库存报损单不存在");
        }
        //验证库存报损单状态是否可以提交审核
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.NEW.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为未提交的库存报损单才可进行提交审核操作");
            }
        }
        //将库存报损单状态修改为已提交（待审核）
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, StockDamageReportStatusEnum.WAIT.name())
                .in(StockDamageReport::getId, ids)
                .update();
    }

    /**
     * 库存报损单撤销提交审核
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void cancel(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要撤销审核的库存报损单不存在");
        }
        //验证库存报损单状态是否可以撤销审核
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.WAIT.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为已提交的库存报损单才可进行撤销操作");
            }
        }
        //将库存报损单状态修改为未提交
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, StockDamageReportStatusEnum.NEW.name())
                .in(StockDamageReport::getId, ids)
                .update();
    }

    /**
     * 审核库存报损单
     *
     * @param ids          库存报损单ID集合
     * @param status       审核状态 PASS：审核通过，REJECT：审核驳回
     * @param rejectReason 驳回原因
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void audit(List<Long> ids, String status, String rejectReason) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要审核的库存报损单不存在");
        }
        if (!StockDamageReportStatusEnum.PASS.name().equals(status)
                && !StockDamageReportStatusEnum.REJECT.name().equals(status)) {
            throw new ServiceException("审核状态不正确");
        }
        if (StockDamageReportStatusEnum.REJECT.name().equals(status)) {
            if (StringUtil.isEmpty(rejectReason)) {
                throw new ServiceException("请填写审核驳回原因");
            }
            if (rejectReason.length() > 200) {
                throw new ServiceException("驳回原因不能超过200个字符");
            }
        }
        //循环审核库存报损单
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.WAIT.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为待审核的库存报损单才可以进行审核操作");
            }
        }

        //修改库存报损单状态
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, status)
                .set(StringUtil.notEmpty(rejectReason), StockDamageReport::getRejectReason, rejectReason)
                .set(StockDamageReport::getAuditTime, DateUtil.getDateline())
                .in(StockDamageReport::getId, ids)
                .update();

        // 如果审核通过
        if (StockDamageReportStatusEnum.PASS.name().equals(status)) {
            for (StockDamageReport report : reports) {
                List<StockDamageReportProduct> transferProductList = stockDamageReportProductManager.list(report.getId());

                List<StockUpdateDTO> updateList = new ArrayList<>();
                for (StockDamageReportProduct product : transferProductList) {
                    StockUpdateDTO stockUpdateDTO = new StockUpdateDTO();
                    stockUpdateDTO.setBatchId(product.getBatchId());
                    stockUpdateDTO.setTip(product.getBatchSn());
                    stockUpdateDTO.setChangeNum(product.getReportNum());
                    stockUpdateDTO.setOperate(product.getType() == 0 ? StockOperateEnum.Increase : StockOperateEnum.Reduce);
                    updateList.add(stockUpdateDTO);
                }
                this.warehouseEntryBatchManager.updateStock(StockChangeSourceEnum.STOCK_DAMAGE, report.getSn(), updateList);
            }

            // 发送审核通过消息
            StockDamageAuditPassMessage message = new StockDamageAuditPassMessage();
            message.setList(reports);
            this.messageSender.send(new MqMessage(AmqpExchange.STOCK_DAMAGE_AUDIT_PASS, AmqpExchange.STOCK_DAMAGE_AUDIT_PASS + "_ROUTING", message));
        }

    }

    /**
     * 查询库存报损单商品统计分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage statistics(StockDamageReportStatisticsParam params) {
        IPage<StockDamageReportStatistics> iPage = this.stockDamageReportMapper.selectReportPage(new Page(params.getPageNo(), params.getPageSize()), params);
        return PageConvert.convert(iPage);
    }

    /**
     * 导出库存报损单商品统计列表
     *
     * @param response
     * @param params   查询参数
     */
    @Override
    public void export(HttpServletResponse response, StockDamageReportStatisticsParam params) {
        //查询库存报损单商品列表
        List<StockDamageReportStatistics> list = this.stockDamageReportMapper.selectReportList(params);

        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList();
        ExcelWriter writer = ExcelUtil.getWriter(true);
        for (StockDamageReportStatistics statistics : list) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("库存报损单号", statistics.getSn());
            map.put("报损时间", DateUtil.toString(statistics.getReportTime(), "yyyy-MM-dd HH:mm:ss"));
            map.put("入库单号", statistics.getStockSn());
            map.put("商品编号", statistics.getProductSn());
            map.put("商品名称", statistics.getProductName());
            map.put("商品类别", statistics.getCategoryName());
            map.put("规格型号", statistics.getSpecification());
            map.put("单位", statistics.getUnit());
            map.put("报损类型", statistics.getDamageTypeKey());
            map.put("报损数量", statistics.getReportNum());
            rows.add(map);
        }

        writer.write(rows, true);

        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=stock_damage_report.xlsx");
        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);
    }


}
