package com.zhangmeng.model.base.baseService.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhangmeng.model.base.baseDao.AbstractBaseMapper;
import com.zhangmeng.model.base.baseEntity.BaseEntity;
import com.zhangmeng.model.base.baseService.BaseService;
import com.zhangmeng.model.dto.query.JpaQueryData;
import com.zhangmeng.model.dto.query.QueryParams;
import com.zhangmeng.model.dto.query.QueryResult;
import com.zhangmeng.model.dto.query.QuerySingleParams;
import com.zhangmeng.model.vo.Result;
import com.zhangmeng.model.vo.StatusCode;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.entity.Example;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @param <T>
 * @author zhangmeng
 * @version 1.0
 * @date 2020年11月13日09:56:22
 */
public abstract class AbstractBaseServiceImpl<T extends BaseEntity<Long>> implements BaseService<T> {

    @Autowired
    private AbstractBaseMapper<T> baseDao;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private final Class<T> modelClass;

    @PersistenceContext
    private EntityManager entityManager;

    @SuppressWarnings("unchecked")
    public AbstractBaseServiceImpl() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        modelClass = (Class<T>) pt.getActualTypeArguments()[0];
    }

    @Override
    public void save(T model) {
        this.baseDao.insert(model);
    }

    @Override
    public void save(List<T> models) {
        this.baseDao.insertList(models);
    }

    @Override
    public void deleteById(Long id) {
        this.baseDao.deleteByPrimaryKey(id);
    }

    @Override
    public void deleteByIds(String ids) {
        this.baseDao.deleteByIds(ids);
    }

    @Override
    public void update(T model) {
        this.baseDao.updateByPrimaryKeySelective(model);
    }

    @Override
    public T findById(Long id) {
        return this.baseDao.selectByPrimaryKey(id);
    }

    @Override
    public List<T> findByIds(String ids) {
        return this.baseDao.selectByIds(ids);
    }

    @Override
    public List<T> findByCondition(Condition condition) {
        return this.baseDao.selectByCondition(condition);
    }

    @Override
    public List<T> findAll() {
        return this.baseDao.selectAll();
    }

    @Override
    public List<T> findAll(QueryParams queryParams) {
        if (queryParams != null) {
            PageHelper.startPage(queryParams.getPageNum(), queryParams.getPageSize(), queryParams.getOrderBy());
            List<T> list = this.baseDao.selectAll();
            PageInfo<T> pageInfo = new PageInfo<>(list);
            return pageInfo.getList();
        }
        return null;
    }

    @Override
    public List<Map<String, Object>> query(String sql) {
        return this.jdbcTemplate.queryForList(sql);
    }

    @Override
    public Integer selectCountByCondition(Condition condition) {
        return this.baseDao.selectCountByCondition(condition);
    }

    @Override
    public Integer selectCountEq(String key, Object value) {
        Condition condition = new Condition(modelClass);
        Example.Criteria criteria = condition.createCriteria();
        criteria.andEqualTo(key, value);
        return this.selectCountByCondition(condition);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<T> findByCondition(QueryParams QueryParams) {
        return (List<T>) findByParams(QueryParams, false, false);
    }

    /**
     * 查询
     *
     * @param QueryParams 查询参数
     * @param isPageInfo  是否返回分页对象
     * @return obj
     */
    @SuppressWarnings("unchecked")
    public PageInfo<T> findByCondition(QueryParams QueryParams, boolean isPageInfo) {
        return (PageInfo<T>) findByParams(QueryParams, isPageInfo, false);
    }

    @SuppressWarnings("unchecked")
    public QueryResult<T> findByCondition(QueryParams QueryParams, boolean isPageInfo, boolean isQueryResult) {
        return (QueryResult<T>) findByParams(QueryParams, isPageInfo, isQueryResult);
    }

    /**
     * 根据单个参数查询
     *
     * @return List<T>
     */
    public List<T> findBySingleParams(QuerySingleParams querySingleParams) {
        Condition condition = new Condition(modelClass);
        Example.Criteria criteria = condition.createCriteria();
        if (querySingleParams != null) {
            String key = querySingleParams.getKey();
            Object value = querySingleParams.getValue();
            QuerySingleParams.Type type = querySingleParams.getType();
            switch (type) {
                case equal:
                    criteria.andEqualTo(key, value);
                    break;
                case not_equal:
                    criteria.andNotEqualTo(key, value);
                    break;
                case like:
                    criteria.andLike(key, "%" + value + "%");
                    break;
                case not_like:
                    criteria.andNotLike(key, "%" + value + "%");
                    break;
            }
        }
        return this.baseDao.selectByCondition(condition);
    }

    /**
     * 根据参数查询
     *
     * @return List<T>
     */
    public List<T> findByParams(List<QuerySingleParams> querySingleParamsList) {
        Condition condition = new Condition(modelClass);
        Example.Criteria criteria = condition.createCriteria();
        if (querySingleParamsList.size() > 0) {
            for (QuerySingleParams querySingleParams : querySingleParamsList) {
                if (querySingleParams != null) {
                    String key = querySingleParams.getKey();
                    Object value = querySingleParams.getValue();
                    QuerySingleParams.Type type = querySingleParams.getType();
                    switch (type) {
                        case equal:
                            criteria.andEqualTo(key, value);
                            break;
                        case not_equal:
                            criteria.andNotEqualTo(key, value);
                            break;
                        case like:
                            criteria.andLike(key, "%" + value + "%");
                            break;
                        case not_like:
                            criteria.andNotLike(key, "%" + value + "%");
                            break;
                    }
                }
            }
        }
        return this.baseDao.selectByCondition(condition);
    }

    public List<T> findByMapWithEq(Map<String, Object> map) {
        Condition condition = new Condition(modelClass);
        Example.Criteria criteria = condition.createCriteria();
        criteria.andEqualTo(map);
        return this.baseDao.selectByCondition(condition);
    }

    @Override
    public List<T> findByObj(T t) {
        return this.baseDao.select(t);
    }

    @Override
    public Result<T> findByByConditionWithResult(QueryParams queryParams) {
        PageInfo pageInfo = this.findByCondition(queryParams, true);
        return new Result<>(true, StatusCode.OK, null, pageInfo.getTotal(), pageInfo.getList());
    }

    private Object findByParams(QueryParams QueryParams, boolean isPageInfo, boolean isQueryResult) {
        PageInfo<T> pageInfo = null;

        if (QueryParams.getPageNum() == null) {
            QueryParams.setPageNum(1);
        }

        if (QueryParams.getPageSize() == null) {
            QueryParams.setPageSize(10);
        }
        if (isNotNull(QueryParams.getOrderBy())) {
            pageInfo = pageHelper(QueryParams.getPageNum(), QueryParams.getPageSize(), QueryParams.getOrderBy(), QueryParams.getCondition());
        } else {
            pageInfo = pageHelper(QueryParams.getPageNum(), QueryParams.getPageSize(), QueryParams.getCondition());
        }
        if (isQueryResult) {
            return page_params_response(pageInfo);
        }
        if (isPageInfo) {
            return pageInfo;
        }
        return pageInfo.getList();
    }

    /**
     * @param pageNum   当前页
     * @param pageSize  每页记录数
     * @param condition 查询条件
     * @return object
     */
    private PageInfo<T> pageHelper(Integer pageNum, Integer pageSize, Condition condition) {
        PageHelper.startPage(pageNum, pageSize);
        List<T> ts = this.baseDao.selectByCondition(condition);
        return new PageInfo<>(ts);
    }

    /**
     * @param pageNum   当前页
     * @param pageSize  每页记录数
     * @param orderBy   排序
     * @param condition 查询条件
     * @return object
     */
    private PageInfo<T> pageHelper(Integer pageNum, Integer pageSize, String orderBy, Condition condition) {
        PageHelper.startPage(pageNum, pageSize, orderBy);
        List<T> ts = this.baseDao.selectByCondition(condition);
        return new PageInfo<>(ts);
    }

    public T jpa_findById(Long id) {
        SimpleJpaRepository<T, Long> jpaRepository = new SimpleJpaRepository<>(modelClass, entityManager);
        return jpaRepository.getOne(id);
    }

    public List<T> jpa_findByCondition(List<JpaQueryData> jpaQueryDataList) {
        SimpleJpaRepository<T, Long> jpaRepository = new SimpleJpaRepository<>(modelClass, entityManager);
        Specification<T> spec = spec(jpaQueryDataList);
        return jpaRepository.findAll(spec);
    }

    private Specification<T> spec(List<JpaQueryData> jpaQueryDataList) {

        return new Specification<T>() {

            /**
             * @param root        对查询对象属性的封装
             * @param criteriaQuery    封装了我们要执行的查询中的各个部分的信息，select from order
             * @param criteriaBuilder    查询条件的构造器
             * @return Predicate
             */
            @SneakyThrows
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                List<Predicate> list = new ArrayList<>();
                if (jpaQueryDataList.size() > 0) {
                    for (JpaQueryData jpaQueryData : jpaQueryDataList) {
                        String key = jpaQueryData.getKey();
                        Object value = jpaQueryData.getValue();
                        JpaQueryData.Condition condition = jpaQueryData.getCondition();
                        switch (condition) {
                            case like:
                                list.add(criteriaBuilder.like(root.get(key), "%" + value + "%"));
                                break;
                            case equal:
                                list.add(criteriaBuilder.equal(root.get(key), value));
                                break;
                            case between:
                                JpaQueryData.Between between = jpaQueryData.getBetween();
                                Object var1 = between.getVar1();
                                Object var2 = between.getVar2();

                                if (var1 instanceof String) {
                                    list.add(criteriaBuilder.between(root.get(key), var1.toString(), var2.toString()));
                                } else if (var1 instanceof Date) {
                                    list.add(criteriaBuilder.between(root.get(key), (Date) var1, (Date) var2));
                                }
                                break;
                        }
                    }
                    Predicate[] arr = new Predicate[list.size()];
                    return criteriaBuilder.and(list.toArray(arr));
                }
                return null;
            }
        };
    }

    public static boolean isNotNull(Object object) {
        return object != null && !"".equals(object);
    }

    public static <T> QueryResult<T> page_params_response(PageInfo<T> pageInfo) {
        QueryResult<T> queryResult = new QueryResult<>();
        queryResult.setPageNum(pageInfo.getPageNum());
        queryResult.setPageSize(pageInfo.getPageSize());
        if (pageInfo.getPrePage() == 0 ){
            queryResult.setPrevPage(1);
        }else {
            queryResult.setPrevPage(pageInfo.getPrePage());
        }
        if (pageInfo.getNextPage() == 0 ){
            queryResult.setNextPage(1);
        }else {
            queryResult.setNextPage(pageInfo.getNextPage());
        }
        queryResult.setList(pageInfo.getList());
        queryResult.setCount(pageInfo.getTotal());
        return queryResult;
    }
}