/*
 * Copyright (c) 京北方信息技术股份有限公司 Corporation 2021 . All rights reserved.
 *
 */
package net.northking.cctp.flowable.api.sm.service;

import net.northking.cctp.common.cache.EntityNameCache;
import net.northking.cctp.common.cache.EntityNameCatalog;
import net.northking.cctp.common.db.DefaultPagination;
import net.northking.cctp.common.db.Pagination;
import net.northking.cctp.common.dto.CpLogAddDto;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.feign.MonsterLogServer;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.flowable.bus.mq.FlowPublisher;
import net.northking.cctp.flowable.bus.platform.LogPublisher;
import net.northking.cctp.flowable.common.FlowableConstants;
import net.northking.cctp.flowable.common.enums.ItemType;
import net.northking.cctp.flowable.common.exception.FlowAppError;
import net.northking.cctp.flowable.db.entity.*;
import net.northking.cctp.flowable.db.jpa.*;
import net.northking.cctp.flowable.dto.sm.CltDictItem;
import net.northking.cctp.flowable.dto.sm.StateAuthDto;
import net.northking.cctp.flowable.dto.sm.StateProcessDefDto;
import net.northking.cctp.flowable.dto.sm.SyncCltDictItemDto;
import net.northking.cctp.flowable.sm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 状态流程定义接口服务实现类
 *
 * @author : xiusong.xie
 * @version : 1.0
 */
@Service
public class StateProcessDefApiServiceImpl implements StateProcessDefApiService {
    /**
     * 日志
     */
    private final static Logger logger = LoggerFactory.getLogger(StateProcessDefApiServiceImpl.class);

    /**
     * 流程仓库
     */
    @Autowired
    private SpDefProcessRepository processRepository;

    /**
     * 状态仓库
     */
    @Autowired
    private SpDefStateRepository stateRepository;

    /**
     * 事件仓库
     */
    @Autowired
    private SpDefEventRepository eventRepository;

    /**
     * 事件角色关系
     */
    @Autowired
    private SpDefEventAuthRepository eventAuthRepository;

    /**
     * 流程与角色关系
     */
    @Autowired
    private SpDefProcessAuthRepository processAuthRepository;

    /**
     * 事件扩展仓库
     */
    @Autowired
    private SpDefItemExtRepository itemExtRepository;

    @Autowired
    private EntityNameCache entityNameCache;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private FlowPublisher flowPublisher;

    @Autowired
    private LogPublisher logPublisher;


