
package com.tmerclub.cloud.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.auth.feign.AccountFeignClient;
import com.tmerclub.cloud.api.auth.vo.AuthAccountVO;
import com.tmerclub.cloud.api.delivery.feign.StationFeignClient;
import com.tmerclub.cloud.api.delivery.vo.StationVO;
import com.tmerclub.cloud.common.order.vo.WarehouseVO;
import com.tmerclub.cloud.api.product.vo.SkuStockVO;
import com.tmerclub.cloud.common.cache.constant.LuaOperateEnum;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.util.PageUtil;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.i18n.I18nMessage;
import com.tmerclub.cloud.common.product.constant.*;
import com.tmerclub.cloud.common.product.dto.SkuDTO;
import com.tmerclub.cloud.common.product.vo.SkuVO;
import com.tmerclub.cloud.common.product.vo.SpuVO;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.common.util.ExcelUtil;
import com.tmerclub.cloud.product.dto.TakeStockDTO;
import com.tmerclub.cloud.api.product.dto.WarehouseDTO;
import com.tmerclub.cloud.product.manager.StockManager;
import com.tmerclub.cloud.product.mapper.TakeStockMapper;
import com.tmerclub.cloud.product.model.StockBillLog;
import com.tmerclub.cloud.product.model.TakeStock;
import com.tmerclub.cloud.product.model.TakeStockSpu;
import com.tmerclub.cloud.product.service.*;
import com.tmerclub.cloud.product.util.StockUtil;
import com.tmerclub.cloud.product.vo.TakeStockExcelVO;
import com.tmerclub.cloud.product.vo.TakeStockVO;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author lhd
 * @date 2022-06-22 16:57:27
 */
