package com.sonic.daosupport.wrapper;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author code
 */
@Slf4j
public class CriteriaWrapper {

    private static final String DELIMITER = "__";

    /** 使用ConcurrentMap，HashMap线程不安全 */
    private static ConcurrentMap<Class, Map<String, CriteriaField>> criteriaFieldAnnotations = Maps.newConcurrentMap();

    @Getter
    private Boolean andOperator = true;

    @Getter
    private ImmutableListMultimap<String, QueryField> queryFieldMap;

    public CriteriaWrapper() {
        queryFieldMap = ImmutableListMultimap.of();
    }

    public CriteriaWrapper(ImmutableListMultimap<String, QueryField> queryFieldMap, boolean andOperator) {
        this.queryFieldMap = queryFieldMap;
        this.andOperator = andOperator;
    }

    public static CriteriaWrapperBuilder builder() {
        return new CriteriaWrapperBuilder();
    }

    public static class CriteriaWrapperBuilder {
        private List<QueryField> queryFields = Lists.newArrayList();
        private boolean andOperator = true;

        /**
         * @param key java bean的字段名或者"Field__Operator"格式
         * @param value 除了IS_NULL, IS_NOT_NULL允许null值外，其他情况如果为null该查询条件会被过滤
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(String key, Object value) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(key));
            return queryField(key, QueryField.getDefaultOperator(key, value), value);
        }

        /**
         *
         * @param key java bean的字段名或者"Field__Operator"格式
         * @param defaultOperator Operator
         * @param value 除了IS_NULL, IS_NOT_NULL允许null值外，其他情况如果为null该查询条件会被过滤
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(String key, Operator defaultOperator, Object value) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(key));

            QueryField queryField = QueryField.buildWithNullFilter(key, defaultOperator, value);
            if (queryField != null) {
                this.queryFields.add(queryField);
            }

            return this;
        }

        /**
         *
         * @param condition false的时候，该查询条件会被过滤
         * @param key java bean的字段名或者"Field__Operator"格式
         * @param value 如果为null，该查询条件会被过滤
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(boolean condition, String key, Object value) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(key));
            return queryField(condition, key, QueryField.getDefaultOperator(key, value), value);
        }

        /**
         *
         * @param condition false的时候，该查询条件会被过滤
         * @param key java bean的字段名或者"Field__Operator"格式
         * @param defaultOperator 默认的查询Operator
         * @param value 如果为null，该查询条件会被过滤
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(boolean condition, String key, Operator defaultOperator, Object value) {
            Preconditions.checkArgument(!Strings.isNullOrEmpty(key));

            if (!condition) {
                return this;
            }

            queryFields.add(QueryField.build(key, defaultOperator, value));
            return this;
        }

        /**
         *
         * @param params {key, value}, key支持"Field__Operator"格式；value如果为null，该查询条件会被过滤
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(Map<String, Object> params) {
            return queryField(params, null);
        }

        /**
         *
         * @param params {key, value}, key支持"Field__Operator"格式；value如果为null，该查询条件会被过滤
         * @param converter 对querfield进行转换. 比如查询条件, 查询值.
         * @return CriteriaWrapperBuilder
         */
        public CriteriaWrapperBuilder queryField(Map<String, Object> params, Function<QueryField, QueryField> converter) {
            List<QueryField> queryFields = params.entrySet().stream()
                    .map(entry -> QueryField.build(entry.getKey(), entry.getValue()))
                    .map(queryField -> converter == null ? queryField : converter.apply(queryField))
                    .filter(Objects::nonNull)
                    .filter(queryField -> queryField.getValue() != null || queryField.getOperator().allowNullValue())
                    .collect(Collectors.toList());
            this.queryFields.addAll(queryFields);
            return this;
        }

        public CriteriaWrapperBuilder queryFields(List<QueryField> queryFields) {
            this.queryFields.addAll(queryFields);
            return this;
        }

        public CriteriaWrapperBuilder andOperator(boolean andOperator) {
            this.andOperator = andOperator;
            return this;
        }

        public CriteriaWrapper build() {
            ImmutableListMultimap<String, QueryField> multimap = queryFields.stream()
                    .collect(ImmutableListMultimap.toImmutableListMultimap(QueryField::getField, Function.identity()));
            return new CriteriaWrapper(multimap, this.andOperator);
        }
    }

