package com.brilliance.mda.runtime.mda.impl;

import com.brilliance.mda.runtime.annotation.Check;
import com.brilliance.mda.runtime.annotation.Default;
import com.brilliance.mda.runtime.annotation.Rule;
import com.brilliance.mda.runtime.mda.*;
import com.brilliance.mda.runtime.mda.driver.DCR;
import com.brilliance.mda.runtime.mda.driver.MdaDriver;
import com.brilliance.mda.runtime.mda.driver.MdaEnv;
import com.brilliance.mda.runtime.mda.driver.compile.EmitterManager;
import com.brilliance.mda.runtime.mda.driver.compile.component.RuleItemExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractRuleEmitter implements IRuleEmitter {
    protected Logger log = LoggerFactory.getLogger(this.getClass());

    private static final long SLOW_EXECUTION_TIME = 100;
    protected Map<String, String> eqMap = new ConcurrentHashMap<>();
    protected Map<String, String> argMap = new HashMap<>();
    protected static ThreadLocal<List<String>> slowExecutionListThreadLocal = new ThreadLocal<>();
    private Map<String, Method> ruleMethodMap = new LinkedHashMap<>();
    private Map<String, Method> checkMethodMap = new LinkedHashMap<>();
    private Map<String, Method> defaultMethodMap = new LinkedHashMap<>();

    private Map<String, Integer> defaultQueue = new LinkedHashMap<>();

    public void scanMethod() {
        Method[] methods = this.getClass().getMethods();
        for (Method m : methods) {
            Rule rule = m.getAnnotation(Rule.class);
            if (rule != null) {
                m.setAccessible(true);
                ruleMethodMap.put(rule.target(), m);
            }
            Check check = m.getAnnotation(Check.class);
            if (check != null) {
                m.setAccessible(true);
                checkMethodMap.put(check.target(), m);
            }
            Default defaultRule = m.getAnnotation(Default.class);
            if (defaultRule != null) {
                m.setAccessible(true);
                defaultMethodMap.put(defaultRule.target(), m);
            }
        }
        defaultMethodMap.forEach((path, method) -> {
            if (!method.getName().startsWith("defaultList")) {
                method.setAccessible(true);
                defaultQueue.put(path, 0);
            }
        });
    }

    /**
     * 提供子类此map，用于子类计算和获取
     *
     * @return
     */
    public Map<String, Integer> getDefaultQueue() {
        return this.defaultQueue;
    }

    public Map<String, Object> getDefaultRuleQue() {
        //根据default依赖关系进行依赖排序。
        Map<String, Object> queue = new LinkedHashMap<>();
        queue.putAll(this.getDefaultQueue());
        //收集moduleList里面的交易
        getAllModuleList().forEach(moduleList -> {
            IRuleEmitter ruleEmitter = DCR.getModuleListEmitter(moduleList.getDataClass());
            if (ruleEmitter == null) {
                return;
            }
            Map<String, Object> subQueue = new LinkedHashMap<>();
            for (int i = 1; i <= moduleList.size(); i++) {
                Map<String, Object> subSubQueueTemp = ruleEmitter.getDefaultRuleQue();
                Map<String, Object> subSubQueue = new LinkedHashMap<>();
                subSubQueue.putAll(subSubQueueTemp);
                subQueue.put(String.valueOf(i), subSubQueue);
            }

            String moduleListPath = moduleList.getPath().toLowerCase().substring(1).replaceAll("\\\\", "\\.");
            queue.put(moduleListPath, subQueue);
        });
        return queue;
    }

    public void executeDefaultRuleQue() {
        Stack<String> keyStack = new Stack<>();
        long begMillis = System.currentTimeMillis();
        Map<String, Object> queue = getDefaultRuleQue();
        log.debug("#####获取默认Queue耗时{}", System.currentTimeMillis() - begMillis);
        begMillis = System.currentTimeMillis();
        DCR.executeMergedQueue(getRoot(), this, queue, queue, keyStack);
        //Default rule执行中会产生修改，导致重新修改，因此继续执行default
        long begMillis2 = System.currentTimeMillis();
        log.debug("#####完整Default执行耗时{}", begMillis2 - begMillis);
        if (!(this instanceof AbstractScopeEmitter)) {
            DCR.executeQueue(true, this, 0);
            log.debug("#####补充执行耗时{}", System.currentTimeMillis() - begMillis2);
        }
    }


    protected boolean checkPathMatch(String path, String target) {
        int targetLen = target.length();
        return targetLen == 0 ||
                (path.startsWith(target) && (path.length() == targetLen || path.charAt(targetLen) == '.'));
    }

    public String getRealPath(String path) {
        if (path == null) return "";
        String realPath = eqMap.get(path);
        if (realPath != null)
            return realPath;
        //计算path
        IBaseObject baseObject = MdaDriver.getBaseObject(MdaEnv.getContext().getRoot(), path);
        if (baseObject != null) {
            //如果得到的是ModuleList，但是其后续路径还有，这种情况应该要
            realPath = baseObject.getPath();
        } else {
            int lastIndex = path.lastIndexOf('.');
            if (lastIndex > 0) {
                //取父级模型，因为按钮在实际中不存在
                baseObject = MdaDriver.getBaseObject(MdaEnv.getContext().getRoot(), path.substring(0, lastIndex));
                if (baseObject != null && baseObject instanceof IModule) {
                    realPath = ((IModule) baseObject).getFieldPath(path.substring(lastIndex + 1));
                    if (realPath == null || realPath.length() == 0) {
                        realPath = baseObject.getPath() + "." + path.substring(lastIndex + 1);
                    }
                }
            } else {
                realPath = path;
            }
        }
        if (realPath == null)
            return path;
        realPath = realPath.toLowerCase().replaceAll("\\\\", ".");
        if (realPath.charAt(0) == '.')
            realPath = realPath.substring(1);
        eqMap.put(path, realPath);
        return realPath;
    }

    //计算真实路径
    private String calcAbsolutePath(String path) {
        out:
        while (true) {
            for (Map.Entry<String, String> entry : argMap.entrySet()) {
                String apath = entry.getKey(); //原始路径
                if (path.startsWith(apath) &&
                        (path.length() == apath.length() || path.charAt(apath.length()) == '.')) {
                    path = entry.getValue() + path.substring(apath.length()); //替换了一次，
                    continue out; //重新校验替换
                }
            }
            //替换完毕
            return path;
        }
    }

    public boolean executeRule(String target, int order) {
        target = getRealPath(target);
        if (target.charAt(0) == '.' || target.charAt(0) == '\\')
            target = target.substring(1);
        Method m = this.ruleMethodMap.get(target);
        if (m != null) {
            return invokeMethod(m, target, order);
        }
        return false;
    }

    public boolean executeDefault(String target, int order) {
        target = getRealPath(target);
        Method m = this.defaultMethodMap.get(target);
        if (m != null) {
            boolean isDcrEnable = DCR.isEnable();
//            if(isDcrEnable)
//                DCR.enNotify(false);
            boolean result;
            try {
                result = invokeMethod(m, target, order);
            } catch (Exception ex) {
                DCR.emptyDefaultQueue();
                throw new RuleExecuteException("Rule执行异常", ex);
            } finally {
                //DCR.enNotify(isDcrEnable);
            }
            return result;
        }
        return false;
    }

    public boolean executeDefaultAfterInit() {
        //为了保证准确性，根据TD规律，重复运行3次
        for (int i = 0; i < 1; i++) {
            executeDefaultRuleQue();
        }
        return true;
    }

    public boolean executeDefaultAll() {
        for (Map.Entry<String, Method> entry : this.defaultMethodMap.entrySet()) {
            invokeMethod(entry.getValue(), entry.getKey(), -1);
        }
        return true;
    }

    // 目前只执行moduleList下模型的check
    public boolean executeCheckAll() {
        for (IModuleList moduleList : this.getAllModuleList()) {
            moduleList.executeCheckAll();
        }
        MdaEnv.removeModuleListCheckErrorItem();
        return true;
    }


    public boolean executeCheck(String target, int order) {
        target = getRealPath(target);
        for (Map.Entry<String, Method> entry : checkMethodMap.entrySet()) {
            if (!checkPathMatch(entry.getKey(), target))
                continue;
            boolean result = invokeMethod(entry.getValue(), entry.getKey(), order);
            if (!result)
                log.warn(String.format("execute @Check[%s,%d] return false", entry.getKey(), order));
        }
        return true;
    }

    private boolean invokeMethod(Method m, String target, int order) {
        IContext ctx = MdaEnv.getContext();
        try {
            ctx.pushEventPath(target);
            boolean result = (Boolean) m.invoke(this, order);
            return result;
        } catch (IllegalAccessException e) {
            log.error("invoke method error", e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof RuleExitException)
                throw (RuleExitException) e.getCause();
            else if (e.getCause() instanceof RulePromptException) {
                throw (RulePromptException) e.getCause();
            } else if (e.getCause() instanceof RuleExecuteException) {
                throw (RuleExecuteException) e.getCause();
            } else
                throw new RuntimeException(e);
        } finally {
            ctx.popEventPath();
        }
        return false;
    }

    @Override
    public boolean executeRule(String target) {
        try {
            return executeRule(target, -1);
        } catch (RuleExitException e) {
            return false;
        } catch (RulePromptException e) {
            return true;
        } catch (RuleExecuteException e) {
            throw e;
        } catch (Exception ex) {
            DCR.emptyDefaultQueue();
            throw new RuleExecuteException("Rule执行异常", ex);
        }
//        finally {
//            AbstractDaoSession daoSession = MdaEnv.getBean(AbstractDaoSession.class);
//            if(daoSession.isOpenTrans())
//            {
//                log.warn("存在未提交的事务，回滚");
//                daoSession.dbRollback();
//            }
//        }
    }

    @Override
    public boolean executeCheck(String target) {
        try {
            return executeCheck(target, -1);
        } catch (RuleExitException e) {
            return false;
        } catch (Exception ex) {
            DCR.emptyDefaultQueue();
            throw new RuleExecuteException("Check执行异常", ex);
        }
//        finally {
//            AbstractDaoSession daoSession = MdaEnv.getBean(AbstractDaoSession.class);
//            if(daoSession.isOpenTrans())
//            {
//                log.warn("存在未提交的事务，回滚");
//                daoSession.dbRollback();
//            }
//        }
    }

    @Override
    public boolean executeDefault(String target) {
        try {
            return executeDefault(target, -1);
        } catch (RuleExitException e) {
            return false;
        } catch (Exception ex) {
            DCR.emptyDefaultQueue();
            throw new RuleExecuteException("Default执行异常", ex);
        }
    }

    @Override
    public boolean executeInit() {
        try {
            Boolean result = executeInit(-1);
            return result;
        } catch (RuleExitException e) {
            e.printStackTrace();
            return false;
        } catch (Exception ex) {
            DCR.emptyDefaultQueue();
            List<String> rows = MdaEnv.getAuthInfo().getSysStream().getRows();
            if (rows.contains("SPT")) {
                Iterator<String> iterator = rows.iterator();
                boolean flag = false;
                while (iterator.hasNext()) {
                    String next = iterator.next();
                    if (flag) {
                        iterator.remove();
                        break;
                    }
                    if (next.equals("SPT")) {
                        iterator.remove();
                        flag = true;
                    }
                }
            }
            throw new RuleExecuteException("Init执行异常", ex);
        }
    }

    public void execInitItem(int order, String path, Ruleable<Boolean> ruleHolder) {
        boolean ignore = false;
        if (ruleHolder instanceof RuleItemExecutor) {
            RuleItemExecutor executor = (RuleItemExecutor) ruleHolder;
            IModule root = executor.getRoot();
            String methodName = executor.getRuleItem().getMethodName();
            ignore = EmitterManager.isIgnoreInitMethod(root.getClass().getSimpleName(), EmitterManager.getMethodNameByRule("INIT", path, methodName, order));
        }
        if (ignore) {
            log.debug("{} ignore init [{}] [{}] ", MdaEnv.getContext().getTransName(), order, path);
        } else {
            StopWatch stopWatch = new StopWatch("execInitItem");
            stopWatch.start();
            //log.debug("{} init [{}] [{}] ", MdaEnv.getContext().getTransName(),order,path);
            MdaEnv.getContext().setConcurrentCallStack(path);
            MdaEnv.getContext().setErrorCode(Constants.NO_ERROR);
            if (!ruleHolder.exec()) {
                stopWatch.stop();
                addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execInitItem", path, order);
                throw new RuleExitException(String.format("%s init %d %s exit", MdaEnv.getContext().getTransName(), order, path));
            }
            stopWatch.stop();
            addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execInitItem", path, order);
        }
    }

    public void execRuleItem(int order, String path, Ruleable<Boolean> ruleHolder) {
        boolean ignore = false;
        String eventPath = MdaEnv.getContext().getEventPath();
        if (ruleHolder instanceof RuleItemExecutor) {
            RuleItemExecutor executor = (RuleItemExecutor) ruleHolder;
            IModule root = executor.getRoot();
            String methodName = executor.getRuleItem().getMethodName();
            ignore = EmitterManager.isIgnoreRuleMethod(root.getClass().getSimpleName(), EmitterManager.getMethodNameByRule(eventPath, path, methodName, order));
        }
        if (ignore) {
            log.debug("{} ignore rule [{}] [{}] ", MdaEnv.getContext().getTransName(), order, path);
        } else {
            StopWatch stopWatch = new StopWatch("execRuleItem");
            stopWatch.start();
            //log.debug("{} rule [{}] [{}]",MdaEnv.getContext().getTransName(),order,eventPath);
            MdaEnv.getContext().setErrorCode(Constants.NO_ERROR);
            MdaEnv.getContext().setConcurrentCallStack(path);
            int oldLength = MdaEnv.getContext().getFieldErrors().size();
            boolean b = ruleHolder.exec();
            if (!b) {
                stopWatch.stop();
                addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execRuleItem", path, order);
                throw new RuleExitException(String.format("%s rule %d %s exit", MdaEnv.getContext().getTransName(), order, eventPath));
            }
            if (MdaEnv.getContext().getFieldErrors().size() > oldLength) {
                log.warn("{} rule [{}] [{}] set fieldError", MdaEnv.getContext().getTransName(), order, eventPath);
            }
            stopWatch.stop();
            addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execRuleItem", path, order);
        }
        //log.debug("rule [{}] [{}] finished",order,eventPath);
    }

    public void execCheckItem(int order, String path, Ruleable<Boolean> ruleHolder) {
        boolean ignore = false;
        String eventPath = MdaEnv.getContext().getEventPath();
        if (ruleHolder instanceof RuleItemExecutor) {
            RuleItemExecutor executor = (RuleItemExecutor) ruleHolder;
            IModule root = executor.getRoot();
            String methodName = executor.getRuleItem().getMethodName();
            ignore = EmitterManager.isIgnoreCheckMethod(root.getClass().getSimpleName(), EmitterManager.getMethodNameByRule(eventPath, path, methodName, order));
        }
        if (ignore) {
            log.debug("{} ignore check [{}] [{}] ", MdaEnv.getContext().getTransName(), order, path);
        } else {
            StopWatch stopWatch = new StopWatch("execCheckItem");
            stopWatch.start();
            //log.debug("{} check [{}] [{}]",MdaEnv.getContext().getTransName(),order,eventPath);
            MdaEnv.getContext().setConcurrentCallStack(path);
            MdaEnv.getContext().setErrorCode(Constants.NO_ERROR);
            int oldLength = MdaEnv.getContext().getFieldErrors().size();
            if (!ruleHolder.exec()) {
                stopWatch.stop();
                addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execCheckItem", path, order);
                throw new RuleExitException(String.format("check [%d] [%s] exit", order, eventPath));
            }
            if (MdaEnv.getContext().getFieldErrors().size() > oldLength) {
                log.warn(String.format("%s check [%d] [%s] set fieldError", MdaEnv.getContext().getTransName(), order, eventPath));
            }
            stopWatch.stop();
            addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execCheckItem", path, order);
            //log.debug("check [{}] [{}] finished",order,eventPath);
        }
    }

    public void execDefaultItem(int order, String path, Ruleable<Boolean> ruleHolder) {
        boolean ignore = false;
        String eventPath = MdaEnv.getContext().getEventPath();
        if (ruleHolder instanceof RuleItemExecutor) {
            RuleItemExecutor executor = (RuleItemExecutor) ruleHolder;
            IModule root = executor.getRoot();
            String methodName = executor.getRuleItem().getMethodName();
            ignore = EmitterManager.isIgnoreDefaultMethod(root.getClass().getSimpleName(), EmitterManager.getMethodNameByRule(eventPath, path, methodName, order));
        }
        if (ignore) {
            log.debug("{} ignore default [{}] [{}] ", MdaEnv.getContext().getTransName(), order, path);
        } else {
            StopWatch stopWatch = new StopWatch("execDefaultItem");
            stopWatch.start();
            //log.debug("{} default [{}] [{}]",MdaEnv.getContext().getTransName(),order,eventPath);
            MdaEnv.getContext().setConcurrentCallStack(path);
            MdaEnv.getContext().setErrorCode(Constants.NO_ERROR);
            if (!ruleHolder.exec()) {
                stopWatch.stop();
                addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execDefaultItem", path, order);
                throw new RuleExitException(String.format("%s default [%d] [%s] exit", MdaEnv.getContext().getTransName(), order, eventPath));
            }
            stopWatch.stop();
            addSlowExecutionLog(stopWatch.getLastTaskTimeMillis(), "execDefaultItem", path, order);
            //log.debug("default [{}] [{}] finished",order,eventPath);
        }
    }

    public List<IModuleList> getAllModuleList() {
        return new ArrayList<>();
    }

    private void addSlowExecutionLog(long executeTime, String executeType, String path, int order) {
        if (executeTime >= SLOW_EXECUTION_TIME) {
            if (slowExecutionListThreadLocal.get() == null) {
                slowExecutionListThreadLocal.set(new ArrayList<>());
            }
            slowExecutionListThreadLocal.get().add(String.format("%s %s %s 消耗时间%dms", executeType, path, order, executeTime));
        }
    }

    protected void printSlowExecutionLog() {
        if (slowExecutionListThreadLocal.get() != null && slowExecutionListThreadLocal.get().size() > 0) {
            StringBuilder logBuilder = new StringBuilder();
            logBuilder.append(Constants.CRLF);
            logBuilder.append("================================================").append(Constants.CRLF);
            logBuilder.append(String.format("执行Rule 超过%dms集合:", SLOW_EXECUTION_TIME)).append(Constants.CRLF);
            for (String slowExecutionLog : slowExecutionListThreadLocal.get()) {
                logBuilder.append(slowExecutionLog).append(Constants.CRLF);
            }
            logBuilder.append("================================================");
            log.warn(logBuilder.toString());
            slowExecutionListThreadLocal.remove();
        }
    }
}
