package com.fjhx.mes.service.production.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.enums.PushBusinessTypeEnum;
import com.fjhx.common.utils.Assert;
import com.fjhx.item.entity.product.po.ProductInfo;
import com.fjhx.item.enums.ProductAvailableRecordType;
import com.fjhx.item.service.product.ProductInfoService;
import com.fjhx.mes.entity.material.po.MaterialPreparation;
import com.fjhx.mes.entity.production.dto.ProduceOrderSelectDto;
import com.fjhx.mes.entity.production.dto.ProductionOrderDto;
import com.fjhx.mes.entity.production.po.ProductionOrder;
import com.fjhx.mes.entity.production.po.ProductionOrderDetail;
import com.fjhx.mes.entity.production.po.ProductionTaskProgress;
import com.fjhx.mes.entity.production.vo.ProductionOrderDetailVo;
import com.fjhx.mes.entity.production.vo.ProductionOrderVo;
import com.fjhx.mes.entity.production.vo.ProductionProcessesVo;
import com.fjhx.mes.mapper.production.ProduceOrderMapper;
import com.fjhx.mes.mapper.production.ProductionProcessesMapper;
import com.fjhx.mes.service.material.MaterialPreparationService;
import com.fjhx.mes.service.production.ProduceOrderDetailService;
import com.fjhx.mes.service.production.ProduceOrderService;
import com.fjhx.mes.service.production.ProductionTaskProgressService;
import com.fjhx.purchase.entity.subscribe.po.SubscribeDetail;
import com.fjhx.purchase.service.subscribe.SubscribeDetailService;
import com.fjhx.sale.entity.contract.po.Contract;
import com.fjhx.sale.entity.contract.po.ContractProduct;
import com.fjhx.sale.entity.contract.po.ContractProductBom;
import com.fjhx.sale.service.contract.ContractProductBomService;
import com.fjhx.sale.service.contract.ContractProductService;
import com.fjhx.sale.service.contract.ContractService;
import com.fjhx.socket.core.PushTypeEnum;
import com.fjhx.socket.core.WebSocketPush;
import com.fjhx.tenant.utils.DeptUstil;
import com.ruoyi.common.core.domain.BasePo;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.wrapper.IWrapper;
import com.ruoyi.common.utils.wrapper.SqlField;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.utils.UserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * <p>
 * 生产订单 服务实现类
 * </p>
 *
 * @since 2024-01-10
 */
@Service
public class ProduceOrderServiceImpl extends ServiceImpl<ProduceOrderMapper, ProductionOrder> implements ProduceOrderService {

    @Autowired
    private ContractService contractService;
    @Autowired
    private ContractProductService contractProductService;
    @Autowired
    private ProduceOrderDetailService produceOrderDetailService;
    @Autowired
    private ISysDeptService sysDeptService;
    @Autowired
    private ProductInfoService productInfoService;
    @Autowired
    private MaterialPreparationService materialPreparationService;
    @Autowired
    private ProductionProcessesMapper productionProcessesMapper;
    @Autowired
    private ProductionTaskProgressService productionTaskProgressService;
    @Autowired
    private ContractProductBomService contractProductBomService;
    @Autowired
    private SubscribeDetailService subscribeDetailService;

