package cn.enilu.flash.dao;

import cn.enilu.flash.bean.entity.front.BaseMongoEntity;
import cn.enilu.flash.utils.factory.Page;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Created on 2017/12/29 0029.
 *
 * @author zt
 */
@Repository
public class MongoRepository {
    @Autowired
    private MongoTemplate mongoTemplate;

    public void save(BaseMongoEntity entity) {
        mongoTemplate.save(entity);
    }

    public void save(Object data, String collectionName) {
        mongoTemplate.save(data, collectionName);

    }

    public void delete(Long id, String collectionName) {
        mongoTemplate.remove(Query.query(Criteria.where("id").is(id)), collectionName);
    }

    public void delete(String collectionName, Map<String, Object> keyValues) {
        mongoTemplate.remove(Query.query(criteria(keyValues)), collectionName);
    }
    public void clear(Class klass){
         mongoTemplate.dropCollection(klass);
         mongoTemplate.createCollection(klass);
    }
    public void clear(String collectionName){
        mongoTemplate.dropCollection(collectionName);
        mongoTemplate.createCollection(collectionName);
    }
    public void update(BaseMongoEntity entity) {
        mongoTemplate.save(entity);
    }

    public UpdateResult update(Long id, String collectionName, Map<String, Object> keyValues) {
        Update update = null;
        for (Map.Entry<String, Object> entry : keyValues.entrySet()) {
            if (update == null) {
                update = Update.update(entry.getKey(), entry.getValue());
            } else {
                update.set(entry.getKey(), entry.getValue());
            }
        }
        return mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)), update, collectionName);
    }

    public UpdateResult update(String id, String collectionName, Map<String, Object> keyValues) {
        Update update = null;
        for (Map.Entry<String, Object> entry : keyValues.entrySet()) {
            if (update == null) {
                update = Update.update(entry.getKey(), entry.getValue());
            } else {
                update.set(entry.getKey(), entry.getValue());
            }
        }
        return mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(id)), update, collectionName);
    }

    public <T, P> T findOne(Class<T> klass, P key) {
        return findOne(klass, "id", key);
    }
    public <T> T findOne(Class<T> klass, Map<String,Object> params) {
        Criteria criteria = criteria(params);
        if (criteria == null) {
            return mongoTemplate.findOne(Query.query(criteria), klass);
        }
        return null;
    }
    public <T> T findOne(Class<T> klass, String key, Object value) {
        return mongoTemplate.findOne(Query.query(Criteria.where(key).is(value)), klass);
    }

    public Object findOne(Long id, String collectionName) {
        return mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)), Map.class, collectionName);
    }

    public Map findOne(String collectionName, Object... extraKeyValues) {
        Criteria criteria = criteria(extraKeyValues);
        if (Objects.isNull(criteria)) {
            List<Map> list = mongoTemplate.findAll(Map.class, collectionName);
            if (list != null && !list.isEmpty()) {
                return list.get(0);
            }
            return Collections.emptyMap();
        }
        return mongoTemplate.findOne(Query.query(criteria), Map.class, collectionName);
    }

    public <T> T findOne(Class<T> klass) {
        Query query = new Query();
        return mongoTemplate.findOne(query, klass);

    }

    public <T> T findOne(Class<T> klass, Object... keyValues) {
        Criteria criteria = criteria(keyValues);

        if (criteria == null) {
            List<T> list = mongoTemplate.findAll(klass);
            if (list != null) {
                return list.get(0);
            }
            return null;
        }
        return mongoTemplate.findOne(Query.query(criteria), klass);
    }

    public <T> Page<T> queryPage(Page<T> page, Class<T> klass) {
        return queryPage(page, klass, null);
    }

    public <T> Page<T> queryPage(Page<T> page, Class<T> klass, Map<String, Object> params) {
        Pageable pageable = PageRequest.of(page.getCurrent() - 1, page.getSize(), Sort.Direction.DESC, "id");
        Query query = new Query();
        if (params != null && !params.isEmpty()) {
            Criteria criteria = criteria(params);
            query = Query.query(criteria);
        }
        List<T> list = mongoTemplate.find(query.with(pageable), klass);
        Long count = count(klass, params);
        page.setTotal(count.intValue());
        page.setRecords(list);
        return page;
    }

    public <T> List<T> findAll(Class<T> klass) {
        return mongoTemplate.findAll(klass);
    }

    public <T> List<T> findAll(Class<T> klass, Object... keyValues) {
        Criteria criteria = criteria(keyValues);
        return mongoTemplate.find(Query.query(criteria), klass);
    }

    public <T> List<T> findAll(Class<T> klass, Map<String, Object> keyValues) {
        Criteria criteria = criteria(keyValues);
        return mongoTemplate.find(Query.query(criteria), klass);
    }

    public List findAll(String collection) {
        return mongoTemplate.findAll(Map.class, collection);
    }

    public List<Map> findAll(String collectionName, Object... keyValues) {
        Criteria criteria = criteria(keyValues);
        return mongoTemplate.find(Query.query(criteria), Map.class, collectionName);
    }

    /**
     * 查询指定位置附近的商家
     * @param x
     * @param y
     * @param collectionName
     * @param params
     * @param miles 公里数
     * @return
     */
    public GeoResults<Map> near(double x, double y, String collectionName, Map<String, Object> params,Integer miles) {
        Point location = new Point(x, y);
        NearQuery nearQuery = NearQuery.near(location).maxDistance(new Distance(miles, Metrics.MILES));
        if (params != null && !params.isEmpty()) {
            Query query = Query.query(criteria(params));
            nearQuery.query(query);
        }
        try {
            return mongoTemplate.geoNear(nearQuery, Map.class, collectionName);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        return null;
    }
    /**
     * 查询指定位置附近的商家,默认查询十公里范围内
     * @param x
     * @param y
     * @param collectionName
     * @param params
     * @return
     */
    public GeoResults<Map> near(double x, double y, String collectionName, Map<String, Object> params) {
      return near(x,y,collectionName,params,10);
    }

    public long count(Class klass) {
        return count(klass, null);
    }

    public long count(Class klass, Map<String, Object> params) {
        Criteria criteria = criteria(params);
        if (criteria == null) {

            return mongoTemplate.count(new Query(), klass);
        } else {
            return mongoTemplate.count(Query.query(criteria), klass);
        }
    }

    public long count(String collection) {
        return mongoTemplate.count(null, collection);
    }

    public long count(String collection, Map<String, Object> params) {
        Criteria criteria = criteria(params);
        if (criteria == null) {
            return mongoTemplate.count(null, collection);
        } else {
            return mongoTemplate.count(Query.query(criteria), collection);
        }
    }

    private Criteria criteria(Map<String, Object> map) {
        Criteria criteria = null;
        if (map != null) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (criteria == null) {
                    criteria = Criteria.where(entry.getKey()).is(entry.getValue());
                } else {
                    criteria.and(entry.getKey()).is(entry.getValue());
                }
            }
        }
        return criteria;
    }

    private Criteria criteria(Object... extraKeyValues) {
        Criteria criteria = null;
        if (extraKeyValues.length % 2 != 0) {
            throw new IllegalArgumentException();
        } else {
            for (int i = 0; i < extraKeyValues.length; i += 2) {
                Object k = extraKeyValues[i];
                Object v = extraKeyValues[i + 1];
                if (i == 0) {
                    criteria = Criteria.where(k.toString()).is(v);
                } else {
                    criteria.and(k.toString()).is(v);
                }
            }

        }
        return criteria;
    }

}