package br.ufrgs.inf.prosoft.tigris.monitoring.storage.providers;

import br.ufrgs.inf.prosoft.tigris.exceptions.StorageException;
import br.ufrgs.inf.prosoft.tigris.monitoring.storage.Repository;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * The type Mongo repository.
 *
 * @param <T> the type parameter
 */
public class MongoRepository<T> implements Repository<T> {

    /**
     * The Type parameter class.
     */
    final Class<T> typeParameterClass;
    private final ObjectMapper mapper;
    private final MongoCollection<Document> dbCollection;
    /**
     * The Logger.
     */
    Logger logger = LoggerFactory.getLogger(MongoRepository.class);

    /**
     * Instantiates a new Mongo repository.
     *
     * @param dbCollection       the db collection
     * @param typeParameterClass the type parameter class
     */
    public MongoRepository(MongoCollection<Document> dbCollection, Class<T> typeParameterClass) {
        this.dbCollection = dbCollection;
        this.typeParameterClass = typeParameterClass;
        this.mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//        mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        //Registering Hibernate4Module to support lazy objects
//        Hibernate4Module hibernate4Module = new Hibernate4Module();
//        hibernate4Module.enable(Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
//        objectMapper.registerModule(hibernate4Module);

//        mapper.registerModule(new Hibernate4Module());
        // or, for Hibernate 5.x
//        mapper.registerModule(new Hibernate5Module());
// or, for Hibernate 3.6
//        mapper.registerModule(new Hibernate3Module());
    }

    @Override
    public void save(T o) throws StorageException {
        try {
            //TODO hash the return value and arguments
            Document parse = Document.parse(mapper.writeValueAsString(o));
            dbCollection.insertOne(parse);
//            dbCollection.insert((DBObject) Boon.fromJson(trace));
        } catch (Exception e) {
            throw new StorageException("Json serialization fail", e);
        }
    }

    @Override
    public List<T> findAll() {
        List<T> logList = new ArrayList<>();

        MongoCursor<Document> cursor = dbCollection.find().iterator();
        try {
            while (cursor.hasNext()) {
                try {
                    logList.add((T) mapper.readValue(mapper.writeValueAsString(cursor.next()), typeParameterClass));
                } catch (Exception e) {
                    throw new StorageException("Cannot convert data from database, serialization fail", e);
                }
            }
        } finally {
            cursor.close();
        }

        return logList;
    }

    @Override
    public void removeAll() {
        dbCollection.drop();
    }

    @Override
    public void saveAll(List<T> toSave) {
//        for (T trace : toSave) {
//            try {
//                save(trace);
//                logger.debug("New trace entry: " + trace);
//            } catch (StorageException e) {
//                //TODO few calls that are not traced is not a problem, usually it is because of the serialization to the repository (mongo)
//                //usually data object will be correctly traced. Beans, sevlets and other api-related objects will not
//                TracerAspect.methodBlackList.add(((LogTrace) trace).getMethodInfo().getSignature());
//                logger.debug("Couldn't trace " + ((LogTrace) trace).getMethodInfo().getSignature() + " due to: " + e.getMessage(), e);
//            }
//        }
    }

    @Override
    public String toString() {
        return "MongoRepository {" +
                dbCollection.toString() +
                '}';
    }
}
