package com.yeejoin.amos.boot.module.cylinder.biz.event.listener;


import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Sequence;
import com.yeejoin.amos.boot.module.cylinder.api.dto.CylinderQuestionInfoDto;
import com.yeejoin.amos.boot.module.cylinder.api.entity.CylinderQuestionInfo;
import com.yeejoin.amos.boot.module.cylinder.biz.event.CylinderQuestionCreateEvent;
import com.yeejoin.amos.boot.module.cylinder.biz.service.impl.CylinderQuestionInfoServiceImpl;
import com.yeejoin.amos.boot.module.cylinder.flc.api.entity.CylinderInfo;
import com.yeejoin.amos.boot.module.cylinder.flc.biz.service.impl.CylinderInfoServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.typroject.tyboot.core.rdbms.orm.entity.BaseEntity;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Component
@Slf4j
public class CylinderQuestionCreateEventListener implements ApplicationListener<CylinderQuestionCreateEvent> {

    @Value("${cylinder.question.create.thread.number:3}")
    private int threadNumber;

    private CylinderInfoServiceImpl cylinderInfoService;

    private CylinderQuestionInfoServiceImpl cylinderQuestionInfoService;

    private Sequence sequence;

    public CylinderQuestionCreateEventListener(CylinderInfoServiceImpl cylinderInfoService,
                                               CylinderQuestionInfoServiceImpl cylinderQuestionInfoService,
                                               Sequence sequence) {
        this.cylinderInfoService = cylinderInfoService;
        this.cylinderQuestionInfoService = cylinderQuestionInfoService;
        this.sequence = sequence;
    }


    private Map<String, CylinderInfo> sequenceCodeAppIdCylinderInfoMap = new ConcurrentHashMap<>();

    /**
     * 批量入库大小
     */
    private int batchSize = 10;

    private List<BlockingQueue<CylinderQuestionInfoDto>> hashCodeBlockingQueues = new ArrayList<>();

    private ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void onApplicationEvent(CylinderQuestionCreateEvent event) {
        log.info("2.收到问题创建消息：{}", JSON.toJSONString(event.getCylinderQuestionInfoDto()));
        // 按照一定的规则将不同的问题对象放到不同的队列里，保证多线程不会同时操作不同的问题对象数据，减少了线程的冲突，提高执行效率
        int queueIndex = Math.abs(event.getCylinderQuestionInfoDto().getQuestionObjectId().hashCode()) % threadNumber;
        hashCodeBlockingQueues.get(queueIndex).add(event.getCylinderQuestionInfoDto());
    }

    @PostConstruct
    public void initCustomThread() {
        // 初始化队列，按照线程数动态创建队列
        initQueue();
        // 多线程执行消费队列入库
        ExecutorService executorService = Executors.newFixedThreadPool(threadNumber);
        for (int i = 0; i < threadNumber; i++) {
            BlockingQueue<CylinderQuestionInfoDto> queue = hashCodeBlockingQueues.get(i);
            executorService.execute(() -> {
                while (true) {
                    try {
                        CylinderQuestionInfoDto questionInfoDto = queue.take();
                        List<CylinderQuestionInfoDto> waitSaveData = new ArrayList<>();
                        waitSaveData.add(questionInfoDto);
                        try {
                            reentrantLock.lock();
                            if (queue.size() >= batchSize) {
                                queue.drainTo(waitSaveData, batchSize);
                            }
                        } finally {
                            reentrantLock.unlock();
                        }
                        this.save2DbBatch(waitSaveData);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            });
        }
    }

    private void initQueue() {
        for (int i = 0; i < threadNumber; i++) {
            hashCodeBlockingQueues.add(new LinkedBlockingQueue<>());
        }
    }

    private void save2DbBatch(List<CylinderQuestionInfoDto> questionInfos) {
        List<CylinderQuestionInfo> cylinderQuestionInfos = questionInfos.stream().map(q -> {
            CylinderQuestionInfo cylinderQuestionInfo = new CylinderQuestionInfo();
            BeanUtil.copyProperties(q, cylinderQuestionInfo);
            cylinderQuestionInfo.setSequenceNbr(sequence.nextId());
            // 将sequenceCode换成sequenceNbr
            cylinderQuestionInfo.setQuestionObjectId(this.castSeqCode2SeqNo(q.getQuestionObjectId(), q.getQuestionAttributionId()));
            cylinderQuestionInfo.setQuestionObjectName(this.getQuestionObjectName(q.getQuestionObjectId(), q.getQuestionAttributionId()));
            cylinderQuestionInfo.setRecDate(new Date());
            return cylinderQuestionInfo;
        }).collect(Collectors.toList());
        cylinderQuestionInfoService.saveBatch(cylinderQuestionInfos);
        log.info("3.问题入库创建成功：{}", JSON.toJSONString(cylinderQuestionInfos));
    }

    private String getQuestionObjectName(String sequenceCode, String appId) {
        String uniKey = getUniKey(appId, sequenceCode);
        if (sequenceCodeAppIdCylinderInfoMap.get(uniKey) != null) {
            return sequenceCodeAppIdCylinderInfoMap.get(uniKey).getFactoryNum() + "";
        }
        return "";
    }

    private String castSeqCode2SeqNo(String sequenceCode, String appId) {
        String uniKey = getUniKey(appId, sequenceCode);
        if (sequenceCodeAppIdCylinderInfoMap.get(uniKey) != null) {
            return sequenceCodeAppIdCylinderInfoMap.get(uniKey).getSequenceNbr() + "";
        }
        synchronized (this) {
            if (sequenceCodeAppIdCylinderInfoMap.get(uniKey) != null) {
                return sequenceCodeAppIdCylinderInfoMap.get(uniKey).getSequenceNbr() + "";
            }
            CylinderInfo cylinderInfo = cylinderInfoService.getBaseMapper().selectOne(new LambdaQueryWrapper<CylinderInfo>()
                    .eq(CylinderInfo::getSequenceCode, sequenceCode)
                    .select(BaseEntity::getSequenceNbr, CylinderInfo::getAppId, CylinderInfo::getUnitName, CylinderInfo::getFactoryNum));
            sequenceCodeAppIdCylinderInfoMap.put(uniKey, cylinderInfo);
        }
        return sequenceCodeAppIdCylinderInfoMap.get(uniKey).getSequenceNbr() + "";
    }

    private String getUniKey(String appId, String sequenceCode) {
        return String.format("%s:%s", appId, sequenceCode);
    }

}


