package com.brframework.commondb.core;

import com.brframework.common.utils.DateTimeUtils;
import com.brframework.commondb.annotation.QuerySort;
import com.brframework.commondb.annotation.param.ParamQuery;
import com.brframework.commondb.annotation.param.QueryExpression;
import com.brframework.commonweb.exception.HandleException;
import com.brframework.commonweb.json.PageParam;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.EntityPathBase;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

import java.io.Serializable;
import java.lang.reflect.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;

/**
 * 通用Service的抽象实现
 *
 * @author xu
 * @date 2019/1/9 10:35
 */
public abstract class AbstractEntityService<Entity, Id extends Serializable, Param> implements EntityService<Entity, Id, Param> {
    /**
     * 默认情况分页的排序字段
     */
    public static final String DEFAULT_PAGE_SORT_FIELD = "createDate";

    public abstract CommonRepository<Id, Entity> getRepository();

    /**
     * 默认情况下的分页排序
     *
     * @return
     */
    protected Sort defaultPageSort() {
        return new Sort(Sort.Direction.DESC, DEFAULT_PAGE_SORT_FIELD);
    }

    @Override
    public Page<Entity> page(PageParam page, Param param) {
        Sort sort = getPageSort(param);
        PageRequest pageRequest = PageRequest.of(page.getPageIndex(), page.getPageSize(), sort);

        if (param == null) {
            return getRepository().findAll(pageRequest);
        }

        return getRepository().findAll(genExpression(param), pageRequest);

    }

    @Override
    public Integer count(Param param) {
        Long count = getRepository().count(genExpression(param));
        if (null == count) {
            return 0;
        }
        return count.intValue();
    }

    @Override
    public Entity accessObject(Id id) {
        //需要触发findById的代理
        Optional<Entity> e = findById(id);
        if (!e.isPresent()) {
            throw new HandleException("查不到" + id + "的相关数据");
        }
        return e.get();
    }

    @Override
    public Optional<Entity> findById(Id id) {
        return getRepository().findById(id);
    }

    @Override
    public void removeById(Id id) {
        getRepository().deleteById(id);
    }

    @Override
    public Entity save(Entity entity) {
        return getRepository().save(entity);
    }

