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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.mongodb.client.FindIterable;
import com.mongodb.client.model.FindOneAndReplaceOptions;
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.serialization.Serialization;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.application.geoportal.common.model.rest.QueryRequest;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor;
import org.gcube.application.geoportal.common.utils.ContextUtils;
import org.gcube.application.geoportal.service.model.internal.faults.RegistrationException;

import javax.ws.rs.WebApplicationException;
import java.util.NoSuchElementException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;

@Slf4j
public class UCDMongoManager extends MongoManager implements UCDManagerI{


    public UCDMongoManager() throws ConfigurationException {
        init("UCD_"+ContextUtils.getCurrentScope().replaceAll("/","_"));
    }

    public long deleteAll(){
        return getCollection().deleteMany(new Document()).getDeletedCount();
    }


    @Override
    protected String mongoIDFieldName() {
        return UseCaseDescriptor.MONGO_ID;
    }

    public UseCaseDescriptor insert(UseCaseDescriptor desc) throws RegistrationException {
        try {
            if (desc.getMongoId() != null)
                throw new RegistrationException("UCD has already a mongo ID");

            //TODO validate

            ObjectId id = insertDoc(Serialization.asDocument(desc));
            desc.setMongoId(id);
            return Serialization.convert(getDocById(id), UseCaseDescriptor.class);
        }catch(JsonProcessingException e){
            log.error("Unexpected serialization exception ",e);
            throw new WebApplicationException("Unexpected exception ",e);
        }
    }

    @Override
    public UseCaseDescriptor put(UseCaseDescriptor desc) throws RegistrationException, ConfigurationException {
        log.debug("PUT UCD ID {} MONGO ID {}",desc.getId(),desc.getMongoId());
        Document filter = new Document(UseCaseDescriptor.ID,desc.getId());
        if(desc.getMongoId()!=null)
            // MONGO ID SHOULD MATCH IF PROVIDED
            filter.put(UseCaseDescriptor.MONGO_ID,desc.getMongoId());
        try {
            UseCaseDescriptor toReturn = Serialization.convert(getCollection().findOneAndReplace(
                    filter, Serialization.asDocument(desc),
                    new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)), UseCaseDescriptor.class);
            if(toReturn == null) {
                if (desc.getMongoId() != null)
                    throw new RegistrationException("Illegal attempt to write to " + desc.getId() + " with unmatching mongo ID ");
                else {
                    log.debug("Unable to update UCD {}. Inserting it..",desc.getId());
                    toReturn = insert(desc);
                }
            }

            log.info("Updated UCD in DB cache. ID {}, MONGO ID {}",toReturn.getId(),toReturn.getMongoId());
            log.trace("Updated UCD is {} ", toReturn);
            return toReturn;
        }catch (RegistrationException e){
            throw e;
        }catch(Throwable e){
            log.error("Unable to update ",e);
            throw new RegistrationException("Invalid UCD provided "+e.getMessage());
        }
    }

    @Override
    public Iterable<UseCaseDescriptor> query(QueryRequest queryRequest) {

        log.info("Searching UCD for {} ",queryRequest);
        LinkedBlockingQueue queue=new LinkedBlockingQueue<UseCaseDescriptor>();

        FindIterable<Document> it=queryDoc(queryRequest);
        it.forEach(
                (Consumer<? super Document>) d ->{try{
                    queue.put(Serialization.convert(d,UseCaseDescriptor.class));
                }catch(Throwable t){log.warn("Unable to translate "+d,t);}});

        log.info("Returned {} elements ",queue.size());
        return queue;
    }

    @Override
    public void deleteById(String id, boolean force) throws ConfigurationException {
        deleteDoc(getById(id).getMongoId());
    }


    @Override
    public UseCaseDescriptor getById(String id) throws ConfigurationException {
        log.info("Getting UC by ID {} ",id);
        QueryRequest request=new QueryRequest();
        request.setFilter(new Document(UseCaseDescriptor.ID,id));
        try {
            return this.query(request).iterator().next();
        }catch (NoSuchElementException e){return null;}
    }
}
