package org.gcube.application.geoportal.service.engine.mongo;


import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.gcube.application.cms.implementations.ImplementationProvider;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.model.rest.QueryRequest;
import org.gcube.application.geoportal.service.model.internal.db.Mongo;

import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Sorts.ascending;
import static com.mongodb.client.model.Sorts.descending;

@Slf4j
public abstract class MongoManager {


	protected Mongo client=null;

	protected MongoCollection<Document> collection=null;

	protected static final String ID="_id";
	
	protected static final ObjectId asId(String id) {return new ObjectId(id);}
	protected static final String asString(ObjectId id) {return id.toHexString();}
	
	protected static final String asString(Document d) {return d.toJson();}
	protected static final Document asDoc(String json) {return Document.parse(json);}
	
	public MongoManager() throws ConfigurationException {
		client=ImplementationProvider.get().getProvidedObjectByClass(Mongo.class);
		log.trace("Cached client is  {} ",client);
	}
	
	protected void init(String collectionName){
		String toUseDB=client.getConnection().getDatabase();
		log.info("Opening collection {} : {} ",toUseDB,collectionName);
		collection=client.getTheClient().getDatabase(toUseDB).getCollection(collectionName);
	}


	MongoCollection getCollection(){
		return collection;
	}

	protected abstract String mongoIDFieldName();

	//*********** PROJECTS
	// NB BsonId
	protected ObjectId insertDoc(Document proj) {
		MongoCollection<Document> collection = getCollection();
		// Check if _id is present
		ObjectId id=proj.getObjectId(mongoIDFieldName());
		if(id==null) {
			proj.append(mongoIDFieldName(), new ObjectId());
			id=proj.getObjectId(mongoIDFieldName());
		}
		collection.insertOne(Document.parse(proj.toJson()));
		return id;		
	}
	
	public void deleteDoc(ObjectId id) {
		MongoCollection<Document> collection = getCollection();
		collection.deleteOne(eq(mongoIDFieldName(),id));
	}
	
	
	
	public Document getDocById(ObjectId id,Document additionalFilter) {
		MongoCollection<Document> coll=getCollection();
		Document condition =new Document(mongoIDFieldName(),id);
		if(additionalFilter!=null)
			condition.putAll(additionalFilter);
		return coll.find(condition).first();
	}

	public Document getDocById(ObjectId id) {
		return getDocById(id,null);
	}


	public FindIterable<Document> iterateDoc(Document filter, Document projection) {
		MongoCollection<Document> coll=getCollection();
		if(filter == null) filter=new Document();
		log.trace("Applying Filter "+filter.toJson());
		if(projection != null ) {
			log.trace("Applying projection "+projection.toJson());
			return coll.find(filter).projection(projection);
		}else return coll.find(filter);
	}
	
	public FindIterable<Document> queryDoc(QueryRequest request){

		FindIterable<Document> toReturn=iterateDoc(request.getFilter(), request.getProjection());

		if(request.getOrdering()!=null){
			if(request.getOrdering().getDirection().equals(QueryRequest.OrderedRequest.Direction.ASCENDING))
				toReturn=toReturn.sort(ascending(request.getOrdering().getFields()));
			else toReturn=toReturn.sort(descending(request.getOrdering().getFields()));
		}

		//Paging
		if(request.getPaging()!=null){
			QueryRequest.PagedRequest paging=request.getPaging();
			toReturn=toReturn.skip(paging.getOffset()).limit(paging.getLimit());
		}

		return toReturn;
	}

//
//	public <T> FindIterable<T> iterateForClass(Document filter,Class<T> clazz) {
//		MongoCollection<Document> coll=getCollection();
//		if(filter==null)
//			return coll.find(clazz);
//		else
//			return coll.find(filter,clazz);
//	}
//
	public Document replaceDoc(Document toUpdate,ObjectId id) {
		MongoCollection<Document> coll=getCollection();
		return coll.findOneAndReplace(
				eq(mongoIDFieldName(),id), toUpdate,new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER));
		
	}
	
	public Document updateDoc(ObjectId id, Document updateSet) {
		MongoCollection<Document> coll=getCollection();
		return coll.findOneAndUpdate(
				eq(mongoIDFieldName(),id),
				updateSet,
				new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER));
	}
	

}