    @Override
    public Page<ProductionOrderVo> getPage(ProduceOrderSelectDto dto) {
        IWrapper<ProductionOrder> wrapper = getWrapper();
        wrapper.orderByDesc("po", ProductionOrder::getId);

        //生产状态
        String produceStatus = dto.getProduceStatus();
        if (ObjectUtil.isNotEmpty(produceStatus)) {
            wrapper.in(ProductionOrder::getProduceStatus, produceStatus.split(","));
        }
        //交期-时间范围过滤
        wrapper.ge("po", ProductionOrder::getDeliveryPeriod, dto.getStaDeliveryPeriod());
        wrapper.le("po", ProductionOrder::getDeliveryPeriod, dto.getEndDeliveryPeriod());
        //下单时间（创建时间）-时间范围过滤
        wrapper.ge("po", ProductionOrder::getCreateTime, dto.getBeginTime());
        wrapper.le("po", ProductionOrder::getCreateTime, dto.getEndTime());
        //关键字
        wrapper.keyword(dto.getKeyword(), new SqlField("po", ProductionOrder::getCode));

        //权限过滤：生产订单-子公司看自己的,总公司看全部
        Long companyId = SecurityUtils.getCompanyId();
        if (!Objects.equals(companyId, 100L)) {
            wrapper.eq("po", ProductionOrder::getCompanyId, companyId);
        } else {
            wrapper.eq("po", ProductionOrder::getCompanyId, dto.getCompanyId());
        }

        //是否逾期过滤
        if (ObjectUtil.isNotEmpty(dto.getIsOverdue())) {
            wrapper.eq("IF(IFNULL(po.finish_time,NOW())>po.delivery_period,1,0)", dto.getIsOverdue());
        }

        //是否投产过滤
        if (ObjectUtil.isNotEmpty(dto.getIsProduce())) {
            wrapper.eq("IF( po.produce_time IS NULL, 0, 1 )", dto.getIsProduce());
        }

        Page<ProductionOrderVo> page = this.baseMapper.getPage(dto.getPage(), wrapper);
        List<ProductionOrderVo> records = page.getRecords();
        if (ObjectUtil.isEmpty(records)) {
            return page;
        }
        //赋值订单明细信息
        List<Long> produceOrderIds = records.stream().map(ProductionOrder::getId).collect(Collectors.toList());
        List<ProductionOrderDetail> productionOrderDetailList = produceOrderDetailService.list(q -> q.in(ProductionOrderDetail::getProduceOrderId, produceOrderIds));
        List<ProductionOrderDetailVo> produceOrderDetailVos = BeanUtil.copyToList(productionOrderDetailList, ProductionOrderDetailVo.class);

        productInfoService.attributeAssign(produceOrderDetailVos, ProductionOrderDetail::getProductId, (item, product) -> {
            item.setProductCode(product.getCustomCode());
            item.setProductName(product.getName());
            item.setProductUnit(product.getUnit());
            item.setProductSpec(product.getSpec());
            item.setProductLength(product.getLength());
            item.setProductWidth(product.getWidth());
            item.setProductHeight(product.getHeight());
            item.setProductColor(product.getColor());
        });

        Map<Long, List<ProductionOrderDetailVo>> produceOrderDetailMap = produceOrderDetailVos.stream().collect(Collectors.groupingBy(ProductionOrderDetail::getProduceOrderId));

        //获取归属公司信息
        Map<Long, String> sysDeptMap = new HashMap<>();
        List<Long> companyIds = records.stream().map(ProductionOrder::getCompanyId).collect(Collectors.toList());
        if (ObjectUtil.isNotEmpty(companyIds)) {
            List<SysDept> deptList = sysDeptService.list(Wrappers.<SysDept>query().in("dept_id", companyIds));
            sysDeptMap = deptList.stream().collect(Collectors.toMap(SysDept::getDeptId, SysDept::getDeptName));
        }


        for (ProductionOrderVo record : records) {
            record.setProduceOrderDetailList(produceOrderDetailMap.get(record.getId()));

            //赋值生产公司
            record.setCompanyName(sysDeptMap.get(record.getCompanyId()));
        }

        return page;
    }

    @Override
    public List<ProductionOrderVo> getSchedulingList(ProduceOrderSelectDto dto) {
        //没传默认当前月
        if (ObjectUtil.isEmpty(dto.getBeginDate())) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
            dto.setBeginDate(sdf.format(new Date()));
        }

        IWrapper<ProductionOrder> wrapper = getWrapper();
        //排程时间过滤
        wrapper.and(q -> q
                .eq("DATE_FORMAT( po.create_time, '%Y-%m' )", dto.getBeginDate())
                .or().eq("DATE_FORMAT( po.delivery_period, '%Y-%m' )", dto.getBeginDate())
                .or(q1 -> q1
                        .le("DATE_FORMAT( po.create_time, '%Y-%m' )", dto.getBeginDate())
                        .ge("DATE_FORMAT( po.delivery_period, '%Y-%m' )", dto.getBeginDate())
                )
        );

        //过滤已投产的数据
        wrapper.isNotNull("po.produce_time");

        //是否逾期过滤
        if (ObjectUtil.isNotEmpty(dto.getIsOverdue())) {
            wrapper.eq("IF(IFNULL(po.finish_time,NOW())>po.delivery_period,1,0)", dto.getIsOverdue());
        }

