package cn.axzo.workflow.core.service.impl;

import cn.axzo.basics.common.BeanMapper;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.enums.OrderEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO;
import cn.axzo.workflow.core.conf.CustomEventManager;
import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd;
import cn.axzo.workflow.core.engine.event.DocChangeEventImpl;
import cn.axzo.workflow.core.repository.entity.ExtAxDocContent;
import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc;
import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper;
import cn.axzo.workflow.core.service.ExtAxDocContentService;
import cn.axzo.workflow.core.service.ExtAxModelDocService;
import cn.axzo.workflow.core.service.ExtAxReModelService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_DUPLICATE;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_EXISTS;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TYPE_CLONE_NOT_SUPPORT;

/**
 * 模型关联的文档
 *
 * @author wangli
 * @since 2025-03-28 18:22
 */
@Service
@Slf4j
public class ExtAxModelDocServiceImpl implements ExtAxModelDocService {
    @Resource
    private SpringProcessEngineConfiguration springProcessEngineConfiguration;
    @Resource
    private ExtAxModelDocMapper extAxModelDocMapper;
    @Resource
    private ExtAxDocContentService extAxDocContentService;
    @Resource
    private ExtAxReModelService extAxReModelService;
    @Resource
    private CustomEventManager eventPublisher;

    @Override
    public BpmPageResult<DocBaseVO> docPage(DocSearchDTO dto) {
        Page<ExtAxModelDoc> page = extAxModelDocMapper.selectPage(new Page<>(dto.getPageNo(), dto.getPageSize()),
                buildQueryWrapper(BeanMapper.copyBean(dto, ExtAxModelDoc.class)));
        List<DocBaseVO> list = BeanMapper.copyList(page.getRecords(), DocBaseVO.class,
                (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType())));
        return new BpmPageResult<>(list, page.getTotal());
    }

    @Override
    public List<DocBaseVO> docList(DocQueryDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        return commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(),
                dto.getProcessDefinitionKey(), dto.getProcessDefinitionKeys(), dto.getTenantId(), extAxModelDocMapper, extAxReModelService));
    }

    @Override
    public List<ExtAxModelDoc> querySetting(String key, String tenantId) {
        return extAxModelDocMapper.selectList(new LambdaQueryWrapper<ExtAxModelDoc>()
                .eq(StringUtils.hasText(key), ExtAxModelDoc::getModelKey, key)
                .eq(Objects.nonNull(tenantId), ExtAxModelDoc::getTenantId, tenantId)
                .eq(ExtAxModelDoc::getTempFile, false)
        );
    }


    @Override
    public DocBaseVO get(Long docId) {
        return get(docId, false);
    }

    @Override
    public DocBaseVO get(Long docId, boolean ignoreDelete) {
        ExtAxModelDoc doc;
        if (ignoreDelete) {
            doc = extAxModelDocMapper.getIgnoreDelete(docId);
        } else {
            doc = extAxModelDocMapper.selectById(docId);
            if (Objects.isNull(doc)) {
                throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
            }
        }
        return BeanMapper.copyBean(doc, DocBaseVO.class, (s, t) -> {
            t.setFileType(FileTypeEnum.valueOfType(s.getFileType()));
        });
    }

    public static LambdaQueryWrapper<ExtAxModelDoc> buildQueryWrapper(ExtAxModelDoc entity) {
        return new LambdaQueryWrapper<ExtAxModelDoc>()
                .eq(StringUtils.hasText(entity.getModelId()), ExtAxModelDoc::getModelId, entity.getModelId())
                .eq(StringUtils.hasText(entity.getModelKey()), ExtAxModelDoc::getModelKey, entity.getModelKey())
                .eq(StringUtils.hasText(entity.getFileRelationId()), ExtAxModelDoc::getFileRelationId,
                        entity.getFileRelationId())
                .eq(StringUtils.hasText(entity.getFileType()), ExtAxModelDoc::getFileType, entity.getFileType())
                .eq(StringUtils.hasText(entity.getTag()), ExtAxModelDoc::getTag, entity.getTag())
                .eq(Objects.nonNull(entity.getStatus()), ExtAxModelDoc::getStatus, entity.getStatus())
                .eq(Objects.nonNull(entity.getTempFile()), ExtAxModelDoc::getTempFile, entity.getTempFile())
                .eq(Objects.isNull(entity.getTempFile()), ExtAxModelDoc::getTempFile, false)
                .eq(Objects.nonNull(entity.getRequire()), ExtAxModelDoc::getRequire, entity.getRequire())
                .eq(Objects.nonNull(entity.getTenantId()), ExtAxModelDoc::getTenantId, entity.getTenantId())
                .orderByAsc(ExtAxModelDoc::getOrder)
                ;
    }

    public Integer maxOrder(DocCreateDTO dto) {
        if (Objects.equals(Boolean.TRUE, dto.getTempFile())) {
            return 0;
        }
        ExtAxModelDoc orderQuery = new ExtAxModelDoc();
        orderQuery.setModelId(dto.getModelId());
        List<ExtAxModelDoc> docs = extAxModelDocMapper.selectList(buildQueryWrapper(orderQuery));
        return docs.stream().mapToInt(ExtAxModelDoc::getOrder).max().orElse(0) + 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createDoc(DocCreateDTO dto, Boolean fireEvent) {
        // 用于发送文档变更事件
        List<ExtAxModelDoc> oldSettings = Collections.emptyList();
        if (fireEvent) {
            oldSettings = querySetting(dto.getModelKey(), dto.getTenantId());
        }

        if (StringUtils.hasText(dto.getTag()) && !Objects.equals(Boolean.TRUE, dto.getTempFile())) {
            ExtAxModelDoc old = new ExtAxModelDoc();
            old.setModelId(dto.getModelId());
            old.setTag(dto.getTag());
            old.setTenantId(dto.getTenantId());
            Integer count = extAxModelDocMapper.selectCount(buildQueryWrapper(old));
            if (count > 0) {
                throw new WorkflowEngineException(MODEL_FILE_TAG_DUPLICATE);
            }
        }
        ExtAxModelDoc entity = BeanMapper.copyBean(dto, ExtAxModelDoc.class);
        entity.setFileType(dto.getFileType().name());
        entity.setOrder(maxOrder(dto));
        extAxModelDocMapper.insert(entity);
        if (Objects.equals(FileTypeEnum.HIPRINT, dto.getFileType())) {
            ExtAxDocContent content = extAxDocContentService.createContent(dto.getContent(), dto.getFileType(),
                    entity.getId());
            entity.setFileRelationId(String.valueOf(content.getId()));
            extAxModelDocMapper.updateById(entity);
        }

        if (fireEvent) {
            // 发送文档变更事件
            eventPublisher.publishEvent(new DocChangeEventImpl(dto.getModelKey(), dto.getTenantId(),
                    querySetting(dto.getModelKey(), dto.getTenantId()), oldSettings));
        }
        return entity.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateDoc(DocUpdateDTO dto) {
        // 用于发送文档变更事件
        List<ExtAxModelDoc> oldSettings = querySetting(dto.getModelKey(), dto.getTenantId());

        if (StringUtils.hasText(dto.getTag())) {
            ExtAxModelDoc query = new ExtAxModelDoc();
            query.setModelId(dto.getModelId());
            query.setTag(dto.getTag());
            query.setTenantId(dto.getTenantId());
            ExtAxModelDoc old = extAxModelDocMapper.selectOne(buildQueryWrapper(query));
            if (Objects.nonNull(old) && !Objects.equals(old.getId(), dto.getId())) {
                throw new WorkflowEngineException(MODEL_FILE_TAG_EXISTS);
            }
        }
        ExtAxModelDoc entity = extAxModelDocMapper.selectById(dto.getId());
        if (Objects.isNull(entity)) {
            return false;
        }
        if (Objects.equals(FileTypeEnum.HIPRINT, dto.getFileType())) {
            extAxDocContentService.updateContent(dto.getContent(), dto.getFileType(), entity.getFileRelationId());
        }
        BeanUtils.copyProperties(dto, entity);
        entity.setFileType(dto.getFileType().name());
        int flat = extAxModelDocMapper.updateById(entity);

        // 发送文档变更事件
        eventPublisher.publishEvent(new DocChangeEventImpl(dto.getModelKey(), dto.getTenantId(),
                querySetting(dto.getModelKey(), dto.getTenantId()), oldSettings));
        return flat > 0;
    }

    /**
     * 该功能，只能处理 hiprint 类型
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long cloneDoc(DocCloneDTO dto) {
        return cloneDoc(dto, false);
    }

    @Override
    public Long cloneDoc(DocCloneDTO dto, Boolean tempFile) {
        ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getDocId());
        if (Objects.isNull(origin)) {
            throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
        }

        List<ExtAxModelDoc> oldSettings = new ArrayList<>();
        if (!Objects.equals(Boolean.TRUE, tempFile)) {
            // 用于发送文档变更事件
            oldSettings.addAll(querySetting(origin.getModelKey(), origin.getTenantId()));
        }

        if (Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(origin.getFileType()))) {
            DocCreateDTO newDoc = BeanMapper.copyBean(origin, DocCreateDTO.class, (s, t) -> {
                t.setFileType(FileTypeEnum.valueOfType(s.getFileType()));
                t.setTempFile(Objects.equals(Boolean.TRUE, tempFile));
                if (StringUtils.hasText(dto.getTargetTenantId())) {
                    // 没设置租户，则使用原来文档的租户 ID
                    t.setTenantId(dto.getTargetTenantId());
                }
                if (StringUtils.hasText(dto.getTargetModelId())) {
                    // 没设置模型，则使用原来文档的模型 ID
                    t.setModelId(dto.getTargetModelId());
                }
                if (StringUtils.hasText(dto.getTargetFileTag())) {
                    t.setTag(dto.getTargetFileTag());
                }
            });
            newDoc.setContent(extAxDocContentService.getContent(Long.valueOf(origin.getFileRelationId())));
            Long newDocId = createDoc(newDoc, false);

            if (!Objects.equals(Boolean.TRUE, tempFile)) {
                // 发送文档变更事件
                eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(),
                        querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings));
            }
            return newDocId;
        } else {
            throw new WorkflowEngineException(MODEL_FILE_TYPE_CLONE_NOT_SUPPORT);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteDoc(Long docId) {
        ExtAxModelDoc origin = extAxModelDocMapper.selectById(docId);
        if(Objects.isNull(origin)) {
            return false;
        }
        // 用于发送文档变更事件
        List<ExtAxModelDoc> oldSettings = querySetting(origin.getModelKey(), origin.getTenantId());

        if (Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(origin.getFileType()))) {
            extAxDocContentService.deleteContent(origin.getFileRelationId());
        }
        int flat = extAxModelDocMapper.deleteById(docId);

        // 发送文档变更事件
        eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(),
                querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings));
        return flat > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchDeleteDoc(List<Long> docIds) {
        if (CollectionUtils.isEmpty(docIds)) {
            return true;
        }
        List<ExtAxModelDoc> entities = extAxModelDocMapper.selectBatchIds(docIds);
        if (CollectionUtils.isEmpty(entities)) {
            return true;
        }
        List<ExtAxModelDoc> hiprintList = entities.stream()
                .filter(entity -> Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(entity.getFileType())))
                .collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(hiprintList)) {
            extAxDocContentService.batchDeleteContent(hiprintList.stream()
                    .map(ExtAxModelDoc::getFileRelationId)
                    .collect(Collectors.toList()));
        }
        int flat = extAxModelDocMapper.deleteBatchIds(docIds);

        // 发送文档变更事件
        eventPublisher.publishEvent(new DocChangeEventImpl(entities.get(0).getModelKey(),
                entities.get(0).getTenantId(), Collections.emptyList(), entities));
        return flat > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean orderDoc(DocOrderDTO dto) {
        ExtAxModelDoc doc = extAxModelDocMapper.selectById(dto.getId());
        if (Objects.isNull(doc)) {
            throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
        }

        LambdaQueryWrapper<ExtAxModelDoc> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(ExtAxModelDoc::getModelId, doc.getModelId())
                .eq(ExtAxModelDoc::getTempFile, false);
        Integer currentOrder = doc.getOrder();
        if (Objects.equals(dto.getOrder(), OrderEnum.UP)) {
            orderWrapper.eq(ExtAxModelDoc::getOrder, currentOrder - 1);
            ExtAxModelDoc upDoc = extAxModelDocMapper.selectOne(orderWrapper);
            if (Objects.isNull(upDoc)) {
                return false;
            }
            doc.setOrder(currentOrder - 1);
            upDoc.setOrder(currentOrder);
            extAxModelDocMapper.updateById(upDoc);
        } else {
            orderWrapper.eq(ExtAxModelDoc::getOrder, currentOrder + 1);
            ExtAxModelDoc downDoc = extAxModelDocMapper.selectOne(orderWrapper);
            if (Objects.isNull(downDoc)) {
                return false;
            }
            doc.setOrder(currentOrder + 1);
            downDoc.setOrder(currentOrder);
            extAxModelDocMapper.updateById(downDoc);
        }
        extAxModelDocMapper.updateById(doc);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean statusDoc(DocStatusDTO dto) {
        ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getId());
        if (Objects.isNull(origin)) {
            throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
        }
        // 用于发送文档变更事件
        List<ExtAxModelDoc> oldSettings = querySetting(origin.getModelKey(), origin.getTenantId());

        origin.setStatus(Objects.equals(Boolean.TRUE, dto.getStatus()));
        int flat = extAxModelDocMapper.updateById(origin);

        // 发送文档变更事件
        eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(),
                querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings));
        return flat > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean requireDoc(DocStatusDTO dto) {
        ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getId());
        if (Objects.isNull(origin)) {
            throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
        }
        origin.setRequire(Objects.equals(Boolean.TRUE, dto.getStatus()));
        return extAxModelDocMapper.updateById(origin) > 0;
    }

    @Override
    public List<DocBaseVO> getIds(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<ExtAxModelDoc> docs = extAxModelDocMapper.selectList(new LambdaQueryWrapper<ExtAxModelDoc>()
                .in(ExtAxModelDoc::getId, ids)
                .eq(ExtAxModelDoc::getTempFile, false)
                .eq(ExtAxModelDoc::getIsDelete, 0));
        return BeanMapper.copyList(docs, DocBaseVO.class,
                (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType())));
    }
}
