package eu.dnetlib.common.logging.dao;

import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import eu.dnetlib.common.logging.LogMessage;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.conversions.Bson;
import org.springframework.beans.factory.annotation.Required;

public class DnetLoggerMongoDao implements DnetLoggerDao {

	private static final Log log = LogFactory.getLog(DnetLoggerMongoDao.class);
	private IndexOptions indexOptions = new IndexOptions().background(true);

	private MongoDatabase db;

	@Override
	public void init(final String collection) {
		if (!Lists.newArrayList(db.listCollectionNames()).contains(collection)) {
			log.info(String.format("creating collection %s", collection));
			db.createCollection(collection);
		}
	}

	@Override
	public void configureIndex(final String collection, final Map<String, IndexConf> conf) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		for (String key : conf.keySet()) {
			coll.createIndex(new BasicDBObject(key, 1), indexOptions);
		}
	}

	@Override
	public void writeLog(final String collection, final Map<String, Object> map) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		DBObject obj = BasicDBObjectBuilder.start(replaceKeyNames(map)).get();
		coll.insertOne(obj);
	}

	private Map<String, Object> replaceKeyNames(final Map<String, Object> inputMap) {
		final Map<String, Object> outMap = Maps.newHashMap();

		if (inputMap != null) {
			for (Entry<String, Object> e : inputMap.entrySet()) {
				final String k = e.getKey();
				if (!StringUtils.isBlank(k)) {
					Object v = e.getValue();
					outMap.put(k.replaceAll("\\.", "_"), v != null ? v : "null");
				}
			}
		}
		return outMap;
	}

	@Override
	public Map<String, String> findOne(final String collection, final String key, final String value) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		final DBObject obj = coll.find(Filters.eq(key, value)).first();
		return dbObject2Map(obj);
	}

	@Override
	public Iterator<Map<String, String>> obtainLogIterator(final String collection) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		return iter(collection, coll.find().iterator());
	}

	@Override
	public Iterator<Map<String, String>> find(final String collection, final String key, final String value) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		return iter(collection, coll.find(Filters.eq(key, value)).iterator());
	}

	@Override
	public Iterator<Map<String, String>> find(final String collection, final Map<String, Object> criteria) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		return iter(collection, coll.find(new BasicDBObject(criteria)).iterator());
	}

	@Override
	public Iterator<Map<String, String>> findByDateRange(final String collection, final Date startDate, final Date endDate) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		Bson dateQuery = dateRangeQuery(startDate, endDate);
		// System.out.println(mongoQueryObject);
		return iter(collection, coll.find(dateQuery).iterator());
	}

	@Override
	public Iterator<Map<String, String>> findByDateRange(final String collection, final Date startDate, final Date endDate, final String key, final String value) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		final Bson dateQuery = dateRangeQuery(startDate, endDate);
		final BasicDBObject customQuery = new BasicDBObject(key, value);

		return iter(collection, coll.find(Filters.and(dateQuery, customQuery)).iterator());
	}

	@Override
	public Iterator<Map<String, String>> findByDateRange(final String collection, final Date startDate, final Date endDate, final Map<String, Object> criteria) {
		final MongoCollection<DBObject> coll = db.getCollection(collection, DBObject.class);
		final Bson dateQuery = dateRangeQuery(startDate, endDate);
		final BasicDBObject customQuery = new BasicDBObject(criteria);

		return iter(collection, coll.find(Filters.and(dateQuery, customQuery)).iterator());
	}

	private Bson dateRangeQuery(final Date startDate, final Date endDate) {
		Bson dateFilter = Filters.and(Filters.gte(LogMessage.LOG_DATE_FIELD, startDate.getTime()), Filters.lt(LogMessage.LOG_DATE_FIELD, endDate.getTime()));
		log.debug("Date filter created: " + dateFilter);
		//return new BasicDBObject(LogMessage.LOG_DATE_FIELD, BasicDBObjectBuilder.start("$gte", startDate.getTime()).append("$lt", endDate.getTime()).get());
		return dateFilter;
	}

	private Iterator<Map<String, String>> iter(final String collection, final MongoCursor<DBObject> cursor) {
		return new Iterator<Map<String, String>>() {

			@Override
			public boolean hasNext() {
				return cursor.hasNext();
			}

			@Override
			public Map<String, String> next() {
				return dbObject2Map(cursor.next());
			}

			@Override
			public void remove() {
				throw new RuntimeException("NOT IMPLEMENTED");
			}
		};
	}

	public MongoDatabase getDb() {
		return db;
	}

	@Required
	public void setDb(final MongoDatabase db) {
		this.db = db;
	}

	private Map<String, String> dbObject2Map(final DBObject obj) {
		final Map<String, String> res = Maps.newHashMap();
		if (obj != null) {
			for (String k : obj.keySet()) {
				res.put(k, "" + obj.get(k));
			}
		}
		return res;
	}

}
