package com.czg.service.product.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.czg.config.RabbitPublisher;
import com.czg.exception.CzgException;
import com.czg.print.StockCheckPrintDTO;
import com.czg.print.StockPrintDTO;
import com.czg.product.dto.ConsStockFlowDTO;
import com.czg.product.entity.ConsInfo;
import com.czg.product.entity.ConsStockFlow;
import com.czg.product.enums.InOutItemEnum;
import com.czg.product.enums.InOutTypeEnum;
import com.czg.product.param.*;
import com.czg.product.service.ConsStockFlowService;
import com.czg.product.vo.ConsCheckStockRecordVo;
import com.czg.sa.StpKit;
import com.czg.service.product.mapper.ConsInfoMapper;
import com.czg.service.product.mapper.ConsStockFlowMapper;
import com.czg.service.product.mapper.ProductMapper;
import com.czg.service.product.util.WxAccountUtil;
import com.czg.utils.FunUtils;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 耗材库存变动记录
 *
 * @author Tankaikai tankaikai@aliyun.com
 * @since 1.0 2025-02-21
 */
@AllArgsConstructor
@Service
@Slf4j
public class ConsStockFlowServiceImpl extends ServiceImpl<ConsStockFlowMapper, ConsStockFlow> implements ConsStockFlowService {