    @Override
    @Transactional
    public String save(StateProcessDefinition dto) {
        // 名称唯一校验
        duplicateNameCheck(dto);
        // 1.保存流程定义表
        SpDefProcess spDefProcess = new SpDefProcess();
        BeanUtils.copyProperties(dto, spDefProcess);
        Optional<SpDefProcess> dbResult = processRepository.findById(dto.getId());
        if (dbResult.isPresent()) {
            spDefProcess.setCreatedBy(dbResult.get().getCreatedBy());
            spDefProcess.setCreatedTime(dbResult.get().getCreatedTime());
        }
        processRepository.save(spDefProcess);

        // 2.保存状态表
        List<SpDefState> states = new ArrayList<>();
        for (State state : dto.getStates()) {
            SpDefState defState = new SpDefState();
            BeanUtils.copyProperties(state, defState);
            defState.setSpId(state.getProcessDefId());
            states.add(defState);
        }
        // 先删除
        SpDefState delState = new SpDefState();
        delState.setSpId(dto.getId());
        List<SpDefState> stateDeleteList = stateRepository.findAll(Example.of(delState));
        stateRepository.deleteAll(stateDeleteList);
        stateRepository.flush();
        // 再插入
        if (!states.isEmpty()) {
            stateRepository.saveAll(states);
        }
        // 3.保存事件表
        List<SpDefEvent> events = new ArrayList<>();
        List<SpDefItemExt> itemExts = new ArrayList<>();
        for (Event event : dto.getEvents()) {
            SpDefEvent defEvent = new SpDefEvent();
            BeanUtils.copyProperties(event, defEvent);
            defEvent.setSpId(dto.getId());
            defEvent.setStartPoint(event.getStartPoint().toString());
            defEvent.setEndPoint(event.getEndPoint().toString());
            events.add(defEvent);
            // 处理代办和通知
            SpDefItemExt item = new SpDefItemExt();
            BeanUtils.copyProperties(event, item);
            item.setItemId(event.getId());
            item.setSpId(dto.getId());
            item.setId(UUIDUtil.create32UUID());
            item.setItemType(ItemType.EVENT.getCode());
            itemExts.add(item);
        }
        // 先删除
        SpDefEvent delEvent = new SpDefEvent();
        delEvent.setSpId(dto.getId());
        List<SpDefEvent> eventDeleteList = eventRepository.findAll(Example.of(delEvent));
        eventRepository.deleteAll(eventDeleteList);
        eventRepository.flush();
        // 再插入
        if (!events.isEmpty()) {
            eventRepository.saveAll(events);
        }
        // 4.保存事件角色关系表
        List<SpDefEventAuth> defEventAuths = new ArrayList<>();
        List<EventAuthority> authorities = dto.getAuthorities();
        for (EventAuthority authority : authorities) {
            SpDefEventAuth defEventAuth = new SpDefEventAuth();
            BeanUtils.copyProperties(authority, defEventAuth);
            defEventAuth.setSpId(authority.getProcessDefId());
            if (StringUtils.isEmpty(defEventAuth.getId())) {
                defEventAuth.setId(UUIDUtil.create32UUID());
            }
            defEventAuths.add(defEventAuth);
        }
        SpDefEventAuth delEventAuth = new SpDefEventAuth();
        delEventAuth.setSpId(dto.getId());
        List<SpDefEventAuth> eventAuthDelList = eventAuthRepository.findAll(Example.of(delEventAuth));
        eventAuthRepository.deleteAll(eventAuthDelList);
        eventAuthRepository.flush();
        if (!defEventAuths.isEmpty()) {
            eventAuthRepository.saveAll(defEventAuths);
        }

        // 5.保存活动表

        // 6.保存所有到所有的
        List<String> anyAuthorities = dto.getAnyAuthorities();
        List<SpDefProcessAuth> processAuths = new ArrayList<>();
        if (dto.getAllowedAnyToAny() && !dto.getAnyAuthorities().isEmpty()) {
            for (String role : anyAuthorities) {
                SpDefProcessAuth temp = new SpDefProcessAuth();
                temp.setId(UUIDUtil.create32UUID());
                temp.setSpId(dto.getId());
                temp.setAuthority(role);
                temp.setTenantId(NKSecurityContext.getTenantId());
                processAuths.add(temp);
            }
        }
        // 先删除
        SpDefProcessAuth delPA = new SpDefProcessAuth();
        delPA.setSpId(dto.getId());
        List<SpDefProcessAuth> paDeleteList = processAuthRepository.findAll(Example.of(delPA));
        processAuthRepository.deleteAll(paDeleteList);
        processAuthRepository.flush();
        if (!processAuths.isEmpty()) {
            processAuthRepository.saveAll(processAuths);
        }
        // 7.保存扩展
        // 先删除
        SpDefItemExt delItem = new SpDefItemExt();
        delItem.setSpId(dto.getId());
        List<SpDefItemExt> itemExtDeleteList = itemExtRepository.findAll(Example.of(delItem));
        itemExtRepository.deleteAll(itemExtDeleteList);
        itemExtRepository.flush();
        // 再新增
        if (!itemExts.isEmpty()) {
            itemExtRepository.saveAll(itemExts);
        }
        // 处理终态缓存
        cacheRefresh(dto);

        //发送mq消息(流程id、数据字典列表)到projects
        List<State> states1 = dto.getStates();
        if (null != states1 && states1.size() != 0 && FlowableConstants.BUG_STATUS.equals(dto.getCatalogCode())) {
            //处理发送对象
            SyncCltDictItemDto s = new SyncCltDictItemDto();
            List<CltDictItem> cltDictItemList = new ArrayList<>();
            s.setBugProcessId(states1.get(0).getProcessDefId());
            states1.forEach(state -> {
                CltDictItem c = new CltDictItem();
                c.setItemCode(state.getLabel());
                c.setItemName(state.getLabelText());
                c.setFinishedState(state.getFinishedState());
                cltDictItemList.add(c);
            });
            s.setCltDictItemList(cltDictItemList);
            //发消息
            flowPublisher.stateListChangedSend(s);
        }
        return dto.getId();
    }