        //权限过滤：生产排程-子公司看自己，总公司看全部
        Long companyId = SecurityUtils.getCompanyId();
        if (!Objects.equals(companyId, 100L)) {
            wrapper.eq("po", ProductInfo::getCompanyId, companyId);
        } else {
            wrapper.eq("po", ProductInfo::getCompanyId, dto.getCompanyId());
        }

        wrapper.groupBy("po.id");
        wrapper.orderByAsc("po", ProductionOrder::getDeliveryPeriod);
        wrapper.orderByAsc("po", ProductionOrder::getCreateTime);
        wrapper.orderByAsc("po", ProductionOrder::getId);
        List<ProductionOrderVo> schedulingList = baseMapper.getSchedulingList(wrapper);

        //获取生产部门信息
        List<Long> companyIds = schedulingList.stream().map(ProductionOrderVo::getCompanyId).collect(Collectors.toList());
        if (ObjectUtil.isNotEmpty(companyIds)) {
            Map<Long, String> deptMap = DeptUstil.getDeptNameMap(companyIds);
            for (ProductionOrderVo productionOrderVo : schedulingList) {
                //赋值生产公司名称
                productionOrderVo.setCompanyName(deptMap.get(productionOrderVo.getCompanyId()));
            }
        }

        return schedulingList;
    }

    @Override
    @DSTransactional
    public void createOrder(ProductionOrderDto produceOrderDto) {
        Long contractId = produceOrderDto.getContractId();
        Assert.notEmpty(produceOrderDto.getContractId(), "合同id不能为空！");

        //将数据移到归属公司下
        SysDept produceCompany = sysDeptService.getById(produceOrderDto.getProduceCompanyId());
        Assert.notEmpty(produceCompany, "查询不到生产公司信息");
        Long companyId = produceCompany.getDeptId();
        produceOrderDto.setCompanyId(companyId);

        //获取合同信息
        Contract contract = contractService.getById(contractId);
        Assert.notEmpty(contract, "查询不到合同信息！");
        contractService.update(q -> q
                .eq(Contract::getId, contractId)
                .set(Contract::getOrderDistributeStatus, 1)
                .set(Contract::getOrderDistributeTime, new Date())
                .set(BasePo::getUpdateTime, new Date())
                .set(BasePo::getUpdateUser, SecurityUtils.getUserId())
        );

        produceOrderDto.setCode(contract.getCode());
        produceOrderDto.setProduceStatus(0);
        this.save(produceOrderDto);

        //创建待采购
        createMaterialWaitPurchase(produceOrderDto);

        //创建生产任务
        createOrderDetails(produceOrderDto);

        //消息推送：给归属公司的 子公司总经理
        String title = String.format("您有新的生产订单，订单号【%s】，请及时跟进。", produceOrderDto.getCode());
        JSONObject msg = new JSONObject();
        msg.put("business_id", produceOrderDto.getId());
        List<Long> generalManagerIds = UserUtil.getUserIdsByRoleKey("subsidiary_gm", companyId);
        for (Long generalManagerId : generalManagerIds) {
            WebSocketPush.byUser(PushTypeEnum.MESSAGE, generalManagerId, title, PushBusinessTypeEnum.CONTRACT_PRODUCTION_TAG_UPDATE.getType(), msg.toString());
        }
    }

    /**
     * 投产
     */
    @Override
    @DSTransactional
    public synchronized void putProduction(ProduceOrderSelectDto dto) {
        Long orderId = dto.getId();
        Date produceTime = dto.getProduceTime();

        Assert.notEmpty(orderId, "投产id不能为空！");
        Assert.notEmpty(produceTime, "投产时间不能为空！");

        ProductionOrder productionOrder = this.getById(orderId);
        Assert.notEmpty(productionOrder, "查询不到订单信息！");

        if (ObjectUtil.isNotEmpty(productionOrder.getProduceTime())) {
            throw new ServiceException("该订单已投产，请勿重复投产！！");
        }

        //生成待备料
        createMaterialPreparation(productionOrder);

        productionOrder.setProduceStatus(1);
        productionOrder.setProduceTime(produceTime);

        this.updateById(productionOrder);
    }

    /**
     * 修改投产时间
     */
    @Override
    public void editProduceTime(ProduceOrderSelectDto dto) {
        Long orderId = dto.getId();
        Date produceTime = dto.getProduceTime();

        Assert.notEmpty(orderId, "投产id不能为空！");
        Assert.notEmpty(produceTime, "投产时间不能为空！");

        ProductionOrder productionOrder = this.getById(orderId);
        Assert.notEmpty(productionOrder, "查询不到订单信息！");

        productionOrder.setProduceTime(produceTime);

        this.updateById(productionOrder);
    }

    /**
     * 创建合同产品 物料待采购信息
     */
    private void createMaterialWaitPurchase(ProductionOrderDto productionOrderDto) {
        //根据归属公司将数据赋值给指定公司
        Long companyId = productionOrderDto.getCompanyId();
        Long contractId = productionOrderDto.getContractId();

        //获取合同产品信息
        List<ContractProduct> contractProductList = contractProductService.list(q -> q.eq(ContractProduct::getContractId, contractId));
        Map<Long, ContractProduct> productMap = contractProductList.stream().collect(Collectors.toMap(ContractProduct::getId, Function.identity()));

        //获取物料列表
        List<Long> cpIds = contractProductList.stream().map(ContractProduct::getId).collect(Collectors.toList());
        List<ContractProductBom> cpBList = contractProductBomService.list(q -> q.in(ContractProductBom::getContractProductId, cpIds));

        //合并所有相同物料
        Map<Long, AvailableStockBo> inOutBoMap = new ConcurrentHashMap<>();
        for (ContractProductBom bom : cpBList) {
            Long materialId = bom.getMaterialId();
            ContractProduct contractProduct = productMap.get(bom.getContractProductId());
            BigDecimal multiply = bom.getQuantity().multiply(contractProduct.getQuantity());

            //合并相同物料
            AvailableStockBo inOutBo = inOutBoMap.get(materialId);
            if (ObjectUtil.isEmpty(inOutBo)) {
                inOutBo = new AvailableStockBo();
                inOutBo.setProductId(materialId);
                inOutBo.setQuantity(BigDecimal.ZERO);
            }
            inOutBo.setQuantity(inOutBo.getQuantity().add(multiply));
            inOutBoMap.put(materialId, inOutBo);
        }

        List<Long> materialIds = cpBList.stream().map(ContractProductBom::getMaterialId).collect(Collectors.toList());
        Map<Long, ProductInfo> materialMap = productInfoService.mapKEntity(ProductInfo::getId, q -> q.in(ProductInfo::getId, materialIds));

        //计算并生成待出库数据
        List<SubscribeDetail> subscribeDetailList = new ArrayList<>();

        //获取待采购物料列表
        List<AvailableStockBo> collect = inOutBoMap.values().stream().collect(Collectors.toList());
        //操作可用库存计数需要采购量
        productInfoService.editAvailableQuantity(collect, contractId, ProductAvailableRecordType.SALE_PASS, companyId);
        for (AvailableStockBo inOutBo : collect) {
            Long materialId = inOutBo.getProductId();
            BigDecimal requiredQuantity = inOutBo.getQuantity();

            //需要采购的数量大于0生成待采购数据
            if (requiredQuantity.compareTo(BigDecimal.ZERO) > 0) {
                SubscribeDetail subscribeDetail = new SubscribeDetail();
                subscribeDetail.setProductId(materialId);
                subscribeDetail.setCount(requiredQuantity);
                subscribeDetail.setStatus(15);//待采购
                subscribeDetail.setContractId(contractId);
                subscribeDetail.setDataType(1);
                subscribeDetail.setCompanyId(companyId);
                subscribeDetailList.add(subscribeDetail);
            }
        }

        //保存待采购明细
        subscribeDetailService.saveBatch(subscribeDetailList);
    }

    /**
     * 创建生产任务
     */
    private synchronized void createOrderDetails(ProductionOrder produceOrder) {
        Long orderDtoId = produceOrder.getId();
        Long contractId = produceOrder.getContractId();
        Long companyId = produceOrder.getCompanyId();

        Contract contract = contractService.getById(contractId);
        Assert.notEmpty(contract, "查询不到合同信息！");

        //创建生产订单明细
        List<ProductionOrderDetail> productionOrderDetailList = new ArrayList<>();
        List<ContractProduct> contractProductList = contractProductService.list(q -> q.eq(ContractProduct::getContractId, contractId));

        for (ContractProduct contractProduct : contractProductList) {
            ProductionOrderDetail productionOrderDetail = new ProductionOrderDetail();
            productionOrderDetail.setProduceOrderId(orderDtoId);
            productionOrderDetail.setContractId(contractId);
            productionOrderDetail.setContractDetailId(contractProduct.getId());
            productionOrderDetail.setProductId(contractProduct.getProductId());
            productionOrderDetail.setQuantity(contractProduct.getQuantity());
            productionOrderDetail.setFinishQuantity(BigDecimal.ZERO);
            productionOrderDetail.setProduceStatus(0);
            productionOrderDetail.setCompanyId(companyId);
            productionOrderDetailList.add(productionOrderDetail);
        }
        produceOrderDetailService.saveBatch(productionOrderDetailList);

        List<Long> productIds = productionOrderDetailList.stream().map(ProductionOrderDetail::getProductId).collect(Collectors.toList());

        //生成生产任务进度
        List<ProductionTaskProgress> productionTaskProgressList = new ArrayList<>();
        List<ProductionProcessesVo> processes = productionProcessesMapper.getProcessesByProductId(IWrapper.getWrapper().in("pi.id", productIds));
        Map<Long, List<ProductionProcessesVo>> processesMap = processes.stream().collect(Collectors.groupingBy(ProductionProcessesVo::getProductId));
        for (ProductionOrderDetail productionOrderDetail : productionOrderDetailList) {
            List<ProductionProcessesVo> productionProcessesVos = processesMap.getOrDefault(productionOrderDetail.getProductId(), new ArrayList<>());
            for (ProductionProcessesVo productionProcessesVo : productionProcessesVos) {
                ProductionTaskProgress prodTaskProgress = new ProductionTaskProgress();
                prodTaskProgress.setTaskId(productionOrderDetail.getId());
                prodTaskProgress.setProcessesId(productionProcessesVo.getId());
                prodTaskProgress.setFinishQuantity(BigDecimal.ZERO);
                productionTaskProgressList.add(prodTaskProgress);
            }
        }
        productionTaskProgressService.saveBatch(productionTaskProgressList);
    }

    /**
     * 创建生产备料
     */
    private synchronized void createMaterialPreparation(ProductionOrder productionOrder) {
        Long contractId = productionOrder.getContractId();
        Long companyId = productionOrder.getCompanyId();
        //创建生产备料
        List<ProductionOrderDetail> productionOrderDetailList = produceOrderDetailService.list(q -> q
                .eq(ProductionOrderDetail::getProduceOrderId, productionOrder.getId())
        );
        //获取合同产品ids
        List<Long> cpIds = productionOrderDetailList.stream()
                .map(ProductionOrderDetail::getContractDetailId).collect(Collectors.toList());
        Map<Long, List<ContractProductBom>> cpBMap = contractProductBomService.mapKGroup(ContractProductBom::getContractProductId,
                q -> q.in(ContractProductBom::getContractProductId, cpIds)
        );

        //获取产品id
        for (ProductionOrderDetail productionOrderDetail : productionOrderDetailList) {
            productionOrderDetail.setProduceStatus(1);
            //创建备料
            List<ContractProductBom> contractProductBomList = cpBMap.get(productionOrderDetail.getContractDetailId());

            List<MaterialPreparation> materialPreparationList = new ArrayList<>();
            for (ContractProductBom bomDetail : contractProductBomList) {
                BigDecimal multiply = bomDetail.getQuantity().multiply(productionOrderDetail.getQuantity());

                MaterialPreparation materialPreparation = new MaterialPreparation();
                materialPreparation.setContractId(contractId);
                materialPreparation.setContractDetailId(productionOrderDetail.getContractDetailId());
                materialPreparation.setProductionOrderId(productionOrder.getId());
                materialPreparation.setProductionOrderDetailId(productionOrderDetail.getId());
                materialPreparation.setMaterialId(bomDetail.getMaterialId());
                materialPreparation.setQuantity(multiply);
                materialPreparation.setStatus(0);
                materialPreparation.setCompanyId(companyId);
                materialPreparationList.add(materialPreparation);
            }
            materialPreparationService.saveBatch(materialPreparationList);
        }

        produceOrderDetailService.updateBatchById(productionOrderDetailList);
    }

}
