package com.crm.rely.backend.aspect;

import com.crm.rely.backend.core.pojo.BaseTable;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * @author houn
 */
@Aspect
@Component
public class DaoMapperAop {

    private final static String ERROR_FORMAT = "出错方法：%s---错误原因:%s";
    private ThreadLocal<List<BaseTable>> threadLocal = new ThreadLocal<>();
    private Logger logger = LoggerFactory.getLogger(DaoMapperAop.class);

    @Autowired
    private Environment environment;

    @Autowired
    ControllerAop controllerAop;

    @Pointcut("execution(* com.*.*.dao.*.*.save*(..)) && !execution(* com.*.log.dao.*.*.save*(..))")
    public void saveLog() {
    }

    @Pointcut("execution(* com.*.*.dao.*.*.add*(..)) && !execution(* com.*.log.dao.*.*.add*(..))")
    public void addLog() {
    }

    @Pointcut("execution(* com.*.*.dao.*.*.update*(..)) && !execution(* com.*.log.dao.*.*.update*(..))")
    public void updateLog() {
    }

    @Pointcut("execution(* com.*.*.dao.*.*.delete*(..))  && !execution(* com.*.log.dao.*.*.delete*(..))")
    public void deleteLog() {
    }
    @Pointcut("execution(* com.*.*.dao.*.*.find*(..))")
    public void findLog() {
    }

    @Pointcut("execution(* com.*.*.dao.*.*.*(..))")
    public void exceptionLog() {
    }

    @Before(value = "saveLog()||addLog()||updateLog()")
    public void beforeSave(JoinPoint joinPoint) {
        List<BaseTable> tables = getTables(joinPoint);
        if (tables == null) {
            throw new RuntimeException("exception:dao aop beforeSave saveLog table is null");
        }
        tables.forEach(value -> {
            if (value.getId() == null) {
                threadLocal.set(tables);
            } else {
                addControllerAopThreadLocal(value, LogType.UPDATE);
            }
        });
    }

    @AfterReturning(value = "saveLog()||addLog()||updateLog()", returning = "resultObject")
    public void afterReturningSave(Object resultObject) {
        LogType logType;
        List<BaseTable> tables = threadLocal.get();
        if (tables == null) {
            logType = LogType.UPDATE;
            tables = getTables(resultObject);
        } else {
            logType = LogType.INSERT;
            tables = getTables(resultObject);
            threadLocal.remove();
        }
        if (tables == null) {
            throw new RuntimeException("exception:dao aop afterReturningSave saveLog table is null");
        }
        tables.forEach(value -> addControllerAopThreadLocal(value, logType));
    }

    @After(value = "deleteLog()")
    public void afterDelete(JoinPoint joinPoint) {
        LogType logType = LogType.DELETE;
        Object[] args = joinPoint.getArgs();
        for (int i = args.length - 1; i >= 0; i--) {
            if (args[i] instanceof Long) {
                Long id = (Long) args[i];
                String className = getT(joinPoint);
                controllerAop.addThreadLocal(className, null, logType, id);
                break;
            } else if (args[i] instanceof Iterable) {
                Iterable iterable = (Iterable) args[i];
                iterable.forEach(value -> {
                    if (value instanceof Long) {
                        Long id = (Long) value;
                        String className = getT(joinPoint);
                        controllerAop.addThreadLocal(className, null, logType, id);
                    } else {
                        BaseTable table = getBaseTable(value);
                        addControllerAopThreadLocal(table, logType);
                    }
                });
                break;
            }else if (args[i] instanceof BaseTable) {
                BaseTable iterable = (BaseTable) args[i];
                BaseTable table = getBaseTable(iterable);
                addControllerAopThreadLocal(table, logType);

            }
        }
    }

    @AfterReturning(value = "findLog()", returning = "resultObject")
    public void afterReturningFind(Object resultObject) {
        addControllerAopThreadLocal(resultObject, LogType.FIND);
    }

    private void addControllerAopThreadLocal(BaseTable table, LogType logType) {

        controllerAop.addThreadLocal(table.getClass().toString(), table.getId(), logType, table);
    }

    private void addControllerAopThreadLocal(Object resultObject, LogType logType) {

        List<BaseTable> tables = getTables(resultObject);
        if (tables != null && tables.size() > 0) {
            tables.forEach(value -> controllerAop.addThreadLocal(value.getClass().toString(), value.getId(), logType, value));
        }
    }

    private List<BaseTable> getTables(Object resultObject) {
        List<BaseTable> tables = new ArrayList<>(10);
        if ((resultObject instanceof BaseTable)) {
            BaseTable table = getBaseTable(resultObject);
            if (table != null) {
                tables.add(table);
            }
        }

        if ((resultObject instanceof Iterable)) {
            Iterable iterable = (Iterable) resultObject;
            iterable.forEach(value -> {
                BaseTable table = getBaseTable(value);
                if (table != null) {
                    tables.add(table);
                }
            });
        }
        return tables;
    }

    private BaseTable getBaseTable(Object resultObject) {
        Object object = null;
        try {
            object = resultObject.getClass().newInstance();
            if (!(object instanceof BaseTable)) {
                return null;
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        BeanUtils.copyProperties(resultObject, object);
        BaseTable table = (BaseTable) object;
        return table;
    }

    private List<BaseTable> getTables(JoinPoint joinPoint) {
        List<BaseTable> tables = new ArrayList<>();
        Object[] args = joinPoint.getArgs();
        for (int i = args.length - 1; i >= 0; i--) {
            if (args[i] instanceof BaseTable) {
                tables.add((BaseTable) args[i]);
                break;
            } else if (args[i] instanceof Iterable) {
                tables.addAll(getTables(args[i]));
                break;
            }
        }
        return tables;
    }

    private String getT(JoinPoint joinPoint) {
        Type[] types = joinPoint.getTarget().getClass().getGenericInterfaces();
        Class controllerClass = (Class) types[0];
        String className = ((ParameterizedType) controllerClass.getGenericInterfaces()[0]).getActualTypeArguments()[0].toString();
        return className;
    }

    /**
     * 异常处理方法
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "exceptionLog()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        for (String value : environment.getActiveProfiles()) {
            if (value.equals("dev")) {
                e.printStackTrace();
                break;
            }
        }

        //日志打印出错方法+原因
        logger.error(String.format(ERROR_FORMAT, joinPoint.getSignature().getName(), e.getMessage()));

        //抛出异常
        throw new RuntimeException("system error");
    }
}