    /**
     *  从bean对象来构造一个CriteriaWrapper
     *  需要这个bean对象中查询的属性通过{@link CriteriaField}来声明
     * @param bean 查询条件的bean对象
     * @return 查询对象
     */
    public static CriteriaWrapper fromBean(Object bean) {
        return fromBean(bean, true);
    }


    /**
     *  从bean对象来构造一个CriteriaWrapper
     *  需要这个bean对象中查询的属性通过{@link CriteriaField}来声明
     * @param bean 查询条件的bean对象
     * @param andOperator true查询条件是and操作, false查询条件or操作.
     * @return 查询对象
     */
    public static CriteriaWrapper fromBean(Object bean, boolean andOperator) {
        return fromBean(bean, andOperator, null);
    }

    /**
     *  从bean对象来构造一个CriteriaWrapper
     *  需要这个bean对象中查询的属性通过{@link CriteriaField}来声明
     * @param bean 查询条件的bean对象
     * @param andOperator true查询条件是and操作, false查询条件or操作.
     * @return 查询对象
     */
    public static CriteriaWrapper fromBean(Object bean, boolean andOperator,
                                           Function<QueryField, List<QueryField>> converter) {
        Map<String, CriteriaField> annotations = getFieldAnnotations(bean.getClass());

        Map<String, Object> beanMap = BeanMap.create(bean);
        List<QueryField> queryFields = beanMap.entrySet().stream()
                .map(entry -> QueryField.build(entry.getKey(), entry.getValue(), annotations))
                .filter(Objects::nonNull)
                .flatMap(queryField -> converter == null ? Stream.of(queryField) : converter.apply(queryField).stream())
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        return CriteriaWrapper.builder().queryFields(queryFields).andOperator(andOperator).build();
    }


    private static Map<String, CriteriaField> getFieldAnnotations(Class beanClazz) {
        return criteriaFieldAnnotations.computeIfAbsent(beanClazz, clazz ->
                FieldUtils.getFieldsListWithAnnotation(clazz, CriteriaField.class).stream()
                        .collect(Collectors.toMap(Field::getName, field -> field.getAnnotation(CriteriaField.class))));
    }

    @Builder(toBuilder = true)
    @Data
    @AllArgsConstructor
    public static class QueryField {
        /** java bean的property name */
        private String field;
        /** 查询的operator */
        private Operator operator;
        /** 查询的值 */
        private Object value;

        private static QueryField build(String field, Object value, Map<String, CriteriaField> annotations) {
            CriteriaField fieldAnnotation = annotations.get(field);
            if (fieldAnnotation == null) {
                // 如果没有Annotation，默认会过滤Null
                return QueryField.buildWithNullFilter(field, getDefaultOperator(field, value), value);
            }

            if (fieldAnnotation.ignore()) {
                return null;
            }

            if (fieldAnnotation.filterEmpty()
                    && (value instanceof Collection)
                    && CollectionUtils.isEmpty((Collection)value)) {
                return null;
            }

            if (fieldAnnotation.filterNull() && Objects.isNull(value)) {
                return null;
            }

            String fieldName = Strings.isNullOrEmpty(fieldAnnotation.field()) ? field : fieldAnnotation.field();
            // XXX 注解中获取的operator如果为EQ，无法判断是用户手动设置为EQ还是使用的默认operator.EQ，这里暂时保持原状
            return QueryField.build(fieldName, fieldAnnotation.operator(), value);
        }

        private static QueryField build(String key, Object value) {
            return QueryField.build(key, getDefaultOperator(key, value), value);
        }

        private static QueryField build(String key, Operator defaultOperator, Object value) {
            String field = StringUtils.substringBefore(key, DELIMITER);
            String expression = StringUtils.substringAfter(key, DELIMITER);
            Operator operator = Strings.isNullOrEmpty(expression) ? defaultOperator : Operator.valueOf(expression);
            return new QueryField(field, operator, value);
        }

        private static QueryField buildWithNullFilter(String key, Operator defaultOperator, Object value) {
            QueryField queryField = build(key, defaultOperator, value);
            if (queryField.getValue() == null && !queryField.getOperator().allowNullValue()) {
                return null;
            }
            return queryField;
        }

        private static Operator getDefaultOperator(String field, Object value) {
            if (!(value instanceof Collection)) {
                return Operator.EQ;
            }

            // 当value instanceof Collection，检查值是否为空
            if (((Collection)value).isEmpty()) {
                log.error("value is collection and is empty, field={}", field);
            }
            return Operator.IN;
        }
    }
}