    /**
     * 生成参数表达式
     *
     * @param param
     * @return
     */
    private BooleanExpression genExpression(Param param) {
        try {

            ExQuery.BooleanExpressionBuilder builder = ExQuery.booleanExpressionBuilder();

            //通过反射获取需要使用到的QueryDsl参数
            Type genType = this.getClass().getGenericSuperclass();
            Class entityType = null;
            if (ParameterizedType.class.isInstance(genType)) {
                //无法获取到User类，或者可能获取到错误的类型，如果有同名且不带包名的泛型存在
                ParameterizedType parameterizedType = (ParameterizedType) genType;
                entityType = (Class) parameterizedType.getActualTypeArguments()[0];
            }

            //获取QueryDsl生成的数据库操作类
            String queryDslClassName = entityType.getName().replace(entityType.getSimpleName(), "Q" + entityType.getSimpleName());
            Class queryDslCLass = Class.forName(queryDslClassName);
            EntityPathBase queryDsl = (EntityPathBase) queryDslCLass
                    .getDeclaredField(entityType.getSimpleName().substring(0, 1).toLowerCase() +
                            entityType.getSimpleName().substring(1)).get(null);


            Field[] declaredFields = param.getClass().getDeclaredFields();
            Map<String, Object> betweenMap = Maps.newHashMap();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                ParamQuery paramQuery = declaredField.getAnnotation(ParamQuery.class);
                if (paramQuery == null) {
                    continue;
                }
                QueryExpression expression = paramQuery.expression();
                String betweenKey = paramQuery.betweenKey();

                switch (expression) {
                    case betweenDateStart:
                        //如果是开始结束时间类型
                        if (Strings.isNullOrEmpty(betweenKey)) {
                            throw new HandleException("请输入正确的ParamQuery key");
                        }
                        if (betweenMap.containsKey(betweenKey)) {
                            //已经存在结束时间
                            Object start = declaredField.get(param);
                            Object end = betweenMap.get(betweenKey);

                            DateTimePath dateTimePath = (DateTimePath) queryDslCLass.getDeclaredField(betweenKey).get(queryDsl);
                            builder.and(ExQuery.betweenDate(dateTimePath, (Comparable) start, (Comparable) end));
                        } else {
                            betweenMap.put(betweenKey, declaredField.get(param));
                            DateTimePath dateTimePath = (DateTimePath) queryDslCLass.getDeclaredField(betweenKey).get(queryDsl);
                            builder.and(null != declaredField.get(param) ? ExQuery.betweenDate(dateTimePath, LocalDateTime.parse(declaredField.get(param).toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), null) : null);
                        }

                        break;
                    case betweenDateEnd:
                        //如果是开始结束时间类型
                        if (Strings.isNullOrEmpty(betweenKey)) {
                            throw new HandleException("请输入正确的ParamQuery key");
                        }

                        if (betweenMap.containsKey(betweenKey)) {
                            //已经存在开始时间
                            Object start = betweenMap.get(betweenKey);
                            Object end = declaredField.get(param);
                            DateTimePath dateTimePath = (DateTimePath) queryDslCLass.getDeclaredField(betweenKey).get(queryDsl);
                            builder.and(ExQuery.betweenDate(dateTimePath, (Comparable) start, (Comparable) end));
                        } else {
                            betweenMap.put(betweenKey, declaredField.get(param));
                            DateTimePath dateTimePath = (DateTimePath) queryDslCLass.getDeclaredField(betweenKey).get(queryDsl);
                            builder.and(null != declaredField.get(param) ? ExQuery.betweenDate(dateTimePath, null, LocalDateTime.parse(declaredField.get(param).toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) : null).build();
                        }

                        break;
                    case betweenDate:
                        Object dateFiled = declaredField.get(param);
                        if (dateFiled != null) {
                            String[] dateBetween = dateFiled.toString().split(",");
                            LocalDateTime startDate = DateTimeUtils.stringToDateTime(dateBetween[0]);
                            LocalDateTime endDate = DateTimeUtils.stringToDateTime(dateBetween[1]);
                            DateTimePath dateTimePath = (DateTimePath) queryDslCLass.getDeclaredField(betweenKey).get(queryDsl);
                            builder.and(ExQuery.betweenDate(dateTimePath, (Comparable) startDate, (Comparable) endDate));
                        }

                        break;
                    default:
                        //通过反射调用对应的ExQuery方法
                        builder.and((BooleanExpression)
                                getExQueryMethod(expression.getMethod()).invoke(null,
                                        queryDslCLass.getDeclaredField(declaredField.getName()).get(queryDsl),
                                        declaredField.get(param))
                        );

                }
            }
            return builder.build();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        throw new HandleException("分页查询失败");
    }

    /**
     * 获取分页排序
     *
     * @param param
     * @return
     */
    private Sort getPageSort(Param param) {
        if (param == null) {
            return defaultPageSort();
        }
        QuerySort sort = param.getClass().getAnnotation(QuerySort.class);
        if (sort == null) {
            return defaultPageSort();
        }
        Sort.Direction direction = sort.direction();
        String[] properties = sort.properties();
        return new Sort(direction, properties);
    }

    private static Map<String, Method> exQueryMethod = null;

    /**
     * 反射获取ExQuery所有方法
     *
     * @param methodName
     * @return
     * @see ExQuery
     */
    private Method getExQueryMethod(String methodName) {
        if (exQueryMethod == null) {
            exQueryMethod = Maps.newHashMap();
            for (Method declaredMethod : ExQuery.class.getDeclaredMethods()) {
                exQueryMethod.put(declaredMethod.getName(), declaredMethod);
            }
        }

        Method method = exQueryMethod.get(methodName);
        if (method == null) throw new HandleException("不存在方法" + methodName);

        return method;
    }


    /**
     * 获取 目标对象
     * 记得设置exposeProxy=true
     *
     * @return
     * @throws Exception
     */
    public AbstractEntityService<Entity, Id, Param> getTargetProxy() {

        try {
            Object target = AopContext.currentProxy();
            if (target instanceof AbstractEntityService) {
                return (AbstractEntityService) target;
            } else {
                return this;
            }
        } catch (IllegalStateException e) {
            return this;
        }
    }


}
