package com.fjhx.utils.wrapperUtil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.query.Query;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.SneakyThrows;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.springblade.core.log.exception.ServiceException;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

@SuppressWarnings({"unused"})
public class IWrapper<T> extends AbstractWrapper<T, String, IWrapper<T>> implements Query<IWrapper<T>, T, String> {

    // =====================================================================
    // QueryWrapper原生方法
    // =====================================================================

    private final SharedString sqlSelect = new SharedString();

    private IWrapper(T entityMap, Class<T> entityClass, AtomicInteger paramNameSeq,
                     Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
                     SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
        super.setEntity(entityMap);
        super.setEntityClass(entityClass);
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
        this.sqlFirst = sqlFirst;
    }

    @Override
    public IWrapper<T> select(String... columns) {
        if (ArrayUtils.isNotEmpty(columns)) {
            this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
        }
        return this;
    }

    @Override
    public IWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
        super.setEntityClass(entityClass);
        this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(getEntityClass()).chooseSelect(predicate));
        return this;
    }

    @Override
    public String getSqlSelect() {
        return sqlSelect.getStringValue();
    }

    @Override
    protected IWrapper<T> instance() {
        return new IWrapper<>(getEntity(), getEntityClass(), paramNameSeq, paramNameValuePairs, new MergeSegments(),
                SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString());
    }

    @Override
    public void clear() {
        super.clear();
        sqlSelect.toNull();
    }


    // =====================================================================
    // 初始化
    // =====================================================================

    private Map<String, ?> entityMap;

    private IWrapper() {
        super.initNeed();
    }

    private IWrapper(Object obj) {
        super.initNeed();
        entityMap = BeanUtil.beanToMap(obj);
    }

    private IWrapper(Map<String, ?> map) {
        super.initNeed();
        entityMap = map;
    }

    public static <T> IWrapper<T> getWrapper(Object obj) {
        return new IWrapper<>(obj);
    }

    public static <T> IWrapper<T> getWrapper(Map<String, ?> map) {
        return new IWrapper<>(map);
    }

    // =====================================================================
    // 拓展方法
    // =====================================================================

    /**
     * 时间段查询
     */
    public <K, V> IWrapper<T> periodTime(SFunction<K, V> function) {
        return periodTime(getSqlFieldName(function));
    }

    public <K, V> IWrapper<T> periodTime(String alias, SFunction<K, V> function) {
        return periodTime(getSqlFieldName(alias, function));
    }

    public IWrapper<T> periodTime(String field) {
        Object beginTime = getValue("beginTime");
        if (ObjectUtil.isEmpty(beginTime)) {
            return this;
        }

        Object endTime = getValue("endTime");
        if (ObjectUtil.isEmpty(endTime)) {
            return this;
        }

        Date beginOfDay;
        Date endOfDay;

        try {
            beginOfDay = DateUtil.beginOfDay(DateUtil.parse(beginTime.toString()));
            endOfDay = DateUtil.endOfDay(DateUtil.parse(endTime.toString()));
        } catch (Exception e) {
            throw new ServiceException("时间类型转换错误");
        }

        return between(true, field, beginOfDay, endOfDay);
    }

    /**
     * 关键字查询
     */
    public IWrapper<T> keyword(KeywordData... keywordData) {
        Object value = getValue("keyword");
        return keyword(value, keywordData);
    }

    public IWrapper<T> keyword(Object value, KeywordData... keywordData) {
        this.and(ObjectUtil.isNotEmpty(value), q -> {
            for (KeywordData keywordDatum : keywordData) {
                q.or().like(keywordDatum.getSqlFieldName(), value);
            }
        });
        return this;
    }

    /**
     * 查询字段
     */
    @SafeVarargs
    public final <K, V> IWrapper<T> select(SFunction<K, V>... functions) {
        return select(getColumns(functions));
    }

    /**
     * 排序
     */
    public <K, V> IWrapper<T> orderByAsc(SFunction<K, V> function) {
        return orderByAsc(getSqlFieldName(function));
    }

    public <K, V> IWrapper<T> orderByAsc(String alias, SFunction<K, V> function) {
        return orderByAsc(getSqlFieldName(alias, function));
    }

    public <K, V> IWrapper<T> orderByDesc(SFunction<K, V> function) {
        return orderByDesc(getSqlFieldName(function));
    }

    public <K, V> IWrapper<T> orderByDesc(String alias, SFunction<K, V> function) {
        return orderByDesc(getSqlFieldName(alias, function));
    }

    /**
     * between
     */
    public <K, V> IWrapper<T> between(SFunction<K, V> function, Object val1, Object val2) {
        String sqlFieldName = getSqlFieldName(function);
        return between(sqlFieldName, val1, val2);
    }

    public <K, V> IWrapper<T> between(String alias, SFunction<K, V> function, Object val1, Object val2) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return between(sqlFieldName, val1, val2);
    }

    public <K, V> IWrapper<T> between(String sqlFieldName, Object val1, Object val2) {
        return between(ObjectUtil.isAllNotEmpty(val1, val2), sqlFieldName, val1, val2);
    }

    /**
     * eq
     */
    public <K, V> IWrapper<T> eq(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return eq(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> eq(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return eq(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> eq(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return eq(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> eq(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return eq(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> eq(String sqlFieldName, Object value) {
        return eq(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * ne
     */
    public <K, V> IWrapper<T> ne(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return ne(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ne(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return ne(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ne(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return ne(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ne(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return ne(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> ne(String sqlFieldName, Object value) {
        return ne(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * ge
     */
    public <K, V> IWrapper<T> ge(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return ge(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ge(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return ge(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ge(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return ge(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> ge(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return ge(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> ge(String sqlFieldName, Object value) {
        return ge(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * gt
     */
    public <K, V> IWrapper<T> gt(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return gt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> gt(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return gt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> gt(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return gt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> gt(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return gt(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> gt(String sqlFieldName, Object value) {
        return gt(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * le
     */
    public <K, V> IWrapper<T> le(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return le(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> le(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return le(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> le(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return le(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> le(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return le(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> le(String sqlFieldName, Object value) {
        return le(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * lt
     */
    public <K, V> IWrapper<T> lt(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return lt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> lt(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return lt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> lt(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return lt(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> lt(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return lt(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> lt(String sqlFieldName, Object value) {
        return lt(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * like
     */
    public <K, V> IWrapper<T> like(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return like(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> like(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return like(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> like(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return like(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> like(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return like(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> like(String sqlFieldName, Object value) {
        return apply(ObjectUtil.isNotEmpty(value), "instr(" + sqlFieldName + ", {0}) > 0", value);
    }

    /**
     * notLike
     */
    public <K, V> IWrapper<T> notLike(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return notLike(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notLike(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return notLike(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notLike(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return notLike(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notLike(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return notLike(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> notLike(String sqlFieldName, Object value) {
        return notLike(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * likeLeft
     */
    public <K, V> IWrapper<T> likeLeft(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return likeLeft(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeLeft(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return likeLeft(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeLeft(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return likeLeft(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeLeft(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return likeLeft(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> likeLeft(String sqlFieldName, Object value) {
        return likeLeft(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * likeRight
     */
    public <K, V> IWrapper<T> likeRight(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return likeRight(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeRight(SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(function);
        return likeRight(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeRight(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return likeRight(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> likeRight(String alias, SFunction<K, V> function, Object value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return likeRight(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> likeRight(String sqlFieldName, Object value) {
        return likeRight(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    /**
     * in
     */
    public <K, V> IWrapper<T> in(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return in(sqlFieldName, (Collection<?>) value);
    }

    public <K, V> IWrapper<T> in(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return in(sqlFieldName, (Collection<?>) value);
    }

    public <K, V> IWrapper<T> in(SFunction<K, V> function, Collection<?> value) {
        String sqlFieldName = getSqlFieldName(function);
        return in(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> in(String alias, SFunction<K, V> function, Collection<?> value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return in(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> in(SFunction<K, V> function, Object... value) {
        String sqlFieldName = getSqlFieldName(function);
        return in(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> in(String alias, SFunction<K, V> function, Object... value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return in(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> in(String sqlFieldName, Collection<?> value) {
        return in(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    @Override
    public IWrapper<T> in(String sqlFieldName, Object... value) {
        if (value.length == 0) {
            return this;
        }
        if (value[0] instanceof Collection) {
            return in(ObjectUtil.isNotEmpty(value[0]), sqlFieldName, Convert.toList(value[0]));
        }
        return in(sqlFieldName, Convert.toList(value));
    }

    /**
     * notIn
     */
    public <K, V> IWrapper<T> notIn(SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(fieldName);
        Object value = getValue(fieldName);
        return notIn(sqlFieldName, (Collection<?>) value);
    }

    public <K, V> IWrapper<T> notIn(String alias, SFunction<K, V> function) {
        String fieldName = getFieldName(function);
        String sqlFieldName = getSqlFieldName(alias, fieldName);
        Object value = getValue(fieldName);
        return notIn(sqlFieldName, (Collection<?>) value);
    }

    public <K, V> IWrapper<T> notIn(SFunction<K, V> function, Collection<?> value) {
        String sqlFieldName = getSqlFieldName(function);
        return notIn(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notIn(String alias, SFunction<K, V> function, Collection<?> value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return notIn(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notIn(SFunction<K, V> function, Object... value) {
        String sqlFieldName = getSqlFieldName(function);
        return notIn(sqlFieldName, value);
    }

    public <K, V> IWrapper<T> notIn(String alias, SFunction<K, V> function, Object... value) {
        String sqlFieldName = getSqlFieldName(alias, function);
        return notIn(sqlFieldName, value);
    }

    @Override
    public IWrapper<T> notIn(String sqlFieldName, Collection<?> value) {
        return notIn(ObjectUtil.isNotEmpty(value), sqlFieldName, value);
    }

    @Override
    public IWrapper<T> notIn(String sqlFieldName, Object... value) {
        if (value.length == 0) {
            return this;
        }
        if (value[0] instanceof Collection) {
            return notIn(ObjectUtil.isNotEmpty(value[0]), sqlFieldName, Convert.toList(value[0]));
        }
        return notIn(sqlFieldName, Convert.toList(value));
    }


    // =====================================================================
    // 私有方法
    // =====================================================================

    /**
     * 获取对象字段名
     * 例：getFieldName(Entity::getBaseId) -》 return "baseId"
     */
    @SneakyThrows
    private <K, V> String getFieldName(SFunction<K, V> function) {
        // SerializedLambda resolve = SerializedLambda.resolve(function);
        // return PropertyNamer.methodToProperty(resolve.getImplMethodName());

        Method method = function.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
        return PropertyNamer.methodToProperty(serializedLambda.getImplMethodName());
    }

    /**
     * 获取sql字段名
     * 例：getSqlFieldName(Entity::getBaseId) -》 return "base_id"
     */
    private <K, V> String getSqlFieldName(SFunction<K, V> function) {
        return StrUtil.toUnderlineCase(getFieldName(function));
    }

    /**
     * 获取sql字段名
     * 例：getSqlFieldName("s", Entity::getBaseId) -》 return "s.base_id"
     */
    private <K, V> String getSqlFieldName(String alias, SFunction<K, V> function) {
        return StrUtil.toUnderlineCase(alias + "." + getFieldName(function));
    }

    /**
     * 获取sql字段名
     * 例：getSqlFieldName("baseId") -》 return "base_id"
     */
    private String getSqlFieldName(String fieldName) {
        return StrUtil.toUnderlineCase(fieldName);
    }

    /**
     * 获取sql字段名
     * 例：getSqlFieldName("s", "baseId") -》 return "s.base_id"
     */
    private String getSqlFieldName(String alias, String fieldName) {
        return alias + "." + StrUtil.toUnderlineCase(fieldName);
    }

    /**
     * 获取字段值
     */
    private Object getValue(String fieldName) {
        if (entityMap == null) {
            throw new NullPointerException("需要传入提供字段值的对象");
        }

        return entityMap.get(fieldName);
    }

    /**
     * functions转String[]
     */
    @SafeVarargs
    private final <K, V> String[] getColumns(SFunction<K, V>... functions) {
        String[] columns = new String[functions.length];
        for (int i = 0; i < functions.length; i++) {
            columns[i] = getSqlFieldName(functions[i]);
        }
        return columns;
    }

}