@Service
public class TakeStockServiceImpl implements TakeStockService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TakeStockServiceImpl.class);

    @Autowired
    private TakeStockMapper takeStockMapper;
    @Autowired
    private TakeStockSpuService takeStockSpuService;
    @Autowired
    private SkuService skuService;
    @Autowired
    private SpuService spuService;
    @Autowired
    private SkuStockService skuStockService;

    @Autowired
    private StockManager stockManager;

    @Autowired
    private StockBillLogService stockBillLogService;
    @DubboReference
    private AccountFeignClient accountFeignClient;
    @Autowired
    private WarehouseService warehouseService;
    @DubboReference
    private StationFeignClient stationFeignClient;

    @Override
    public PageVO<TakeStock> page(PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> takeStockMapper.list(new TakeStockDTO()));
    }

    @Override
    public TakeStockVO getByTakeStockId(Long takeStockId) {
        TakeStock takeStock = takeStockMapper.getByTakeStockId(takeStockId);
        TakeStockVO takeStockVO = new TakeStockVO();
        BeanUtils.copyProperties(takeStock, takeStockVO);
        // 补充仓库名称
        WarehouseVO warehouse = null;
        if (!Objects.isNull(takeStockVO.getWarehouseId())) {
            warehouse = warehouseService.getByWarehouseId(takeStockVO.getWarehouseId());
        }
        if (!Objects.isNull(warehouse)) {
            takeStockVO.setWarehouseName(warehouse.getWarehouseName());
        } else {
            takeStockVO.setWarehouseName("默认仓库");
            takeStockVO.setStockPointType(StockPointType.WAREHOUSE.getValue());
        }
        return takeStockVO;
    }

    @Override
    public void save(TakeStock takeStock) {
        // 检查仓库
        WarehouseVO warehouse = this.checkWarehouse(takeStock.getWarehouseId(), takeStock.getShopId(), SysTypeEnum.MULTISHOP.value());
        takeStock.setStockPointType(Objects.equals(warehouse.getSysType(), SysTypeEnum.STATION.value()) ? StockPointType.STATION.getValue() : StockPointType.WAREHOUSE.getValue());
        takeStockMapper.save(takeStock);
    }

    @Override
    public void update(TakeStock takeStock) {
        takeStockMapper.update(takeStock);
    }

    @Override
    public void deleteById(Long takeStockId) {
        takeStockMapper.deleteById(takeStockId);
    }

    @Override
    public PageVO<TakeStockVO> pageByParam(PageDTO pageDTO, TakeStockDTO takeStockDTO) {
        PageVO<TakeStockVO> page = PageUtil.doPage(pageDTO, () -> takeStockMapper.pageByParam(I18nMessage.getLang(), takeStockDTO));
        // 数据准备
        List<TakeStockVO> takeStockList = page.getList();
        Set<Long> warehouseIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        for (TakeStockVO takeStockVO : takeStockList) {
            warehouseIds.add(takeStockVO.getWarehouseId());
        }
        // 查询仓库数据
        List<WarehouseVO> warehouses = new ArrayList<>(0);
        if (!CollectionUtils.isEmpty(warehouseIds)) {
            WarehouseDTO searchDTO = new WarehouseDTO();
            searchDTO.setWarehouseIds(new ArrayList<>(warehouseIds));
            warehouses = warehouseService.listWarehouse(searchDTO);
        }
        Map<Long, String> warehouseMap = new HashMap<>(Constant.INITIAL_CAPACITY);
        if (!CollectionUtils.isEmpty(warehouses)) {
            warehouseMap = warehouses.stream().collect(Collectors.toMap(WarehouseVO::getWarehouseId, WarehouseVO::getWarehouseName));
        }
        // 查询制单人信息
        Map<Long, String> userIdNameMap = getUserIdNameMap(takeStockList.stream().map(TakeStockVO::getMaker));
        // 信息补充or处理
        for (TakeStockVO takeStockVO : takeStockList) {
            // 补充制单人名称
            takeStockVO.setMakerName(userIdNameMap.get(takeStockVO.getMaker()));
            // 手机号码处理
            if (StrUtil.isNotBlank(takeStockVO.getMakerMobile())) {
                takeStockVO.setMakerMobile(PhoneUtil.hideBetween(takeStockVO.getMakerMobile()).toString());
            }
            // 补充仓库名称
            takeStockVO.setWarehouseName(warehouseMap.get(takeStockVO.getWarehouseId()));
            takeStockVO.setStockPointType(takeStockVO.getStockPointType() == null ? StockPointType.WAREHOUSE.getValue() : takeStockVO.getStockPointType());
        }
        return page;
    }

    private Map<Long, String> getUserIdNameMap(Stream<Long> takeStockList) {
        Set<Long> userIds = takeStockList.collect(Collectors.toSet());
        List<AuthAccountVO> accounts = accountFeignClient.listByUserIdsAndPhoneAndType(new ArrayList<>(userIds), null, SysTypeEnum.MULTISHOP.value()).getData();
        Map<Long, String> userIdNameMap = new HashMap<>(16);
        if (!CollectionUtils.isEmpty(accounts)) {
            userIdNameMap = accounts.stream().collect(Collectors.toMap(AuthAccountVO::getUserId, AuthAccountVO::getUsername));
        }
        return userIdNameMap;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateTakeStock(TakeStockDTO takeStockDTO) {
        // 校验实物盘点信息
        this.checkInfo(takeStockDTO);
        // 更新实物盘点
        TakeStock takeStock = new TakeStock();
        BeanUtils.copyProperties(takeStockDTO, takeStock);
        takeStockMapper.update(takeStock);
        // 补充商品盘盈盘亏状态
        if (!CollectionUtils.isEmpty(takeStockDTO.getTakeStockSpuList())) {
            for (TakeStockSpu takeStockSpu : takeStockDTO.getTakeStockSpuList()) {
                if (takeStockSpu.getStocks().compareTo(takeStockSpu.getTotalStock()) > 0) {
                    takeStockSpu.setIoType(TakeStockSpuStatus.LOSS.value());
                } else if (takeStockSpu.getStocks().compareTo(takeStockSpu.getTotalStock()) < 0) {
                    takeStockSpu.setIoType(TakeStockSpuStatus.PROFIT.value());
                } else {
                    takeStockSpu.setIoType(TakeStockSpuStatus.EQUAL.value());
                }
            }
        }
        // 更新实物盘点商品
        takeStockSpuService.updateByTakeStockDTO(takeStockDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void finishTakeStock(TakeStockDTO takeStockDTO) {
        // 数据校验
        TakeStock dbTakeStock = this.checkInfo(takeStockDTO);
        // 更新实物盘点
        TakeStock takeStock = new TakeStock();
        BeanUtils.copyProperties(takeStockDTO, takeStock);
        takeStock.setBillStatus(TakeStockBillStatus.COMPLETED.value());
        takeStockMapper.update(takeStock);
        // 更新库存并获取ioType分类
        if (CollectionUtils.isEmpty(takeStockDTO.getTakeStockSpuList())) {
            return;
        }
        Map<Integer, List<SkuDTO>> ioTypeSkuMap = this.updateSkuAndGetIoTypeMap(takeStockDTO.getTakeStockSpuList(), dbTakeStock.getWarehouseId());
        // 更新实物盘点商品
        takeStockSpuService.updateByTakeStockDTO(takeStockDTO);
        // 记录出入库信息
        StockBillLog stockBillLog;
        List<SkuStockVO> skuStocks = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<SkuDTO> skuDTOList = ioTypeSkuMap.get(TakeStockSpuStatus.PROFIT.value());
        if (!CollectionUtils.isEmpty(skuDTOList)) {
            // 盘盈记录
            stockBillLog = this.buildStockBillLog(dbTakeStock, StockBillType.PROFIT_STORAGE);
            stockBillLogService.generateStockLogOfTakeStock(stockBillLog, skuDTOList);
            for (SkuDTO skuDTO : skuDTOList) {
                skuStocks.add(this.buildSkuStock(skuDTO, dbTakeStock.getWarehouseId(), LuaOperateEnum.SKU_ADD));
            }
        }
        skuDTOList = ioTypeSkuMap.get(TakeStockSpuStatus.LOSS.value());
        if (!CollectionUtils.isEmpty(skuDTOList)) {
            // 盘亏记录
            stockBillLog = this.buildStockBillLog(dbTakeStock, StockBillType.LOSS_OUTBOUND);
            stockBillLogService.generateStockLogOfTakeStock(stockBillLog, skuDTOList);
            for (SkuDTO skuDTO : skuDTOList) {
                skuStocks.add(this.buildSkuStock(skuDTO, dbTakeStock.getWarehouseId(), LuaOperateEnum.SKU_SUB));
            }
        }
        // 更新库存
        if (!CollectionUtils.isEmpty(skuStocks)) {
            skuStockService.changeSkuStock(skuStocks);
        }
    }

    @Override
    public void nullifyByWarehouseId(Long warehouseId) {
        // 查找未完成/作废的实物盘点
        TakeStockDTO searchParam = new TakeStockDTO();
        searchParam.setWarehouseId(warehouseId);
        searchParam.setBillStatus(TakeStockBillStatus.TAKING.value());
        List<TakeStock> takeStocks = takeStockMapper.list(searchParam);
        if (CollectionUtils.isEmpty(takeStocks)) {
            return;
        }
        // 作废（直接更新状态）
        for (TakeStock takeStock : takeStocks) {
            takeStock.setBillStatus(TakeStockBillStatus.VOIDED.value());
            takeStockMapper.update(takeStock);
        }
    }

    @Override
    public void exportExcel(TakeStockDTO takeStockDTO, HttpServletResponse response) {
        // 获取excel导出数据
        List<TakeStockExcelVO> takeStockExcelVOList = takeStockMapper.listTakeStockExportData(takeStockDTO, I18nMessage.getLang());
        // 查询仓库信息
        Set<Long> warehouseIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        for (TakeStockExcelVO takeStockExcelVO : takeStockExcelVOList) {
            warehouseIds.add(takeStockExcelVO.getWarehouseId());
        }
        List<WarehouseVO> warehouses = new ArrayList<>(Constant.INITIAL_CAPACITY);
        if (!CollectionUtils.isEmpty(warehouseIds)) {
            WarehouseDTO searchDTO = new WarehouseDTO();
            searchDTO.setWarehouseIds(new ArrayList<>(warehouseIds));
            warehouses = warehouseService.listWarehouse(searchDTO);
        }
        Map<Long, String> warehouseMap = new HashMap<>(Constant.INITIAL_CAPACITY);
        if (!CollectionUtils.isEmpty(warehouses)) {
            warehouseMap = warehouses.stream().collect(Collectors.toMap(WarehouseVO::getWarehouseId, WarehouseVO::getWarehouseName));
        }
        // 制单人名称数据准备
        Map<Long, String> userIdNameMap = getUserIdNameMap(takeStockExcelVOList.stream().map(TakeStockExcelVO::getMaker));
        // 数据转换
        TakeStockBillStatus billStatusEnum;
        TakeStockSpuStatus spuStatusEnum;
        for (TakeStockExcelVO takeStockExcelVO : takeStockExcelVOList) {
            // 盘点状态
            billStatusEnum = TakeStockBillStatus.instance(takeStockExcelVO.getBillStatus());
            takeStockExcelVO.setBillStatusStr(billStatusEnum == null ? "" : billStatusEnum.strCn());
            // 盈亏状态
            spuStatusEnum = TakeStockSpuStatus.instance(takeStockExcelVO.getIoType());
            takeStockExcelVO.setIoTypeStr(spuStatusEnum == null ? "" : spuStatusEnum.str());
            // 制单人名称
            takeStockExcelVO.setMakerName(userIdNameMap.get(takeStockExcelVO.getMaker()));
            // 仓库名称
            takeStockExcelVO.setWarehouseName(warehouseMap.get(takeStockExcelVO.getWarehouseId()));
            // 库存点类型
            takeStockExcelVO.setStockPointTypeStr(Objects.equals(takeStockExcelVO.getStockPointType(), StockPointType.STATION.getValue()) ? StockPointType.STATION.getDesc() : StockPointType.WAREHOUSE.getDesc());
        }
        // 数据导出为excel
        ExcelWriter excelWriter = null;
        try {
            // 先执行合并策略
            ExcelWriterBuilder writerBuilder = ExcelUtil.getExcelWriterMerge(response, TakeStockExcelVO.EXCEL_NAME, TakeStockExcelVO.MERGE_ROW_INDEX, TakeStockExcelVO.MERGE_COLUMN_INDEX);
            if (CollUtil.isNotEmpty(takeStockExcelVOList)) {
                excelWriter = writerBuilder.build();
                // 进行写入操作
                WriteSheet sheetWriter = EasyExcel
                        .writerSheet(TakeStockExcelVO.SHEET_NAME)
                        .head(TakeStockExcelVO.class)
                        .build();
                excelWriter.write(takeStockExcelVOList, sheetWriter);
            }
        } catch (Exception e) {
            LOGGER.error("导出excel失败{}", e.getMessage());
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    @Override
    public Long countUnFinish(Long warehouseId) {
        if (Objects.isNull(warehouseId)) {
            return 0L;
        }
        return takeStockMapper.countUnFinish(warehouseId);
    }

    /**
     * 校验实物盘点信息
     *
     * @param takeStock
     * @return 数据库实物判断信息
     */
    private TakeStock checkInfo(TakeStockDTO takeStock) {
        TakeStock dbTakeStock = takeStockMapper.getByTakeStockId(takeStock.getTakeStockId());
        if (!Objects.equals(dbTakeStock.getShopId(), takeStock.getShopId())) {
            throw new LuckException("当前盘点信息不属于你的店铺");
        }
        if (Objects.equals(dbTakeStock.getBillStatus(), TakeStockBillStatus.COMPLETED.value())
                || Objects.equals(dbTakeStock.getBillStatus(), TakeStockBillStatus.VOIDED.value())) {
            throw new LuckException(("当前盘点任务已完成或已作废，请刷新页面"));
        }
        //不是废弃再进行判断
        List<TakeStockSpu> prods = takeStock.getTakeStockSpuList();
        for (TakeStockSpu prod : prods) {
            if (Objects.isNull(prod.getTotalStock())) {
                throw new LuckException("商品实盘库存不能为空");
            }
        }
        return dbTakeStock;
    }

    /**
     * 更新库存并获取分类ioTypeMap
     *
     * @param takeStockProdList
     * @return
     */
    private Map<Integer, List<SkuDTO>> updateSkuAndGetIoTypeMap(List<TakeStockSpu> takeStockProdList, Long warehouseId) {
        List<Long> skuIds = takeStockProdList.stream().map(TakeStockSpu::getSkuId).collect(Collectors.toList());
        List<Long> spuIds = takeStockProdList.stream().map(TakeStockSpu::getSpuId).collect(Collectors.toList());
        List<SkuVO> skuVOList = skuService.listSkuAndStockBySkuIds(skuIds);
        List<SkuDTO> skuList = BeanUtil.mapAsList(skuVOList, SkuDTO.class);
        Map<Long, SkuDTO> skuIdSkuMap = skuList.stream().collect(Collectors.toMap(SkuDTO::getSkuId, s -> s));
        List<SpuVO> spuList = spuService.listSpuBySpuIds(spuIds);
        Map<Long, SpuVO> spuMap = spuList.stream().collect(Collectors.toMap(SpuVO::getSpuId, s -> s));
        // 库存查询
        List<SkuStockVO> skuStocks = skuIds.stream().map(x -> new SkuStockVO(x, warehouseId)).collect(Collectors.toList());
        Map<String, SkuStockVO> skuStockMap = stockManager.mapSkuStockBySkuStocks(skuStocks);
        // 根据sku库存盘点判断盘盈or盘亏，并进行归类
        Map<Integer, List<SkuDTO>> ioTypeSkuMap = new HashMap<>(16);
        List<SkuDTO> skuDtoList;
        Integer ioType;
        SkuDTO sku;
        SkuStockVO skuStockVO;
        for (TakeStockSpu prod : takeStockProdList) {
            sku = skuIdSkuMap.get(prod.getSkuId());
            SpuVO spuVO = spuMap.get(prod.getSpuId());
            sku.setSpuName(spuVO.getName());
            sku.setSpuMainImgUrl(spuVO.getMainImgUrl());
            skuStockVO = skuStockMap.get(StockUtil.skuPointKey(sku.getSkuId(), warehouseId));
            //盈亏以及异常判断
            if (Objects.isNull(sku)) {
                //sku删除，异常
                prod.setIoType(TakeStockSpuStatus.EXCEPTION.value());
                prod.setExceptionReason(TakeStockExceptionEnum.DELETE_PROD.value());
                continue;
            } else if (Objects.isNull(skuStockVO) ||
                    ! prod.getStocks().equals(skuStockVO.getStock())) {
                //库存不相等，异常
                prod.setIoType(TakeStockSpuStatus.EXCEPTION.value());
                prod.setExceptionReason(TakeStockExceptionEnum.PROD_CHANGE.value());
                continue;
            }
            if (prod.getStocks() > prod.getTotalStock()) {
                //盘亏
                prod.setIoType(TakeStockSpuStatus.LOSS.value());
                ioType = TakeStockSpuStatus.LOSS.value();
            } else if (prod.getStocks() < prod.getTotalStock()) {
                //盘盈
                prod.setIoType(TakeStockSpuStatus.PROFIT.value());
                ioType = TakeStockSpuStatus.PROFIT.value();
            } else {
                //盘平
                prod.setIoType(TakeStockSpuStatus.EQUAL.value());
                continue;
            }
            // 分类
            sku.setChangeStock(prod.getTotalStock() - prod.getStocks());
            sku.setStock(prod.getTotalStock());
            skuDtoList = ioTypeSkuMap.getOrDefault(ioType, new ArrayList<>(Constant.INITIAL_CAPACITY));
            skuDtoList.add(sku);
            ioTypeSkuMap.put(ioType, skuDtoList);
        }
        return ioTypeSkuMap;
    }

    /**
     * 检查仓库
     * @param warehouseId 仓库id
     * @param shopId 店铺id
     * @param sysType 系统类型
     */
    private WarehouseVO checkWarehouse(Long warehouseId,
                                       Long shopId,
                                       Integer sysType) {
        WarehouseVO warehouse = warehouseService.getByWarehouseId(warehouseId);
        if (Objects.isNull(warehouse)) {
            throw new LuckException(String.format("仓库[%s]不存在", warehouseId));
        }
        if (Objects.equals(warehouse.getSysType(), com.tmerclub.cloud.common.constant.SysTypeEnum.STATION.value())) {
            // 检查门店仓库
            if(Objects.equals(sysType, com.tmerclub.cloud.common.constant.SysTypeEnum.SUPPLIER.value())) {
                throw new LuckException("供应商不存在门店");
            }
            StationVO station = stationFeignClient.getStationByInside(warehouse.getShopId()).getData();
            if (!Objects.equals(station.getShopId(), shopId))  {
                throw new LuckException(String.format("门店[%s]非该店铺的门店", station.getStationId()));
            }
        } else if (!Objects.equals(shopId, warehouse.getShopId())
                || !Objects.equals(sysType, warehouse.getSysType())) {
            // 检查商家/供应商仓库
            throw new LuckException(String.format("仓库[%s]非该店铺的仓库", warehouseId));
        }
        return warehouse;
    }

    /**
     * 构建库存流水日志对象
     * @param takeStock 实物判断
     * @param stockBillType 库存流水类型
     * @return 库存流水日志对象
     */
    private StockBillLog buildStockBillLog(TakeStock takeStock, StockBillType stockBillType) {
        StockBillLog stockBillLog = new StockBillLog();
        stockBillLog.setShopId(takeStock.getShopId());
        stockBillLog.setEmployeeId(takeStock.getMaker());
        stockBillLog.setMakerMobile(takeStock.getMakerMobile());
        stockBillLog.setStockBillType(stockBillType.value());
        stockBillLog.setStockPointId(takeStock.getWarehouseId());
        stockBillLog.setStockPointType(takeStock.getStockPointType());
        stockBillLog.setSourceOrderNo(takeStock.getTakeStockNo());
        return stockBillLog;
    }

    /**
     * 构建sku库存对象
     * @param skuDTO skuDTO
     * @param warehouseId 仓库id
     * @param luaOperateEnum 库存操作
     * @return sku库存对象
     */
    private SkuStockVO buildSkuStock(SkuDTO skuDTO, Long warehouseId, LuaOperateEnum luaOperateEnum) {
        SkuStockVO skuStockVO = new SkuStockVO();
        BeanUtils.copyProperties(skuDTO, skuStockVO);
        skuStockVO.setStockPointId(warehouseId);
        skuStockVO.setOperateType(luaOperateEnum.value());
        skuStockVO.setStock(Math.abs(skuDTO.getChangeStock()));
        return skuStockVO;
    }
}
