package com.artfess.mongodb.repository.impl;


import com.artfess.mongodb.page.BasePage;
import com.artfess.mongodb.page.Page;
import com.artfess.mongodb.query.FieldSortMongo;
import com.artfess.mongodb.repository.MongoDbRepository;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhx
 * @create 2021/7/31
 */
public class MongoDbRepositoryImpl<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> implements MongoDbRepository<T, ID> {
    protected final MongoTemplate mongoTemplate;
    protected final MongoEntityInformation<T, ID> entityInformation;
    private Class<T> clazz;

    public MongoDbRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoTemplate mongoOperations) {
        super(metadata, mongoOperations);
        this.mongoTemplate = mongoOperations;
        this.entityInformation = metadata;
        clazz = entityInformation.getJavaType();
    }


    /**
     * @param id             更新主键
     * @param updateFieldMap key:需要更新的属性  value:对应的属性值
     */
    @Override
    public void update(ID id, Map<String, Object> updateFieldMap) {
        if (updateFieldMap != null && !updateFieldMap.isEmpty()) {
            Criteria criteria = new Criteria("_id").is(id);
            Update update = new Update();
            updateFieldMap.forEach(update::set);
            mongoTemplate.updateFirst(new Query(criteria), update, clazz);
        }
    }

    /**
     * @param queryFieldMap  字段名
     * @param updateFieldMap key:需要更新的属性  value:对应的属性值
     */
    @Override
    public void update(Map<String, Object> queryFieldMap, Map<String, Object> updateFieldMap) {
        Criteria criteria = new Criteria();
        if (null != queryFieldMap && !queryFieldMap.isEmpty()) {
            queryFieldMap.forEach((key, value) -> criteria.and(key).is(value));
        }

        if (updateFieldMap != null && !updateFieldMap.isEmpty()) {
            Update update = new Update();
            updateFieldMap.forEach(update::set);
            mongoTemplate.updateFirst(new Query(criteria), update, clazz);
        }
    }

    @Override
    public List<T> find(HashMap<String, Object> map) {
        Criteria criteria = new Criteria();
        if (null != map && !map.isEmpty()) {
            map.forEach((key, value) -> criteria.and(key).is(value));
        }

        return mongoTemplate.find(new Query(criteria), clazz);
    }

    @Override
    public Page<T> findPage(Page<T> page, HashMap<String, Object> map) {
        Criteria criteria = new Criteria();
        if (null != map && !map.isEmpty()) {
            map.forEach((key, value) -> criteria.and(key).is(value));
        }
        Query query = new Query(criteria);
        long count = this.count(map);
        // 总数
        page.setTotal(count);
        long currentPage = page.getCurrent();
        long pageSize = page.getSize();

        query.skip((currentPage - 1) * pageSize).limit((int) pageSize);

        List<T> rows = this.mongoTemplate.find(query, clazz);


        page.build(rows);
        return page;
    }

    @Override
    public BasePage<T> findBasePage(BasePage<T> page) {
        T param = page.getParam();

        Criteria criteria = new Criteria();


        if (null != param) {
            //query.addCriteria(new Criteria("_id").is(id));
            // 利用反射获取部位空的属性值
            Field[] field = clazz.getDeclaredFields();
            for (Field f : field) {
                // 设置些属性是可以访问的
                f.setAccessible(true);
                try {
                    String name = f.getName();
                    Object o = getFieldValueByName(name, param);
                    if (o != null) {
                        criteria.and(name).is(o);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        Query query = new Query(criteria);
        long count = this.mongoTemplate.count(query, clazz);
        // 总数
        page.setTotal(count);
        long currentPage = page.getCurrent();
        long pageSize = page.getSize();

        query.skip((currentPage - 1) * pageSize).limit((int) pageSize);
        List<FieldSortMongo> sorter = page.getSorter();
        //排序
        if (sorter.size()>0){

            for (FieldSortMongo fieldSort : sorter) {

                if (fieldSort.getOrder()){
                    query.with(Sort.by(Sort.Direction.ASC, fieldSort.getProperty()));
                }else {
                    query.with(Sort.by(Sort.Direction.DESC, fieldSort.getProperty()));
                }

            }

        }
        List<T> rows = this.mongoTemplate.find(query, clazz);

        page.setCurrent(count);
        page.setPages(page.getPages());
        page.setRows(rows);
        page.setSize(pageSize);


        return page;
    }

    private static Object getFieldValueByName(String fieldName, Object o) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = o.getClass().getMethod(getter, new Class[]{});
            Object value = method.invoke(o, new Object[]{});
            return value;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }




    @Override
    public long count(HashMap<String, Object> map) {
        Criteria criteria = new Criteria();
        if (null != map && !map.isEmpty()) {
            map.forEach((key, value) -> criteria.and(key).is(value));
        }

        return mongoTemplate.count(new Query(criteria), clazz);
    }


    @Override
    public T findByIdCollectionName(String id, String collectionName) {
        return mongoTemplate.findById(id, clazz, collectionName);
    }

    /**
     * 根据id进行更新
     * @param id
     * @param t
     */
    @Override
    public void updateById(String id, T t) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(id));
        Update update = buildBaseUpdate(t);
        this.mongoTemplate.updateMulti(query,update,clazz);

    }


    /**
     * 构建更新条件Query
     *
     * @param t
     * @return
     */
    private Update buildBaseUpdate(T t) {
        Update update = new Update();

        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value = field.get(t);
                if (value != null) {
                    update.set(field.getName(), value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return update;
    }
}