    /**
     * 分页查询
     *
     * @param queryByPage 查询条件
     * @return
     */
    @Override
    public Pagination<StateProcessDefDto> pagingQuery(QueryByPage<SpDefProcess> queryByPage) {
        Pagination<StateProcessDefDto> result = new DefaultPagination<>(queryByPage.getPageNo(), queryByPage.getPageSize());
        ArrayList<StateProcessDefDto> records = new ArrayList<>();
        if (queryByPage.getQuery() == null) {
            queryByPage.setQuery(new SpDefProcess());
        }
        if (!queryByPage.hasOrderBy()) {
            queryByPage.addQueryOrderByDesc(SpDefProcess.KEY_createdTime);
        }
        queryByPage.getQuery().setDeleted(false);
        //判断name是否为空
        if (StringUtils.isEmpty(queryByPage.getQuery().getName())){
            Pagination<SpDefProcess> page = processRepository.findAll(queryByPage);
            for (SpDefProcess record : page.getRecords()) {
                StateProcessDefDto process = new StateProcessDefDto();
                BeanUtils.copyProperties(record, process);
                String cacheCreated = entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, process.getCreatedBy());
                process.setCreatedBy(StringUtils.isEmpty(cacheCreated) ? process.getCreatedBy() : cacheCreated );
                String cacheUpdated = entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, process.getUpdatedBy());
                process.setUpdatedBy(StringUtils.isEmpty(cacheUpdated) ? process.getUpdatedBy() : cacheUpdated);
                records.add(process);
            }
            result.setRecords(records);
            result.setRecordCount(page.getRecordCount());
        }else {
            //模糊查询
            Integer pageNo = (queryByPage.getPageNo() - 1) * queryByPage.getPageSize();
            List<SpDefProcess> page = processRepository.querySpDefProcess(queryByPage.getQuery().getName(),
                    NKSecurityContext.getTenantId(), queryByPage.getQuery().getDeleted(), pageNo, queryByPage.getPageSize());
            //查询数量
            Integer count = processRepository.countSpDefProcess(queryByPage.getQuery().getName(),
                    NKSecurityContext.getTenantId(), queryByPage.getQuery().getDeleted());

            for (SpDefProcess record : page) {
                StateProcessDefDto process = new StateProcessDefDto();
                BeanUtils.copyProperties(record, process);
                String cacheCreated = entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, process.getCreatedBy());
                process.setCreatedBy(StringUtils.isEmpty(cacheCreated) ? process.getCreatedBy() : cacheCreated );
                String cacheUpdated = entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, process.getUpdatedBy());
                process.setUpdatedBy(StringUtils.isEmpty(cacheUpdated) ? process.getUpdatedBy() : cacheUpdated);
                records.add(process);
            }
            result.setRecords(records);
            result.setRecordCount(count);
            result.setPageNo(queryByPage.getPageNo());
            result.setPageSize(queryByPage.getPageSize());
        }
        return result;
    }

    /**
     * 根据id查询流程信息
     *
     * @param uuid 主键
     * @return
     */
    @Override
    public StateProcessDefinition findByPK(String uuid) {
        Optional<SpDefProcess> optional = processRepository.findById(uuid);

        if (optional.isPresent()) {
            SpDefProcess spDefProcess = optional.get();
            StateProcessDefinition result = new StateProcessDefinition();
            BeanUtils.copyProperties(spDefProcess, result);
            // TODO: 动作
            // 状态节点
            SpDefState stateTemp = new SpDefState();
            stateTemp.setSpId(uuid);
            List<SpDefState> stateResults = stateRepository.findAll(Example.of(stateTemp));
            List<State> stateList = new ArrayList<>();
            if (!stateResults.isEmpty()) {
                stateResults.forEach(one -> {
                    State state = new State();
                    BeanUtils.copyProperties(one, state);
                    state.setProcessDefId(one.getSpId());
                    stateList.add(state);
                });
            }
            result.setStates(stateList);
            // 每个事件的角色
            SpDefEventAuth authTemp = new SpDefEventAuth();
            authTemp.setSpId(uuid);
            List<SpDefEventAuth> authResults = eventAuthRepository.findAll(Example.of(authTemp));
            Map<String, List<String>> authMap = new HashMap<>();
            if (!authResults.isEmpty()) {
                authResults.forEach(one -> {
                    if (!authMap.containsKey(one.getEventId()) || authMap.get(one.getEventId()) == null) {
                        List<String> temp = new ArrayList<>();
                        authMap.put(one.getEventId(), temp);
                    }
                    List<String> authList = authMap.get(one.getEventId());
                    authList.add(one.getAuthority());
                });
            }
            // 每个时间的扩展
            SpDefItemExt itemTemp = new SpDefItemExt();
            itemTemp.setSpId(uuid);
            List<SpDefItemExt> itemResult = itemExtRepository.findAll(Example.of(itemTemp));
            Map<String, SpDefItemExt> itemMap = new HashMap<>();
            if (!itemResult.isEmpty()) {
                itemResult.forEach(one -> {
                    itemMap.put(one.getItemId(), one);
                });
            }
            // 事件（连接线）
            SpDefEvent eventTemp = new SpDefEvent();
            eventTemp.setSpId(uuid);
            List<SpDefEvent> eventResults = eventRepository.findAll(Example.of(eventTemp));
            List<Event> eventList = new ArrayList<>();
            if (!eventResults.isEmpty()) {
                eventResults.forEach(one -> {
                    Event event = new Event();
                    BeanUtils.copyProperties(one, event);
                    event.setStartPoint(new Coordinate(one.getStartPoint()));
                    event.setEndPoint(new Coordinate(one.getEndPoint()));
                    event.setProcessDefId(one.getSpId());
                    event.setAuthorities(authMap.get(one.getId())); // 从map中通过事件id获取角色列表
                    // 从map中获取扩展属性
                    if (itemMap.containsKey(one.getId())) {
                        SpDefItemExt ext = itemMap.get(one.getId());
                        event.setEnableNotice(ext.getEnableNotice());
                        event.setEnableTodo(ext.getEnableTodo());
                    }
                    eventList.add(event);
                });
            }
            result.setEvents(eventList);
            // 所有到所有
            if (result.getAllowedAnyToAny() != null && result.getAllowedAnyToAny()) {
                SpDefProcessAuth pa = new SpDefProcessAuth();
                pa.setSpId(uuid);
                List<SpDefProcessAuth> paResult = processAuthRepository.findAll(Example.of(pa));
                List<String> paList = new ArrayList<>();
                if (!paResult.isEmpty()) {
                    paResult.forEach(one -> paList.add(one.getAuthority()));
                }
                result.setAnyAuthorities(paList);
            }
            return result;
        } else {
            return null;
        }
    }

    @Override
    @Transactional
    public Integer deleteByPK(String uuid) {

        Optional<SpDefProcess> idOptional = processRepository.findById(uuid);
        if (!idOptional.isPresent()) {
            throw new PlatformRuntimeException(FlowAppError.DELETE_FAILED);
        }
        SpDefProcess result = idOptional.get();
        if (result.getDefaulted()) {
            throw new PlatformRuntimeException(FlowAppError.FLOW_IS_DEFAULTED_NOT_ALLOWED_DELETED);
        }
        Boolean isDelete = result.getDeleted();
        result.setDeleted(true);
        try {
            processRepository.save(result);
            if (!isDelete){
                CpLogAddDto cpLogAddDto = new CpLogAddDto();
                cpLogAddDto.setContent(FlowableConstants.DEL_STATE_PROCESS + result.getName());
                cpLogAddDto.setModule(FlowableConstants.STATE_PROCESS_MODULE);
                cpLogAddDto.setType(FlowableConstants.LOG_STATUS_DEL);
                MonsterLogServer.sendLogToMonster(cpLogAddDto);
                logPublisher.deployLogContent(cpLogAddDto);
            }
            return 1;
        } catch (Exception e) {
            logger.error("删除失败", e);
        }
        return 0;
    }

    @Override
    @Transactional
    public Integer deleteBatch(List<String> batch) {
        int result = 0;
        for (String one : batch) {
            result += this.deleteByPK(one);
        }
        return result;
    }

    /**
     * 更改流程是否默认
     *
     * @param uuid
     * @return
     */
    @Override
    @Transactional
    public Integer updateDefault(String uuid) {
        try {
            Optional<SpDefProcess> idOptional = processRepository.findById(uuid);
            if (!idOptional.isPresent()) {
                throw new PlatformRuntimeException(FlowAppError.FLOW_IS_NOT_EXIST);
            }
            // 取消现在的
            SpDefProcess query = new SpDefProcess();
            query.setDeleted(false);
            query.setDefaulted(true);
            query.setTenantId(NKSecurityContext.getTenantId());
            List<SpDefProcess> defaultedList = processRepository.findAll(Example.of(query));
            for (SpDefProcess old : defaultedList) {
                old.setDefaulted(false);
            }
            processRepository.saveAll(defaultedList);
            // 将选中的更新
            SpDefProcess process = idOptional.get();
            process.setDefaulted(true);
            processRepository.save(process);
            return 1;
        } catch (Exception e) {
            logger.error(FlowAppError.SET_DEFAULTED_IS_FAILED.getMsg(), e);
            throw new PlatformRuntimeException(FlowAppError.SET_DEFAULTED_IS_FAILED);
        }
    }

    /**
     * 获取下拉状态值
     *
     * @param stateAuth
     * @return
     */
    @Override
    public List<String> queryAvailableStates(StateAuthDto stateAuth) {
        List<String> availableStates = new ArrayList<String>();
        // 流程的所有到所有中是否包含传入角色信息
        SpDefProcessAuth paQuery = new SpDefProcessAuth();
        paQuery.setSpId(stateAuth.getProcessId());
        List<SpDefProcessAuth> allProcessAutList = processAuthRepository.findAll(Example.of(paQuery));
        if (stateAuth.getRoleList() != null && !stateAuth.getRoleList().isEmpty()) {
            long count = allProcessAutList.stream().filter(one -> stateAuth.getRoleList().contains(one.getAuthority())).count();
            if (count > 0L) {
                // 查询流程所有状态
                SpDefState allStateQuery = new SpDefState();
                allStateQuery.setSpId(stateAuth.getProcessId());
                List<SpDefState> allStateList = stateRepository.findAll(Example.of(allStateQuery));
                for (SpDefState one : allStateList) {
                    availableStates.add(one.getLabel());
                }
            } else {

                // 处理没有所有到所有
                SpDefState stateQuery = new SpDefState();
                stateQuery.setSpId(stateAuth.getProcessId());
                stateQuery.setLabel(stateAuth.getStatus());
                Optional<SpDefState> optional = stateRepository.findOne(Example.of(stateQuery));
                if (optional.isPresent()) { // 如果存在该状态，查询下一可用状态
                    SpDefState current = optional.get();
                    availableStates = stateRepository.queryAvailableStates(current.getId(), stateAuth.getRoleList(), NKSecurityContext.getTenantId());
                }
            }
        }
        return availableStates;
    }

    /**
     * 获取可选角色
     *
     * @param stateAuth
     * @return
     */
    @Override
    public List<String> queryAvailableRoles(StateAuthDto stateAuth) {
        List<String> availableRoles = new ArrayList<>();
        availableRoles = stateRepository.queryAvailableRoles(stateAuth.getProcessId(), stateAuth.getStatus());
        return availableRoles;
    }

    /**
     * 条件获取状态
     *
     * @param stateAuth
     * @return
     */
    @Override
    public List<String> querySpecialState(StateAuthDto stateAuth) {
        List<String> resultList = new ArrayList<>();
        SpDefState query = new SpDefState();
        query.setSpId(stateAuth.getProcessId());
        if (!stateAuth.getInitialState() && !stateAuth.getFinishedState()) {
            query.setInitialState(null);
            query.setFinishedState(null);
        } else {
            query.setInitialState(stateAuth.getInitialState());
            query.setFinishedState(stateAuth.getFinishedState());
        }
        List<SpDefState> stateList = stateRepository.findAll(Example.of(query));
        for (SpDefState state : stateList) {
            resultList.add(state.getLabel());
        }
        return resultList;
    }



    private void putFinishedState(String flowId, List<String> states) {
        redisTemplate.opsForValue().set(FlowableConstants.FINISHED_PREFIX + flowId, states);
    }

    /**
     * 重名检查
     *
     * @param dto
     */
    private void duplicateNameCheck(StateProcessDefinition dto) {
        SpDefProcess temp = new SpDefProcess();
        temp.setTenantId(NKSecurityContext.getTenantId());
        temp.setName(dto.getName());
        temp.setDeleted(false);
        List<SpDefProcess> nameList = processRepository.findAll(Example.of(temp));
        if (nameList.size() > 0
                && nameList.get(0).getName().equals(dto.getName())
                && !nameList.get(0).getId().equals(dto.getId())) {
            throw new PlatformRuntimeException(FlowAppError.FLOW_NAME_IS_DULICATED);
        }
    }

    /**
     * 刷新终态缓存
     */
    @Override
    public void reloadFinishedCache() {
        SpDefState state = new SpDefState();
        state.setFinishedState(true);
        List<SpDefState> all = stateRepository.findAll(Example.of(state));
        Map<String, List<String>> stateMap = new HashMap<>();
        if (!all.isEmpty()) {
            for (SpDefState temp : all) {
                if (stateMap.containsKey(temp.getSpId())) {
                    stateMap.get(temp.getSpId()).add(temp.getLabel());
                } else {
                    stateMap.put(temp.getSpId(), new ArrayList<>());
                }
            }
        }
        if (!stateMap.isEmpty()) {
            for (String key : stateMap.keySet()) {
                this.putFinishedState(key, stateMap.get(key));
            }
        }
        logger.info("已加载【{}】条缺陷流程终态信息到缓存中", stateMap.size());
    }

    private void cacheRefresh(StateProcessDefinition dto) {
        // 处理特殊状态的缓存
        List<State> finishedStates = dto.findFinishedStates();
        List<String> finished = new ArrayList<>();
        if (finishedStates.size() > 0) {
            finishedStates.forEach(one -> finished.add(one.getLabel()));
        }
        putFinishedState(dto.getId(), finished);
        // 发送消息
        flowPublisher.finishedChangedSend(dto.getId(), dto.getName());
    }

    @Override
    public List<SpDefState> queryBugProcessTemplateState(StateAuthDto stateAuth) {
        List<SpDefState> resultList = new ArrayList<>();
        SpDefState query = new SpDefState();
        query.setSpId(stateAuth.getProcessId());
        List<SpDefState> stateList = stateRepository.findAll(Example.of(query));
        for (SpDefState state : stateList) {
            SpDefState s=new SpDefState();
            s.setLabel(state.getLabel());
            s.setFinishedState(state.getFinishedState());
            resultList.add(s);
        }
        return resultList;
    }

}
