package cn.axzo.msg.center.message.service.todo;

import cn.axzo.framework.domain.web.result.ApiListResult;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.domain.entity.PendingRecordAdapter;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.service.group.GroupTemplateService;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.message.service.impl.v3.V3ExtPopulator;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.TodoQueryType;
import cn.axzo.msg.center.service.enums.TodoType;
import cn.axzo.msg.center.service.pending.request.GetTodoCountRequest;
import cn.axzo.msg.center.service.pending.request.GetTodoPageRequest;
import cn.axzo.msg.center.service.pending.request.GetTodoRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageByBizCodeRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageDetailRequestV3;
import cn.axzo.msg.center.service.pending.request.PendingMessageQueryRequest;
import cn.axzo.msg.center.service.pending.response.PendingMessageCountDTO;
import cn.axzo.msg.center.service.pending.response.PendingMessageResponse;
import cn.axzo.msg.center.service.pending.response.PendingMessageSimpleDTO;
import cn.azxo.framework.common.model.Page;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

/**
 * @author yanglin
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoSimpleQueryService {

    private final TodoBusinessDao todoBusinessDao;
    private final TodoDao todoDao;
    private final TodoRespBuilder todoRespBuilder;
    private final PendingMessageBizConfig cfg;
    private final V3ExtPopulator v3ExtPopulator;
    private final GroupTemplateService groupTemplateService;

    public PendingMessageResponse query(PendingMessageQueryRequest req) {
        // 虽然通过code可以查询到唯一的记录, 把一些事情交给数据库去库, 成本不高
        Todo todo = todoDao.lambdaQuery()
                .eq(Todo::getIdentityCode, req.getMsgIdentityCode())
                .eq(req.getMsgState() != null, Todo::getState, req.getMsgState())
                .eq(req.getWorkspaceId() != null, Todo::getOrgId, req.getWorkspaceId())
                .last("LIMIT 1")
                .one();
        if (todo == null)
            return null;
        // 虽然通过business id可以查询到唯一的记录, 把一些事情交给数据库去库, 成本不高
        TodoBusiness business = todoBusinessDao.lambdaQuery()
                .eq(TodoBusiness::getId, todo.getTodoBusinessId())
                .eq(req.getBizFinalState() != null, TodoBusiness::getBizFinalState, req.getBizFinalState())
                .like(StringUtils.isNotBlank(req.getTitle()), TodoBusiness::getTitle, req.getTitle())
                .last("LIMIT 1")
                .one();
        PendingMessageResponse response = todoRespBuilder.toResponse(business, todo, req);
        v3ExtPopulator.populate(response.getModelV3());
        return response;
    }

    public PendingMessageResponse getDetail(PendingMessageDetailRequestV3 request) {
        Todo todo = todoDao.findTodoByCode(request.getMsgIdentityCode()).orElse(null);
        if (todo == null)
            return null;
        if (request.getQueryType() == TodoQueryType.SEND_BY_ME) {
            TodoBusiness business = todoBusinessDao.getById(todo.getTodoBusinessId());
            PendingRecordAdapter adapter = todoRespBuilder.buildBusinessAdapter(business);
            return todoRespBuilder.toResponse(adapter, request);
        }
        PendingMessageResponse response = todoRespBuilder.toResponse(todo, request);
        v3ExtPopulator.populate(response.getModelV3());
        return response;
    }

    public PendingMessageResponse getLatestTodoByBiz(
            String templateCode, String bizCode, String subBizCode, TodoType todoType) {
        Todo todo = todoDao
                .getLatestByBiz(templateCode, bizCode, subBizCode, todoType)
                .orElse(null);
        return todo == null ? null : todoRespBuilder.toResponse(todo, null);
    }

    public List<PendingMessageSimpleDTO> getLatestTodosByBiz(PendingMessageByBizCodeRequest param) {
        List<String> bizCodes = param.determineBizCodes();
        if (CollectionUtils.isEmpty(bizCodes))
            return Collections.emptyList();
        BizAssertions.assertTrue(bizCodes.size() <= cfg.getGetLatestByBizCodeMaxSize(),
                String.format("bizCode的数量不能超过 %d", cfg.getGetLatestByBizCodeMaxSize()));
        List<Todo> todos = todoDao.lambdaQuery()
                .in(Todo::getBizCode, bizCodes)
                .eq(Todo::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .list();
        Map<String, List<Todo>> bizCode2Todos = todos.stream()
                .collect(groupingBy(Todo::getBizCode));
        List<Todo> latestTodos = new ArrayList<>();
        for (List<Todo> todosPerBizCode : bizCode2Todos.values()) {
            List<Todo> filteredTodos = new ArrayList<>(todosPerBizCode.size());
            // 存在操作人
            if (param.getOperatorPersonId() != null)
                filteredTodos = todosPerBizCode.stream()
                        .filter(e -> e.getExecutorPersonId().equals(param.getOperatorPersonId()))
                        .collect(toList());
            else if (null != param.getOperatorIdentityId())
                filteredTodos = todosPerBizCode.stream()
                        .filter(e -> e.getExecutorId().equals(param.getOperatorIdentityId()))
                        .collect(toList());
            // 流程最终的记录
            List<Todo> finalTodos = CollectionUtils.isEmpty(filteredTodos) ? todosPerBizCode : filteredTodos;
            // 获取最新的待办
            finalTodos.stream()
                    .max(comparing(Todo::getId))
                    .ifPresent(latestTodos::add);
        }
        return todoRespBuilder.buildTodosSimple(latestTodos);
    }

    public List<PendingMessageSimpleDTO> getTodosSimple(GetTodoRequest request) {
        List<Todo> todos = todoDao.lambdaQuery()
                .in(CollectionUtils.isNotEmpty(request.getIdentityCodes()), Todo::getIdentityCode, request.getIdentityCodes())
                .in(CollectionUtils.isNotEmpty(request.getTemplateCodes()), Todo::getTemplateCode, request.getTemplateCodes())
                .in(CollectionUtils.isNotEmpty(request.getBizCodes()), Todo::getBizCode, request.getBizCodes())
                .in(CollectionUtils.isNotEmpty(request.getStates()), Todo::getState, request.getStates())
                .and(CollectionUtils.isNotEmpty(request.getExecutors()), wrapper -> {
                    for (GetTodoRequest.Executor executor : request.getExecutors()) {
                        wrapper.or()
                                .eq(executor.getOuId() != null, Todo::getOuId, executor.getOuId())
                                .eq(executor.getOuId() != null, Todo::getOrgId, executor.getWorkspaceId())
                                .in(CollectionUtils.isNotEmpty(executor.getPersonIds()), Todo::getExecutorPersonId, executor.getPersonIds());
                    }
                })
                .exists(request.getBizCategory() != null, String.format("select id from todo_business as b where b.id=todo_business_id and b.biz_category = '%s'", request.getBizCategory() == null ? "" : request.getBizCategory().name()))
                .last("LIMIT " + cfg.getGetTodoSimpleSize())
                .list();
        return todoRespBuilder.buildTodosSimple(todos);
    }

    public Map<BizCategoryEnum, PendingMessageCountDTO> getTodosSimpleCount(GetTodoCountRequest request) {
        Map<BizCategoryEnum, PendingMessageCountDTO> resultMap = new EnumMap<>(BizCategoryEnum.class);
        BizCategoryEnum[] values;
        if (request.getBizCategory() == null) {
            values = BizCategoryEnum.values();
        } else {
            values = new BizCategoryEnum[]{request.getBizCategory()};
        }
        for (BizCategoryEnum bizCategory : values) {
            List<Todo> todos = todoDao.lambdaQuery()
                    .in(CollectionUtils.isNotEmpty(request.getIdentityCodes()), Todo::getIdentityCode, request.getIdentityCodes())
                    .in(CollectionUtils.isNotEmpty(request.getTemplateCodes()), Todo::getTemplateCode, request.getTemplateCodes())
                    .in(CollectionUtils.isNotEmpty(request.getBizCodes()), Todo::getBizCode, request.getBizCodes())
                    .eq(Todo::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                    .and(CollectionUtils.isNotEmpty(request.getExecutors()), wrapper -> {
                        for (GetTodoCountRequest.Executor executor : request.getExecutors()) {
                            wrapper.or()
                                    .eq(executor.getOuId() != null, Todo::getOuId, executor.getOuId())
                                    .eq(executor.getWorkspaceId() != null, Todo::getOrgId, executor.getWorkspaceId())
                                    .in(CollectionUtils.isNotEmpty(executor.getPersonIds()), Todo::getExecutorPersonId, executor.getPersonIds());
                        }
                    })
                    .exists(String.format("select id from todo_business as b where b.id=todo_business_id and b.biz_category = '%s'", bizCategory.name()))
                    .select(Todo::getId, Todo::getIdentityCode, Todo::getOuId, Todo::getExecutorPersonId, Todo::getOrgId, Todo::getState, Todo::getTodoBusinessId, Todo::getTemplateCode)
                    .list();
            if (todos == null) {
                todos = Collections.emptyList();
            }
            List<PendingMessageCountDTO.SinglePendingDto> pendingDtos = todos.stream()
                    .map(todo -> PendingMessageCountDTO.SinglePendingDto.builder()
                            .identityCode(todo.getIdentityCode())
                            .templateCode(todo.getTemplateCode())
                            .executePersonId(todo.getExecutorPersonId())
                            .ouId(todo.getOuId())
                            .workspaceId(todo.getOrgId())
                            .build())
                    .collect(toList());
            PendingMessageCountDTO dto = PendingMessageCountDTO.builder().count(pendingDtos.size()).dtos(pendingDtos).build();
            resultMap.put(bizCategory, dto);
        }
        return resultMap;
    }

    public Page<PendingMessageSimpleDTO> getTodosSimplePage(GetTodoPageRequest request) {
        List<String> groupTempCodes = null;
        if (!request.determineGroupNodeCodes().isEmpty()) {
            groupTempCodes = groupTemplateService.collectTemplateCodes(request.determineGroupNodeCodes());
        } else if (request.getAppTerminalType() != null) {
            List<ValueNode<NodeWrapper>> nodes = groupTemplateService.getTodoGroups(request.getAppTerminalType());
            groupTempCodes = groupTemplateService.collectTemplateCodes(nodes);
        }
        List<String> valuableTempCodes = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(groupTempCodes)) {
            if (CollectionUtils.isNotEmpty(request.getTemplateCodes())) {
                valuableTempCodes.addAll(request.getTemplateCodes().stream()
                        .filter(groupTempCodes::contains)
                        .collect(Collectors.toList()));
            } else {
                valuableTempCodes.addAll(groupTempCodes);
            }
        } else if (CollectionUtils.isNotEmpty(request.getTemplateCodes())) {
            valuableTempCodes.addAll(request.getTemplateCodes());
        }

        IPage<Todo> page = todoDao.lambdaQuery()
                .in(CollectionUtils.isNotEmpty(request.getIdentityCodes()), Todo::getIdentityCode, request.getIdentityCodes())
                .in(CollectionUtils.isNotEmpty(valuableTempCodes), Todo::getTemplateCode, valuableTempCodes)
                .in(CollectionUtils.isNotEmpty(request.getBizCodes()), Todo::getBizCode, request.getBizCodes())
                .in(CollectionUtils.isNotEmpty(request.getStates()), Todo::getState, request.getStates())
                .likeRight(StringUtils.isNotBlank(request.getTitleLike()), Todo::getTitle, request.getTitleLike())
                .and(CollectionUtils.isNotEmpty(request.getExecutors()), wrapper -> {
                    for (GetTodoPageRequest.Executor executor : request.getExecutors()) {
                        wrapper.or()
                                .eq(executor.getOuId() != null, Todo::getOuId, executor.getOuId())
                                .eq(executor.getWorkspaceId() != null, Todo::getOrgId, executor.getWorkspaceId())
                                .in(CollectionUtils.isNotEmpty(executor.getPersonIds()), Todo::getExecutorPersonId, executor.getPersonIds());
                    }
                })
                .exists(request.getBizCategory() != null, String.format("select id from todo_business as b where b.id=todo_business_id and b.biz_category = '%s'", request.getBizCategory() == null ? "" : request.getBizCategory().name()))
                .page(request.toPage());
        List<PendingMessageSimpleDTO> messageSimpleDTOS = todoRespBuilder.buildTodosSimple(page.getRecords());
        return Page.toPage(page.getCurrent(), page.getSize(), page.getTotal(), messageSimpleDTOS);
    }

    /**
     * 常用的RPC请求返回值解析，如果 被请求方 返回非200会抛出异常
     *
     * @param supplier       ApiListResult类型的RPC接口
     * @param operationType  接口方法描述
     * @param param          接口入参
     * @return 接口返回值
     */
    public static <T> List<T> rpcApiListResultProcessor(Supplier<ApiListResult<T>> supplier, String operationType, Object... param) {
        log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
        ApiListResult<T> result = printLatency(supplier, operationType);
        log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result));
        Assert.notNull(result, "服务调用异常");
        Assert.isTrue(result.isSuccess(), "服务调用异常:" + result.getMsg());
        return result.getData();
    }

    public static <R> R printLatency(Supplier<R> function, String optType) {
        StopWatch stopWatch = new StopWatch(optType);
        stopWatch.start(optType);
        R r = function.get();
        stopWatch.stop();
        log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS));
        return r;
    }

}