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

import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.maokai.api.util.Ref;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoBusinesses;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.dal.mapper.TodoBusinessMapper;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
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.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.service.group.GroupTemplateService;
import cn.axzo.msg.center.message.service.group.GroupTemplates;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.message.service.group.TemplateCache;
import cn.axzo.msg.center.message.service.impl.PendingMessageNewServiceImpl;
import cn.axzo.msg.center.message.service.todo.cache.NodeStatCache;
import cn.axzo.msg.center.message.service.todo.manage.TodoTerminalHelper;
import cn.axzo.msg.center.message.service.todo.mybatis.CollectSQLInterceptor;
import cn.axzo.msg.center.message.service.todo.mybatis.CollectSQLInterceptor.CollectSqlConfig;
import cn.axzo.msg.center.message.service.todo.pagequery.PageQuerySort;
import cn.axzo.msg.center.message.service.todo.queryanalyze.SimpleAnalyzer;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.TodoType;
import cn.axzo.msg.center.service.pending.request.AnalysisInfo;
import cn.axzo.msg.center.service.pending.request.PendingMessagePageRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessagePageRequest.CopiedToMeParam;
import cn.axzo.msg.center.service.pending.request.PersonTodoToBeDoneStatRequest;
import cn.axzo.msg.center.service.pending.response.AnalysisPage;
import cn.axzo.msg.center.service.pending.response.PendingMessageResponse;
import cn.axzo.msg.center.service.pending.response.PendingMessageStatisticResponseV2;
import cn.axzo.msg.center.service.pending.response.PendingMessageStatisticResponseV2.GroupStat;
import cn.axzo.msg.center.service.pending.response.PendingMessageStatisticResponseV2.Stat;
import cn.axzo.msg.center.service.pending.response.PersonTodoToBeDoneStatResponse;
import cn.axzo.msg.center.utils.DateFormatUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import static cn.axzo.msg.center.inside.notices.utils.Queries.query;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 * 尽可能的复用之前的代码, 避免完全重写
 *
 * @author yanglin
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoRangeQueryService {

    private final TodoBusinessDao todoBusinessDao;
    private final TodoBusinessMapper todoBusinessMapper;
    private final TodoDao todoDao;
    private final PendingMessageNewServiceImpl pendingMessageNewServiceImpl;
    private final TodoRespBuilder todoRespBuilder;
    private final AnalysisConfig analysisConfig;
    private final GroupTemplateService groupTemplateService;
    private final NodeStatCache nodeStatCache;
    private final TodoTerminalHelper todoTerminalHelper;
    private final AnalysisHelper analysisHelper;

    // !! page query

    public AnalysisPage<PendingMessageResponse> pageQuery(PendingMessagePageRequest request) {
        List<String> templateCodes = determineVisibleTemplateCodes(request);
        if (CollectionUtils.isEmpty(templateCodes))
            return AnalysisPage.emptyPage(request.getPage(), request.getPageSize());
        AnalysisPage<PendingRecordAdapter> pageResult;
        if (request.isQueryBusiness())
            // 我发起的
            pageResult = pageQueryBusiness(request, templateCodes);
        else
            // 可执行的/抄送我的
            pageResult = pageQueryTodo(request, templateCodes);
        List<PendingMessageResponse> messages = todoRespBuilder
                .convertAdapter2MessageResponse(pageResult.getList(), request.getTerminalType());
        if (request.determineQueryTemplateTerminals())
            todoTerminalHelper.populateTemplateTerminals(messages);
        messages.forEach(message -> message.setQueryType(request.getQueryType()));
        AnalysisPage<PendingMessageResponse> respResult = pageResult.copyAs(messages);
        respResult.addAnalysis(pageResult.getAnalysis());
        respResult.addAnalysis("request", request);
        respResult.addAnalysis("terminals", pendingMessageNewServiceImpl::getTerminalConfigInfo);
        return respResult;
    }

    /**
     * 业务视角
     */
    private AnalysisPage<PendingRecordAdapter> pageQueryBusiness(
            PendingMessagePageRequest request, List<String> templateCodes) {
        Date startingAt = DateFormatUtil.toDate(LocalDateTime.now().minusDays(90));
        LambdaQueryWrapper<TodoBusiness> query = businessQuery(request.getTitle(), true)
                .eq(TodoBusiness::getPromoterPersonId, request.getPersonId())
                .eq(TodoBusiness::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .gt(TodoBusiness::getCreateAt, startingAt)
                .in(TodoBusiness::getTemplateCode, templateCodes)
                .eq(request.getBizFinalState() != null, TodoBusiness::getBizFinalState, request.getBizFinalState());
        PageQuerySort.TODO_BUSINESS.appendSortExpr(request, query);
        return queryAndAnalysis(request, () -> {
            IPage<TodoBusiness> page = todoBusinessMapper.selectPage(request.toPage(), query);
            List<PendingRecordAdapter> messages = todoRespBuilder.buildBusinessAdapters(page.getRecords());
            return createAnalysisResult(request.getAnalysisToken(), page, messages);
        });
    }

    /**
     * 接收者视角
     */
    private AnalysisPage<PendingRecordAdapter> pageQueryTodo(
            PendingMessagePageRequest request, List<String> templateCodes) {
        // 模糊匹配发起人title
        Collection<Long> businessIds = StringUtils.isBlank(request.getTitle())
                ? Collections.emptyList()
                : todoBusinessMapper.selectList(businessQuery(request.getTitle(), false))
                .stream()
                .map(BaseEntityExt::getId)
                .collect(toSet());
        CopiedToMeParam copiedToMeParam = request.getCopiedToMeParam();
        Ref<List<Long>> ouCollector = Ref.create();
        OuInfo ouInfo = new OuInfo(request.getOuId(), request.getAppTerminalType());
        LambdaQueryWrapper<Todo> query = todoQuery(query(Todo.class), ouInfo, ouCollector)
                // 查询的待办类型: COPIED_TO_ME, EXECUTABLE
                .eq(Todo::getType, request.determineToDoType())
                .eq(Todo::getExecutorPersonId, request.getPersonId())
                //判断是否支持批量过滤
                .eq(request.getSupportBatchProcess() != null, Todo::getSupportBatchProcess, request.getSupportBatchProcess())
                // like search
                .and(StringUtils.isNotBlank(request.getTitle()), nested -> nested
                        .likeRight(Todo::getTitle, request.getTitle())
                        .or()
                        .in(!businessIds.isEmpty(), Todo::getTodoBusinessId, businessIds))
                // copied to me
                .and(request.determineToDoType() == TodoType.COPIED_TO_ME, nested -> nested
                        .eq(copiedToMeParam != null && copiedToMeParam.determineQueryUnreadOnly(),
                                Todo::getState, PendingMessageStateEnum.CREATED)
                        .last(nested.isEmptyOfWhere(), "1 = 1"))
                // executable
                .and(request.determineToDoType() == TodoType.EXECUTABLE, nested -> nested
                        .eq(request.getMsgState() != null, Todo::getState, request.getMsgState())
                        .eq(request.getWorkspaceId() != null, Todo::getOrgId, request.getWorkspaceId())
                        .last(nested.isEmptyOfWhere(), "1 = 1"))
                // 这个条件放在最后, 因为templateCodes一般比较多
                // 如果放到前面, 格式化后会不方便查看其它的查询条件
                // 用于查询的模版会通过一个单独的字段打印出来, 用于排查问题
                .in(Todo::getTemplateCode, templateCodes);
        PageQuerySort.TODO.appendSortExpr(request, query);
        return queryAndAnalysis(request, () -> {
            IPage<Todo> page = todoDao.page(request.toPage(), query);
            List<PendingRecordAdapter> messages = todoRespBuilder.buildTodoAdapters(page.getRecords());
            return createAnalysisResult(request.getAnalysisToken(), page, messages);
        });
    }

    private AnalysisPage<PendingRecordAdapter> queryAndAnalysis(
            PendingMessagePageRequest request, Supplier<AnalysisPage<PendingRecordAdapter>> pageQuery) {
        AnalysisInfo analysis = request.getA();
        Object analyzeItem = analysisHelper.getAnalyzeBusiness(analysis);
        if (analyzeItem == null)
            analyzeItem = analysisHelper.getAnalyzeTodo(request, analysis);
        if (analyzeItem != null || analysis.isCollectSql())
            CollectSQLInterceptor.enableCollectSQL(new CollectSqlConfig(true));
        try {
            Ref<String> execSQL = Ref.create();
            AnalysisPage<PendingRecordAdapter> result = pageQuery.get();
            execSQL.set(CollectSQLInterceptor.getSQL().orElse(null));
            // !! analysis
            result.addAnalysis("query", execSQL::get);
            Object finalAnalyzeItem = analyzeItem;
            result.addAnalysis("eval", () -> {
                String sql = execSQL.get();
                if (sql == null || finalAnalyzeItem == null) return null;
                SimpleAnalyzer simpleAnalyzer = new SimpleAnalyzer(sql);
                HashMap<String, Object> analyzeResult = new HashMap<>();
                analyzeResult.put("evalItem", finalAnalyzeItem);
                analyzeResult.put("evalResult", simpleAnalyzer.analyze(finalAnalyzeItem));
                return analyzeResult;
            });
            return result;
        } finally {
            CollectSQLInterceptor.disableCollectSQL();
        }
    }

    @NotNull
    private <T> AnalysisPage<T> createAnalysisResult(
            String analysisToken, IPage<?> pageResult, List<T> messages) {
        AnalysisPage<T> analysisResult = new AnalysisPage<>(
                pageResult.getCurrent(), pageResult.getSize(), pageResult.getTotal(), messages);
        analysisResult.setEnableAnalysis(analysisConfig.determineAnalysisEnable(analysisToken));
        return analysisResult;
    }

    /**
     * 根据分类获取可见的模版编码
     */
    public List<String> determineVisibleTemplateCodes(PendingMessagePageRequest request) {
        TemplateCache.enableCache();
        try {
            if (!request.determineGroupNodeCodes().isEmpty())
                return groupTemplateService.collectTemplateCodes(request.determineGroupNodeCodes());
            List<ValueNode<NodeWrapper>> nodes;
            // 如果没有传端就查询所有端的待办, 用于待办转交
            if (request.getAppTerminalType() == null)
                nodes = groupTemplateService.getTodoGroups(AppTerminalTypeEnum.allTerminals());
            else
                // 获取可见的模版时, 不用定位到叶子节点
                nodes = groupTemplateService.getTodoGroups(request.getAppTerminalType());
            return groupTemplateService.collectTemplateCodes(nodes);
        } finally {
            TemplateCache.disableCache();
        }
    }

    // !! stat

    public PersonTodoToBeDoneStatResponse personTodoToBeDoneStat(PersonTodoToBeDoneStatRequest request) {
        List<Todo> allTodos = todoDao.getToBeDone(request.getPersonId(), request.getOuId());
        List<Todo> todos = todoTerminalHelper.maybeFilterByTerminal(allTodos, request.getTerminals());
        TodoBusinesses businesses = todoBusinessDao.getBusinesses(todos);
        PersonTodoToBeDoneStatResponse resp = new PersonTodoToBeDoneStatResponse();
        for (BizCategoryEnum category : BizCategoryEnum.values()) {
            List<Todo> categoryTodos = businesses.getTodoByCategory(category);
            resp.addStat(new PersonTodoToBeDoneStatResponse.Stat(category, categoryTodos.size()));
        }
        // 有些待办模版可能没有设置分类
        if (request.isFilterByAllTerminals())
            resp.setDebugCount(allTodos.size() - todos.size());
        return resp;
    }

    public PendingMessageStatisticResponseV2 countStatGrouped(MessageGroupNodeStatisticParam request) {
        return nodeStatCache.getCacheResponseOrReload(request, () -> countStatGroupedImpl(request));
    }

    private PendingMessageStatisticResponseV2 countStatGroupedImpl(MessageGroupNodeStatisticParam request) {
        TemplateCache.enableCache();
        try {
            List<ValueNode<NodeWrapper>> nodes = determineStatNodes(request);
            LeafNodeStats nodeStats = countStatByNodes(request, nodes);
            List<GroupStat> stats = new ArrayList<>();
            for (ValueNode<NodeWrapper> valueNode : nodes) {
                GroupStat groupStat = new GroupStat();
                stats.add(groupStat);
                MessageGroupNode node = valueNode.getValue().unwrap();
                groupStat.setNodeId(node.getId());
                groupStat.setGroupCode(node.getCode());
                groupStat.setGroupName(node.getName());
                groupStat.setGroupIcon(node.getIcon());
                Map<TodoType, Integer> type2Count = nodeStats.collectStat(valueNode);
                groupStat.setStat(new Stat(
                        type2Count.get(TodoType.EXECUTABLE),
                        type2Count.get(TodoType.COPIED_TO_ME)));
            }
            PendingMessageStatisticResponseV2 resp = new PendingMessageStatisticResponseV2();
            resp.addGroupStats(stats);
            // 一个端可能会有重复分类
            resp.setTotalStat();
            // 为了性能, 不做去重的统计了
            resp.setUniqueTotalStat(new Stat(-1, -1));
            return resp;
        } catch (Exception e) {
            log.warn("分类统计异常, request={}", request, e);
            throw new ServiceException("分类统计, 请稍后重试");
        } finally {
            TemplateCache.disableCache();
        }
    }

    /**
     * app首页
     */
    public Integer countStat(MessageGroupNodeStatisticParam request) {
        PendingMessageStatisticResponseV2 nodeStat = nodeStatCache
                .getCacheResponseOrReload(request, () -> countStatGroupedImpl(request));
        Stat totalStat = nodeStat.getTotalStat();
        return totalStat == null ? 0 : totalStat.getPendingCount();
    }

    private LeafNodeStats countStatByNodes(MessageGroupNodeStatisticParam request, List<ValueNode<NodeWrapper>> nodes) {
        List<String> templateCodes = groupTemplateService.collectTemplateCodes(nodes);
        GroupTemplates groupTemplates = groupTemplateService.collectTemplates(nodes);
        if (CollectionUtils.isEmpty(templateCodes))
            return new LeafNodeStats(Collections.emptyList(), groupTemplates);
        LambdaQueryWrapper<Todo> query = new QueryWrapper<Todo>()
                // 1. group by bellow
                .select("COUNT(*) AS count, template_code, type")
                .lambda();
        OuInfo ouInfo = new OuInfo(request.getOuId(), request.getTerminalType());
        //noinspection unchecked
        List<Todo> stats = todoDao.list(todoQuery(query, ouInfo, null)
                .in(Todo::getTemplateCode, templateCodes)
                .eq(Todo::getExecutorPersonId, request.getPersonId())
                .in(CollectionUtils.isNotEmpty(request.getWorkspaceIds()), Todo::getOrgId, request.getWorkspaceIds())
                .in(Todo::getType, TodoType.EXECUTABLE, TodoType.COPIED_TO_ME)
                .in(Todo::getState, PendingMessageStateEnum.HAS_BEEN_SENT, PendingMessageStateEnum.CREATED)
                // 2. group by here
                .groupBy(Todo::getTemplateCode, Todo::getType));
        return new LeafNodeStats(stats, groupTemplates);
    }

    public List<ValueNode<NodeWrapper>> determineStatNodes(MessageGroupNodeStatisticParam request) {
        Collection<String> nodeCodes = request.getGroupNodeCodes();
        if (nodeCodes == null) {
            nodeCodes = Collections.emptyList();
        }
        nodeCodes = nodeCodes.stream()
                .filter(StringUtils::isNotBlank)
                .collect(toList());
        List<ValueNode<NodeWrapper>> nodes = CollectionUtils.isNotEmpty(nodeCodes)
                ? groupTemplateService.getGroupRoot().getNodes(nodeCodes)
                : groupTemplateService.getTodoGroups(request.getTerminalType());
        // 只返回给前端叶子节点的信息, 前端不需要层级信息
        return groupTemplateService.collectLeafNodes(nodes);
    }

    // helper

    private LambdaQueryWrapper<TodoBusiness> businessQuery(String title, boolean matchBusinessTitle) {
        return query(TodoBusiness.class)
                .eq(TodoBusiness::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                // like search
                .and(StringUtils.isNotBlank(title), nested -> nested
                        .likeRight(TodoBusiness::getPromoterName, title)
                        .or()
                        .likeRight(matchBusinessTitle, TodoBusiness::getTitle, title));
    }

    private LambdaQueryWrapper<Todo> todoQuery(LambdaQueryWrapper<Todo> query,
                                               OuInfo info, @Nullable Ref<List<Long>> ouCollector) {
        List<Long> ouIds = Collections.emptyList();
        // 工人端不通过ou做查询
        if (info.terminalType != AppTerminalTypeEnum.C_WORKER_APP) {
            ouIds = pendingMessageNewServiceImpl.determineOuIds(info.ouId);
            if (ouCollector != null)
                ouCollector.set(ouIds);
        }
        // 1. 查询 ouId = 0 的数据
        // 2. 查询ouId下面所有的平台班组id当成ouId
        return query
                .eq(Todo::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .in(CollectionUtils.isNotEmpty(ouIds), Todo::getOuId, ouIds);
    }

    @RequiredArgsConstructor
    private static class OuInfo {
        final Long ouId;
        final AppTerminalTypeEnum terminalType;
    }

}