package com.cf.imes.module.system.service.lable;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQuery;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.es.core.service.ESDocumentService;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.module.system.controller.admin.label.vo.*;
import com.cf.imes.module.system.dal.dataobject.labelelementproperty.LabelElementPropertyDO;
import com.cf.imes.module.system.dal.dataobject.lable.LabelDO;
import com.cf.imes.module.system.dal.dataobject.lable.LabelElementDO;
import com.cf.imes.module.system.dal.dataobject.machine.CuttingSettingDO;
import com.cf.imes.module.system.dal.mysql.label.LabelElementMapper;
import com.cf.imes.module.system.dal.mysql.label.LabelMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.LABEL_NOT_EXISTS;

/**
 * 标签模板 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Validated
@Slf4j
public class LabelServiceImpl implements LabelService {

    @Resource
    private LabelMapper LabelMapper;
    @Resource
    private LabelElementMapper labelElementMapper;
    @Resource
    private ESDocumentService esDocumentService;
    @Resource
    private ElasticsearchClient elasticsearchClient;

    private static final String LABEL_ELEMENT_PROPERTY_INX = "label_element_property";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createLabel(LabelSaveReqVO createReqVO) {
        // 插入
        LabelDO Label = BeanUtils.toBean(createReqVO, LabelDO.class);
        LabelMapper.insert(Label);

        // 插入子表
        List<LabelElementSaveReqVO> createReqVOElements = createReqVO.getElements();
        List<LabelElementPropertyDO> labelElementPropertyDOS = new ArrayList<>();
        createReqVOElements.forEach(e -> {
            e.setLabelId(Label.getId());
            LabelElementDO elementDO = Convert.convert(LabelElementDO.class, e);
            labelElementMapper.insert(elementDO);
            LabelElementPropertyDO propertyDO = e.getPropertyDO();
            propertyDO.setElementId(elementDO.getId());
            labelElementPropertyDOS.add(propertyDO);
        });
        //插入索引
        try {
            esDocumentService.bulkCreate(LABEL_ELEMENT_PROPERTY_INX, labelElementPropertyDOS);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw exception(INTERNAL_SERVER_ERROR);
        }
        // 返回
        return Label.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLabel(LabelSaveReqVO updateReqVO) {
        // 校验存在
        validateLabelExists(updateReqVO.getId());
        // 更新
        LabelDO updateObj = BeanUtils.toBean(updateReqVO, LabelDO.class);
        LabelMapper.updateById(updateObj);

        // 更新子表
        List<LabelElementSaveReqVO> elements = updateReqVO.getElements();
        /*updateLabelElementList(updateReqVO.getId(), Convert.convert(new TypeReference<List<LabelElementDO>>() {
        }, elements));*/
        labelElementMapper.updateBatch(Convert.convert(new TypeReference<List<LabelElementDO>>() {
        }, elements));
        List<LabelElementPropertyDO> labelElementPropertyDOS = elements.stream().map(e -> e.getPropertyDO()).collect(Collectors.toList());
        try {
            esDocumentService.bulkCreate(LABEL_ELEMENT_PROPERTY_INX, labelElementPropertyDOS);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw exception(INTERNAL_SERVER_ERROR);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteLabel(Long id) {
        // 校验存在
        LabelDO LabelDO = LabelMapper.selectById(id);
        if (LabelDO == null) {
            throw exception(LABEL_NOT_EXISTS);
        }
        List<LabelElementDO> labelElementDOS = labelElementMapper.selectListByLabelId(id);
        List<FieldValue> fieldValues = labelElementDOS.stream().map(e -> FieldValue.of(e.getId())).collect(Collectors.toList());
        // 删除索引
        DeleteByQueryRequest.Builder builder = new DeleteByQueryRequest.Builder()
                .index(LABEL_ELEMENT_PROPERTY_INX)
                .query(TermsQuery.of(e -> e.field("elementId").terms(b -> b.value(fieldValues)))._toQuery());
        try {
            elasticsearchClient.deleteByQuery(builder.build());
        } catch (IOException e) {
            log.error(e.getMessage());
            throw exception(INTERNAL_SERVER_ERROR);
        }
        // 删除标签模板表
        LabelMapper.deleteById(id);
        // 删除标签元素模板表
        labelElementMapper.delete(new LambdaQueryWrapperX<LabelElementDO>().eq(LabelElementDO::getLabelId, id));

    }

    private void validateLabelExists(Long id) {
        if (LabelMapper.selectById(id) == null) {
            throw exception(LABEL_NOT_EXISTS);
        }
    }

    @Override
    public LabelRespVO getLabel(Long id) {
        LabelDO LabelDO = LabelMapper.selectById(id);
        List<LabelElementDO> labelElementDOS = labelElementMapper.selectListByLabelId(LabelDO.getId());
        LabelRespVO LabelRespVO = BeanUtils.toBean(LabelDO, LabelRespVO.class);
        List<LabelElementRespVO> labelElementRespVOS = BeanUtils.toBean(labelElementDOS, LabelElementRespVO.class);
        List<FieldValue> fieldValues = labelElementDOS.stream().map(e -> FieldValue.of(e.getId())).collect(Collectors.toList());
        SearchResponse<CuttingSettingDO> searchSetting = null;
        try {
            SearchResponse<LabelElementPropertyDO> search = elasticsearchClient.search(builder -> builder.index(LABEL_ELEMENT_PROPERTY_INX)
                            .query(TermsQuery.of(e -> e.field("elementId")
                                    .terms(b -> b.value(fieldValues)))._toQuery())
                            .from(0)
                            .size(10000),
                    LabelElementPropertyDO.class);
            List<Hit<LabelElementPropertyDO>> hits = search.hits().hits();
            if (CollectionUtil.isNotEmpty(hits)) {
                List<LabelElementPropertyDO> propertyDOS = hits.stream().map(e -> e.source()).collect(Collectors.toList());
                for (LabelElementRespVO respVO : labelElementRespVOS) {
                    respVO.setPropertyDO(propertyDOS.stream().filter(e -> Objects.equals(e.getElementId(), respVO.getId())).findAny().orElse(null));
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new ServiceException(INTERNAL_SERVER_ERROR);
        }

        LabelRespVO.setLabelElements(labelElementRespVOS);
        return LabelRespVO;
    }

    @Override
    public PageResult<LabelDO> getLabelPage(LabelPageReqVO pageReqVO) {
        return LabelMapper.selectPage(pageReqVO);
    }

    // ==================== 子表（标签元素模板） ====================

    @Override
    public List<LabelElementRespVO> getLabelElementListByLabelId(Long LabelId) {
        List<LabelElementDO> labelElementDOS = labelElementMapper.selectListByLabelId(LabelId);
        List<LabelElementRespVO> respVOS = BeanUtils.toBean(labelElementDOS, LabelElementRespVO.class);
        List<FieldValue> fieldValues = labelElementDOS.stream().map(e -> FieldValue.of(e.getId())).collect(Collectors.toList());
        try {
            SearchResponse<LabelElementPropertyDO> response = elasticsearchClient.search(b -> b.index(LABEL_ELEMENT_PROPERTY_INX)
                            .from(0)
                            .size(10000)
                            .query(TermsQuery.of(e -> e.field("elementId").terms(t -> t.value(fieldValues)))._toQuery()),
                    LabelElementPropertyDO.class);
            List<Hit<LabelElementPropertyDO>> hits = response.hits().hits();
            if(CollectionUtil.isNotEmpty(hits)) {
                List<LabelElementPropertyDO> propertyDOS = hits.stream().map(e -> e.source()).collect(Collectors.toList());
                for (LabelElementRespVO respVO : respVOS) {
                    respVO.setPropertyDO(propertyDOS.stream().filter(e -> Objects.equals(e.getElementId(), respVO.getId())).findAny().orElse(null));
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw exception(INTERNAL_SERVER_ERROR);
        }
        return respVOS;
    }

    @Override
    public Map<String, List<LabelRespVO>> getGroupList() {
        List<LabelDO> LabelDOS = LabelMapper.selectList();
        List<LabelRespVO> LabelRespVOS = BeanUtils.toBean(LabelDOS, LabelRespVO.class);
        Set<Long> ids = LabelRespVOS.stream().map(e -> e.getId()).collect(Collectors.toSet());
        List<LabelElementDO> labelElementDOS = labelElementMapper.selectList(new LambdaQueryWrapperX<LabelElementDO>().in(LabelElementDO::getLabelId, ids));
        List<LabelElementRespVO> labelElementRespVOS = BeanUtils.toBean(labelElementDOS, LabelElementRespVO.class);
        List<FieldValue> fieldValues = labelElementRespVOS.stream().map(e -> FieldValue.of(e.getId())).collect(Collectors.toList());
        try {
            SearchResponse<LabelElementPropertyDO> search = elasticsearchClient.search(builder -> builder.index(LABEL_ELEMENT_PROPERTY_INX)
                            .query(TermsQuery.of(e -> e.field("elementId")
                                    .terms(b -> b.value(fieldValues)))._toQuery())
                            .from(0)
                            .size(10000),
                    LabelElementPropertyDO.class);
            List<Hit<LabelElementPropertyDO>> hits = search.hits().hits();
            if (CollectionUtil.isNotEmpty(hits)) {
                List<LabelElementPropertyDO> propertyDOS = hits.stream().map(e -> e.source()).collect(Collectors.toList());
                for (LabelElementRespVO respVO : labelElementRespVOS) {
                    respVO.setPropertyDO(propertyDOS.stream().filter(e -> Objects.equals(e.getElementId(), respVO.getId())).findAny().orElse(null));
                }
            }

        } catch (IOException e) {
            log.error(e.getMessage());
            throw new ServiceException(INTERNAL_SERVER_ERROR);
        }
        for (LabelRespVO LabelRespVO : LabelRespVOS) {
            LabelRespVO.setLabelElements(labelElementRespVOS.stream().filter(e -> Objects.equals(e.getLabelId(), LabelRespVO.getId())).toList());
        }
        return LabelRespVOS.stream().collect(Collectors.groupingBy(e -> e.getType()));
    }

    private void createLabelElementList(Long LabelId, List<LabelElementDO> list) {
        list.forEach(o -> o.setLabelId(LabelId));
        labelElementMapper.insertBatch(list);
    }

    private void updateLabelElementList(Long LabelId, List<LabelElementDO> list) {
        deleteLabelElementByLabelId(LabelId);
        list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
        createLabelElementList(LabelId, list);
    }

    private void deleteLabelElementByLabelId(Long LabelId) {
        labelElementMapper.deleteByLabelId(LabelId);
    }

}