    private final ConsInfoMapper consInfoMapper;
    private final ProductMapper productMapper;
    @Resource
    private WxAccountUtil wxAccountUtil;
    @Resource
    private RabbitPublisher rabbitPublisher;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ConsInOutStockHeadParam inStock(ConsInOutStockHeadParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
        List<ConsStockFlow> insertList = new ArrayList<>();
        List<ConsInfo> updateStockList = new ArrayList<>();
        for (ConsInOutStockBodyParam entity : param.getBodyList()) {

            ConsStockFlow consStockFlow = BeanUtil.copyProperties(entity, ConsStockFlow.class);
            BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
            consStockFlow.setShopId(shopId);
            consStockFlow.setInOutType(InOutTypeEnum.IN.value());
            consStockFlow.setInOutItem(InOutItemEnum.MANUAL_IN.value());
            consStockFlow.setCreateUserId(createUserId);
            consStockFlow.setCreateUserName(createUserName);
            consStockFlow.setVendorId(param.getVendorId());
            String conId = entity.getConId();
            ConsInfo consInfo;
            if (StrUtil.isBlank(entity.getConId())) {
                consInfo = consInfoMapper.selectOneByQuery(new QueryWrapper().like(ConsInfo::getConName, entity.getConName())
                        .eq(ConsInfo::getShopId, shopId).limit(1));
                if (consInfo == null) {
                    entity.setFailReason("耗材不存在");
                } else if (!consInfo.getConUnit().equals(entity.getUnitName())) {
                    entity.setFailReason("耗材单位不匹配");
                    consInfo = null;
                }

                entity.setConId(consInfo == null ? null : consInfo.getId().toString());
                consStockFlow.setConId(consInfo == null ? null : consInfo.getId());
            } else {
                consInfo = consInfoMapper.selectOneById(conId);
            }
            if (consInfo == null) {
                continue;
            }
            consStockFlow.setBeforeNumber(consInfo.getStockNumber());
            consStockFlow.setAfterNumber(NumberUtil.add(consStockFlow.getBeforeNumber(), entity.getInOutNumber()));
            insertList.add(consStockFlow);
            consInfo.setStockNumber(consStockFlow.getAfterNumber());
            updateStockList.add(consInfo);
        }
        if (!insertList.isEmpty()) {
            mapper.insertBatchSelective(insertList, 50);
        }
        for (ConsInfo consInfo : updateStockList) {
            consInfoMapper.update(consInfo);
        }
        FunUtils.transactionSafeRun(() -> sendInOutPrintMsg("IN", shopId, param));
        return param;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void outStock(ConsInOutStockHeadParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        ConsStockFlow head = BeanUtil.copyProperties(param, ConsStockFlow.class);
        List<ConsStockFlow> entityList = BeanUtil.copyToList(param.getBodyList(), ConsStockFlow.class);
        List<ConsStockFlow> insertList = new ArrayList<>();
        List<ConsInfo> updateStockList = new ArrayList<>();
        for (ConsStockFlow entity : entityList) {
            BeanUtil.copyProperties(head, entity, CopyOptions.create().ignoreNullValue());
            entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, entity.getInOutNumber()));
            entity.setShopId(shopId);
            entity.setInOutType(InOutTypeEnum.OUT.value());
            entity.setInOutItem(InOutItemEnum.MANUAL_OUT.value());
            entity.setCreateUserId(createUserId);
            entity.setCreateUserName(createUserName);
            Long conId = entity.getConId();
            ConsInfo consInfo = consInfoMapper.selectOneById(conId);
            if (consInfo == null) {
                throw new CzgException(StrUtil.format("耗材{}不存在", entity.getConName()));
            }
            entity.setBeforeNumber(consInfo.getStockNumber());
            entity.setAfterNumber(NumberUtil.add(entity.getBeforeNumber(), entity.getInOutNumber()));
            insertList.add(entity);
            consInfo.setStockNumber(entity.getAfterNumber());
            updateStockList.add(consInfo);
        }
        mapper.insertBatchSelective(insertList, 50);
        for (ConsInfo consInfo : updateStockList) {
            consInfoMapper.update(consInfo);
        }
        FunUtils.transactionSafeRun(() -> sendInOutPrintMsg("OUT", shopId, param));
    }

    private void sendInOutPrintMsg(String type, Long shopId, ConsInOutStockHeadParam param) {
        StockPrintDTO stockPrint = new StockPrintDTO();
        stockPrint.setType(type);
        stockPrint.setPrintTime(LocalDateTime.now());
        stockPrint.setInStockTime(LocalDateTime.now());
        stockPrint.setOperator(param.getOperator());
        List<StockPrintDTO.InStockItem> items = new ArrayList<>();
        BigDecimal totalInOutNumber = BigDecimal.ZERO;
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (ConsInOutStockBodyParam cons : param.getBodyList()) {
            if (cons.getConId() == null) {
                continue;
            }
            StockPrintDTO.InStockItem item = new StockPrintDTO.InStockItem();
            item.setConsName(cons.getConName());
            item.setUnit(cons.getUnitName());
            item.setStockNumber(cons.getInOutNumber());
            item.setAmount(cons.getSubTotal());
            items.add(item);
            totalInOutNumber = NumberUtil.add(totalInOutNumber, cons.getInOutNumber());
            totalAmount = NumberUtil.add(totalAmount, cons.getSubTotal());
        }
        stockPrint.setConsCount(items.size());
        stockPrint.setStockNumberCount(totalInOutNumber);
        stockPrint.setAmountCount(totalAmount);
        stockPrint.setItems(items);
        //库存盘点
        rabbitPublisher.sendOtherPrintMsg(shopId, stockPrint, "STOCK");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkStock(ConsCheckStockParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        ConsStockFlow entity = new ConsStockFlow();
        entity.setCreateUserId(createUserId);
        entity.setCreateUserName(createUserName);
        entity.setShopId(shopId);
        entity.setConId(param.getConId());
        entity.setConName(param.getConName());
        entity.setPurchasePrice(param.getPrice());
        ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
        if (consInfo == null) {
            throw new CzgException(StrUtil.format("耗材{}不存在", entity.getConName()));
        }
        BigDecimal winLossNumber = NumberUtil.sub(param.getActualNumber(), param.getStockNumber());
        if (!NumberUtil.equals(winLossNumber, param.getWinLossNumber())) {
            throw new CzgException(StrUtil.format("耗材{}库存在发生变动，请刷新后重试", entity.getConName()));
        }
        entity.setBeforeNumber(consInfo.getStockNumber());
        entity.setInOutNumber(winLossNumber);
        entity.setAfterNumber(NumberUtil.add(entity.getBeforeNumber(), entity.getInOutNumber()));
        if (NumberUtil.isLess(winLossNumber, BigDecimal.ZERO)) {
            entity.setInOutType(InOutTypeEnum.OUT.value());
            entity.setInOutItem(InOutItemEnum.LOSS_OUT.value());
        } else {
            entity.setInOutType(InOutTypeEnum.IN.value());
            entity.setInOutItem(InOutItemEnum.WIN_IN.value());
        }
        entity.setSubTotal(NumberUtil.mul(winLossNumber, param.getPrice()));
        entity.setRemark(param.getRemark());
        consInfo.setStockNumber(entity.getAfterNumber());
        consInfoMapper.update(consInfo);
        saveFlow(entity, consInfo.getConWarning());

        FunUtils.transactionSafeRun(() -> {
            StockCheckPrintDTO stockCheckPrintDTO = new StockCheckPrintDTO();
            stockCheckPrintDTO.setWinLossNumberCount(winLossNumber.intValue());
            stockCheckPrintDTO.setWinLossAmount(entity.getSubTotal());
            stockCheckPrintDTO.setRemark(entity.getRemark());
            stockCheckPrintDTO.setOperator(param.getOperator());
            List<StockCheckPrintDTO.StockCheckItem> items = new ArrayList<>();
            items.add(new StockCheckPrintDTO.StockCheckItem(consInfo.getConName(), param.getPrice(), consInfo.getConUnit(), param.getActualNumber(), param.getWinLossNumber()));
            stockCheckPrintDTO.setItems(items);
            //库存盘点
            rabbitPublisher.sendOtherPrintMsg(shopId, stockCheckPrintDTO, "STOCK_CHECK");
        });
    }

    @Override
    public Page<ConsCheckStockRecordVo> getCheckStockRecordPage(Long conId) {
        Long shopId = StpKit.USER.getShopId(0L);
        return super.pageAs(PageUtil.buildPage(), query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
    }

    @Override
    public List<ConsCheckStockRecordVo> getCheckStockRecordList(Long conId) {
        Long shopId = StpKit.USER.getShopId(0L);
        return super.mapper.selectListByQueryAs(query().eq(ConsStockFlow::getShopId, shopId).eq(ConsStockFlow::getConId, conId).orderBy(ConsStockFlow::getId, false), ConsCheckStockRecordVo.class);
    }

    @Override
    public void reportDamage(ConsReportDamageParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        Long createUserId = StpKit.USER.getLoginIdAsLong();
        String createUserName = StpKit.USER.getAccount();
        ConsInfo consInfo = consInfoMapper.selectOneById(param.getConId());
        if (consInfo == null) {
            throw new CzgException("耗材不存在");
        }
        ConsStockFlow entity = new ConsStockFlow();
        entity.setCreateUserId(createUserId);
        entity.setCreateUserName(createUserName);
        entity.setShopId(shopId);
        entity.setConId(param.getConId());
        entity.setConName(consInfo.getConName());
        entity.setPurchasePrice(consInfo.getPrice());
        BigDecimal balance = NumberUtil.sub(consInfo.getStockNumber(), param.getNumber());
        if (NumberUtil.isLess(balance, BigDecimal.ZERO)) {
            throw new CzgException(StrUtil.format("耗材{}报损数量不能大于当前库存{}", entity.getConName(), consInfo.getStockNumber()));
        }
        entity.setBeforeNumber(consInfo.getStockNumber());
        entity.setInOutNumber(NumberUtil.sub(BigDecimal.ZERO, param.getNumber()));
        entity.setAfterNumber(balance);
        entity.setInOutType(InOutTypeEnum.OUT.value());
        entity.setInOutItem(InOutItemEnum.DAMAGE_OUT.value());
        entity.setSubTotal(NumberUtil.mul(param.getNumber(), consInfo.getPrice()));
        entity.setImgUrls(JSON.toJSONString(param.getImgUrls()));
        consInfo.setStockNumber(entity.getAfterNumber());
        consInfoMapper.update(consInfo);
        saveFlow(entity, consInfo.getConWarning());
    }

    @Override
    public Page<ConsStockFlowDTO> findConsStockFlowPage(ConsStockFlowParam param) {
        Long shopId = StpKit.USER.getShopId(0L);
        PageHelper.startPage(PageUtil.buildPageHelp());
        return PageUtil.convert(new PageInfo<>(mapper.findConsStockFlowPage(shopId, param.getInOutType(), param.getInOutItem(), param.getConId())));
    }

    @Override
    public void saveFlow(ConsStockFlow entity, BigDecimal conWarning) {
        super.save(entity);
        boolean b = sendStockMsg(entity, conWarning);
        if (b) {
            ThreadUtil.execAsync(() -> rabbitPublisher.sendConsInfoChangeMsg(Convert.toStr(entity.getShopId())));
        }
    }

    @Override
    public boolean sendStockMsg(ConsStockFlow entity, BigDecimal warning) {
        boolean result = false;
        // 库存小于警告值，发送消息提醒
        if (NumberUtil.isLess(entity.getAfterNumber(), warning)) {
            result = true;
            List<String> openIdList = consInfoMapper.findOpenIdList(entity.getShopId(), "con");
            if (CollUtil.isEmpty(openIdList)) {
                return result;
            }
            String shopName = productMapper.getShopName(entity.getShopId());
            String conName = StrUtil.format("{}数量<预警值{}", entity.getConName(), warning);
            ThreadUtil.execAsync(() -> openIdList.parallelStream().forEach(openId ->
                    wxAccountUtil.sendStockMsg("耗材库存预警", shopName, conName, entity.getAfterNumber(), openId)));
        }
        return result;
    }
}
