package org.texttechnologylab.project.gruppe_05_1.database;

import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.WriteConcern;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.*;
import org.texttechnologylab.project.gruppe_05_1.exceptions.ServerErrorException;
import org.texttechnologylab.project.gruppe_05_1.exceptions.SessionNotFoundException;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.texttechnologylab.project.gruppe_05_1.database.domainimp.speeches.AgendaItem_MongoDB_Impl;
import org.texttechnologylab.project.gruppe_05_1.database.domainimp.speeches.Session_MongoDB_Impl;
import org.texttechnologylab.project.gruppe_05_1.database.domainimp.speeches.Speech_MongoDB_Impl;
import org.texttechnologylab.project.gruppe_05_1.util.Logger;
import org.texttechnologylab.project.gruppe_05_1.util.PPRUtils;
import org.texttechnologylab.project.gruppe_05_1.util.PropertiesUtils;
import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Impls.*;
import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.*;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.mongodb.client.model.Filters.eq;
import static org.texttechnologylab.project.gruppe_05_1.Main.MEMBER_IMAGES_DIR;

public class MongoDBHandler {

    public static final String propertiesFileName = "mongoDB.properties";
    public static final String DEFAULT_ID_FIELD_NAME = "_id";
    private final MongoClient mongoClient;
    private final MongoDatabase database;
    private static MongoDatabase mongoDatabase = null;

    public final static Class<? extends List> DOC_LIST_CLASS = new ArrayList<Document>().getClass();

    private static String localServer;       // z.B mongodb://localhost:27017
    private static String remoteServer;
    private static String user;
    private static String password;
    private static String port;
    private static String collection;
    private static String databaseName;

    private MongoCollection<Document> speakerCollection;
    private MongoCollection<Document> speechesCollection;
    private MongoCollection<Document> sessionsCollection;
    private MongoCollection<Document> agendaItemsCollection;
    private MongoCollection<Document> memberPhotoCollection;
    private MongoCollection<Document> historyCollection;


    public MongoDBHandler() {
        // Set loglevel for slf4j to avoid spam // TODO: Fix this (optional)
        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "error");
        Properties mongoProperties = PropertiesUtils.readPropertiesFromResource(propertiesFileName);
        // Zugangsdaten
        localServer = mongoProperties.getProperty("localserver");
        remoteServer = mongoProperties.getProperty("remote_host");
        user = mongoProperties.getProperty("remote_user");
        password = mongoProperties.getProperty("remote_password");
        port = mongoProperties.getProperty("remote_port");
        collection = mongoProperties.getProperty("remote_collection");
        databaseName = mongoProperties.getProperty("remote_database");

        // URI für lokale Datenbank oder für eine Datenbank auf dem Server
        String uri;
        if ( (localServer != null) && (! localServer.isBlank())) {
            uri = localServer;
            mongoClient = MongoClients.create(uri);

            // Connect
            database = mongoClient.getDatabase(databaseName);
        } else {
            MongoCredential credential = MongoCredential
                    .createCredential(
                            user,
                            databaseName,
                            password.toCharArray());

            MongoClientSettings settings = MongoClientSettings.builder()
                    .credential(credential)
                    .timeout(180, TimeUnit.HOURS) // needs increased timeout for the bulk speech inserts
                    .applyToClusterSettings(builder ->
                            builder.hosts(List.of(new ServerAddress(remoteServer, Integer.parseInt(port)))))
                    .build();

            mongoClient = MongoClients.create(settings);
            database = mongoClient.getDatabase(databaseName);
        }

        speakerCollection =  database.getCollection(MongoPprUtils.SPEAKER_COLLECTION_NAME);
        speechesCollection = database.getCollection(MongoPprUtils.SPEECH_COLLECTION_NAME);
        sessionsCollection = database.getCollection(MongoPprUtils.SESSION_COLLECTION_NAME);
        agendaItemsCollection = database.getCollection(MongoPprUtils.AGENDA_ITEMS_COLLECTION_NAME);
        memberPhotoCollection = database.getCollection(MongoPprUtils.PICTURES_COLLECTION_NAME);
        historyCollection = database.getCollection(MongoPprUtils.HISTORY_COLLECTION_NAME);

        createIndicesForSpeakerCollection();
        createIndicesForSpeechCollection();
        Logger.info("Connected to MongoDB database: " + databaseName);
    }

    public MongoDatabase getDatabase() {
        return this.database;
    }

    /**
     * Get the MongoDB according to properties.
     * If a local server URI is defined, use it. Otherwise, use remote server.
     * @return MongoDatabase
     */
    static public MongoDatabase getMongoDatabase() {

        if (mongoDatabase != null) {
            return mongoDatabase;
        }


        Properties mongoProperties = PropertiesUtils.readPropertiesFromResource(propertiesFileName);
        // Zugangsdaten
        localServer = mongoProperties.getProperty("localserver");
        remoteServer = mongoProperties.getProperty("remote_host");
        user = mongoProperties.getProperty("remote_user");
        password = mongoProperties.getProperty("remote_password");
        port = mongoProperties.getProperty("remote_port");
        collection = mongoProperties.getProperty("remote_collection");
        databaseName = mongoProperties.getProperty("remote_database");


        // MongoDBClient erzeugen
        // String uri = mongoServer + "://" + mongoUser + ":" + mongoPassword + "@" + mongoNeetwork;   // cluster, network, user...

        // URI für lokale Datenbank oder für eine Datenbank auf dem Server
        String uri;
        if ( (localServer != null) && (! localServer.isBlank())) {
            uri = localServer;
            MongoClient mongoClient = MongoClients.create(uri);

            // Connect
            MongoDatabase mongoDatabase = mongoClient.getDatabase(databaseName);

            return mongoDatabase;
        } else {
            MongoCredential credential = MongoCredential.createCredential(user, databaseName, password.toCharArray());

            // Build MongoClientSettings
            MongoClientSettings settings = MongoClientSettings.builder()
                    .applyToClusterSettings(builder ->
                            builder.hosts(Collections.singletonList(new ServerAddress(remoteServer,
                                    Integer.parseInt(port)))))
                    .credential(credential)
                    .build();

            // Create MongoClient
            MongoClient mongoClient = MongoClients.create(settings);
            mongoDatabase = mongoClient.getDatabase(databaseName);
        }

        return mongoDatabase;
    }

    /*
     * Collection Operations
     * =====================
     */

    /**
     *
     * @return List<String> with the names of all collections
     */
    public Set<String> getCollectionNames() {
        // return getDatabase().listCollectionNames().into(new ArrayList<>());
        return getDatabase().listCollectionNames().into(new HashSet<>());
    }

    /**
     *
     * @param name Name of collection to check for existance
     * @return does the collection exist
     */
    public boolean collectionExists(String name) {
        return getDatabase().listCollectionNames().into(new ArrayList<>()).contains(name);
    }


    /**
     * Tries to create a collection. If the collection exists and contains documents, throw an exception
     * @param database
     * @param collectionName
     */

    static public void createCollectionIfNotExist(MongoDatabase database, String collectionName) {
        if (collectionName.isBlank()) {
            throw new IllegalArgumentException("Collection name for MongoDB may not be blank");
        } else {
            MongoCollection<Document> collection = database.getCollection(collectionName);
            if (collection.countDocuments() > 0) {
                throw new IllegalArgumentException("Collection '" + collectionName + "' is not empty");
            } else {
                database.createCollection(collectionName);
            }

        }
    }

    public void createCollectionIfNotExist(String collectionName) {
        createCollectionIfNotExist(getDatabase(), collectionName);
    }


    /**
     * Create Collection
     * @param database
     * @param collectionName
     */
    static public void createCollection(MongoDatabase database, String collectionName) {
        if (collectionName.isBlank()) {
            throw new IllegalArgumentException("Collection name for MongoDB may not be blank");
        } else {
            database.createCollection(collectionName);
        }
    }

    public void createCollection(String collectionName) {
        createCollection(getDatabase(), collectionName);
    }


    /**
     * Creates a collection. If the collection exists already, delete all its document
     * @param database
     * @param collectionName
     */
    static public void createOrTrancateCollection(MongoDatabase database, String collectionName) {
        if (collectionName.isBlank()) {
            throw new IllegalArgumentException("Collection name for MongoDB may not be blank");
        } else {
            MongoCollection<Document> collection = database.getCollection(collectionName);
            if (collection.countDocuments() > 0) {
                collection.drop();
            }
            database.createCollection(collectionName);
        }
    }


    static public void createIndexForCollection(MongoCollection<Document> collection, String indexName, boolean isAscending) {
        // MongoDB creates automatically an index on "_id"
        if (indexName.equals(DEFAULT_ID_FIELD_NAME)) {
            return;
        }

        if (isAscending) {
            collection.createIndex(Indexes.ascending(indexName));
        } else {
            collection.createIndex(Indexes.descending(indexName));
        }
    }

    static public void createIndexForCollection(MongoCollection<Document> collection, List<String> indexNames, boolean isAscending) {
        // MongoDB creates automatically an index on "_id"
        if (indexNames.contains(DEFAULT_ID_FIELD_NAME)) {
            indexNames.remove(DEFAULT_ID_FIELD_NAME);
            if (indexNames.size() == 0) return;
        }

        if (isAscending) {
            collection.createIndex(Indexes.ascending(indexNames));
        } else {
            collection.createIndex(Indexes.descending(indexNames));
        }
    }

    public void createIndicesForSpeakerCollection() {
        if (speakerCollection.listIndexes().into(new ArrayList<>()).size() == 1) {
            MongoDBHandler.createIndexForCollection(speakerCollection,"name", true);
            MongoDBHandler.createIndexForCollection(speakerCollection,"firstName", true);
            MongoDBHandler.createIndexForCollection(speakerCollection,"party", true);
        }

    }
    public void createIndicesForSpeechCollection() {
        if (speechesCollection.listIndexes().into(new ArrayList<>()).size() == 1) {
            MongoDBHandler.createIndexForCollection(speechesCollection, "speakerId", true);
            MongoDBHandler.createIndexForCollection(speechesCollection, "speechKey", true);
        }
    }

    public void createOrTrancateCollection(String collectionName) {
        createOrTrancateCollection(database, collectionName);
    }

    /**
     * Does a document with a given ID (for the "_id"-field) exists in a given collection?
     * @param collection
     * @param id
     * @return
     */
    static public boolean existsDocument(MongoCollection<Document>  collection, String id) {
        ObjectId idToFind = new ObjectId(id);
        Document foundDocument = collection.find(new Document("_id", idToFind)).first();
        return (foundDocument != null);
    }


    /**
     * Find a document  with a given ID (for the "_id"-field) in a given collection
     * @param collection
     * @param id
     * @return the document (null if not found)
     */
    static public Document findDocumentByIdInCollection(MongoCollection<Document>  collection, String id) {
        ObjectId idToFind = new ObjectId(id);
        Document foundDocument = collection.find(new Document("_id", idToFind)).first();
        return foundDocument;
    }

    /**
     *
     * @param collection
     * @return count of documents in the collection
     */
    static public long size(MongoCollection<Document>  collection) {
        if (null == collection) {
            throw new IllegalArgumentException("Collection may not be null");
        } else {
            return collection.countDocuments();
        }
    }


    /**
     *
     * @param database
     * @param collectionName
     * @return count of documents in the collection
     */
    static public long size(MongoDatabase database, String collectionName) {
        if (collectionName.isBlank()) {
            throw new IllegalArgumentException("Collection name for MongoDB may not be blank");
        } else {
            return database.getCollection(collectionName).countDocuments();
        }
    }

    /**
     *
     * @param database
     * @param collectionName
     */
    static public void dropCollection(MongoDatabase database, String collectionName) {
        if (collectionName.isBlank()) {
            throw new IllegalArgumentException("Collection name for MongoDB may not be blank");
        } else {
            database.getCollection(collectionName).drop();
        }
    }


    /*
     * Document Operations
     * ===================
     */

    /**
     * Creates a BSON document containing only simple fields according to fields given in a map
     * @param attributes
     * @return
     */
    static public Document createDocument(boolean createIdField, Map<String, Object> attributes) {
        Document doc = new Document();

        if (createIdField) {
            doc.append("_id", new ObjectId());
        }

        for (Map.Entry<String, Object> entry : attributes.entrySet()) {
            doc.append(entry.getKey(), entry.getValue());
        }
        return doc;
    }

    /**
     * Creates a BSON document containing simple fields (attributes) as well as other (possibly nested) objects
     * @param attributes the simple fields
     * @param fields the (possibly nested) objects
     * @return
     */
    static public Document createDocument(boolean createIdField, Map<String, String> attributes, Map<String, Object> fields) {
        Document doc = new Document();

        if (createIdField) {
            doc.append("_id", new ObjectId());
        }

        if ( ! attributes.isEmpty()) {
            Map<String, Object> attributesMap = new HashMap<>();
            for (Map.Entry<String, String> entry : attributes.entrySet()) {
                attributesMap.put(entry.getKey(), entry.getValue());
            }
            Document attributeChild = createDocument(createIdField, attributesMap);
            doc.append("attributes", attributeChild);
        }

        for (Map.Entry<String, Object> entry : fields.entrySet()) {
            doc.append(entry.getKey(), entry.getValue());
        }
        return doc;
    }

    public static Double getFieldAsDouble(Document doc, String fieldName) {
        Object obj = doc.get(fieldName);
        if (obj instanceof Double) return (Double)  obj;
        if (obj instanceof Integer) return ((Integer) obj).doubleValue();
        if (obj instanceof String) return Double.valueOf((String) obj);
        Logger.error("Wert " + obj + " sollte Double sein, ist aber nicht");
        return (Double) obj;
    }

    /*
     * Weitere CRUD Operations
     * =======================
     */


    /**
     *
     * @param collection
     * @param doc
     * @return
     */
    static public void insertDocument(MongoCollection<Document> collection, Document doc) {
        collection.insertOne(doc);
    }

    /**
     *
     * @param collection
     * @param docs
     * @return
     */
    static public void insertDocuments(MongoCollection<Document> collection, List<Document> docs) {
        collection.insertMany(docs);
    }

    /**
     *
     * @param collection
     * @param fieldName
     * @param fieldValue
     * @return
     */
    static public Document findFirstDocumentInCollection(MongoCollection<Document>  collection, String fieldName, String fieldValue) {
        return collection.find(eq(fieldName, fieldValue)).first();
    }


    /**
     * Searches a document and performs an update on it
     * The document to update must be matched by name and value of a certain field
     * @param collection
     * @param searchCriteriaName search criteria: name of the field
     * @param searchCriteriaValue search criteria: value of the field
     * @param newOrUpdatedFieldName name of new field
     * @param newOrUpdatedFieldValue value of new field
     */
    static public void updateDocument(MongoCollection<Document>  collection,
                                      String searchCriteriaName, String searchCriteriaValue,
                                      String newOrUpdatedFieldName, Object newOrUpdatedFieldValue){

        Document updateQuery = new Document().append(searchCriteriaName, searchCriteriaValue);

        Bson updates = Updates.combine(
                Updates.addToSet(newOrUpdatedFieldName, newOrUpdatedFieldValue));

        collection.updateOne(updateQuery, updates);
    }


    /**
     *
     * @param collection
     * @param searchCriteriaName search criteria: name of the field
     * @param searchCriteriaValue search criteria: value of the field
     */
    static public void deleteOneDocument(MongoCollection<Document>  collection, String searchCriteriaName, String searchCriteriaValue) {
        Bson deleteQuery = eq(searchCriteriaName, searchCriteriaValue);

        collection.deleteOne(deleteQuery);
    }

    /*
     * Justus Jonas operations
     * =======================
     */

    public void insertSession(Session session) {
        Document sessionDocument = new Document("sessionId", session.getId())
                .append("dateTime", session.getDateTime())
                .append("endTime", session.getEndTime())
                .append("legislativePeriod", session.getLegislativePeriod());

        sessionsCollection.insertOne(sessionDocument);
    }

    public Session insertSession(String dateTime, String endTime, String legislativePeriod) {
        // get a new random sessionId that is not already in use
        int sessionId = 0;
        while (!retrieveAllSessions(Filters.eq("sessionId", sessionId)).isEmpty()) {
            // generate random int
            sessionId = (int) (Math.random() * Integer.MAX_VALUE);
        }

        // create session
        Session session = new Session_File_Impl(legislativePeriod, sessionId, dateTime, endTime);

        // insert session into DB
        insertSession(session);

        return session;
    }


    public void insertSessions(List<Session> sessions) {
        for (Session session : sessions) {
            insertSession(session);
        }
    }

    public void insertAgendaItems(List<AgendaItem> agendaItems) {
        List<Document> agendaItemDocuments = new ArrayList<>();
        for (AgendaItem agendaItem : agendaItems) {
            Document agendaItemDocument = new Document("id", agendaItem.getId())
                    .append("sessionId", agendaItem.getSessionId())
                    .append("title", agendaItem.getTitle());

            agendaItemDocuments.add(agendaItemDocument);
        }

        agendaItemsCollection.insertMany(agendaItemDocuments);

    }

    public AgendaItem insertAgendaItem(int sessionId, String title) throws SessionNotFoundException, ServerErrorException {
        // check if session exists
        List<Session> sessions = retrieveAllSessions(Filters.eq("sessionId", sessionId));
        if (sessions.isEmpty()) {
            Logger.error("No session found with id " + sessionId);
            throw new SessionNotFoundException();
        }
        // get a new random agendaItemId that is not already in use
        int agendaItemId = 0;
        while (!retrieveAllAgendaItems(Filters.eq("id", agendaItemId)).isEmpty()) {
            // generate random int
            agendaItemId = (int) (Math.random() * Integer.MAX_VALUE);
        }

        // create agendaItem
        AgendaItem agendaItem = new AgendaItem_File_Impl(agendaItemId, sessionId, title);

        // insert agendaItem into DB
        insertAgendaItems(List.of(agendaItem));

        return agendaItem;
    }

    public void insertSpeeches(List<Speech> speeches) {
        // Convert each Speech to a Document
        List<Document> speechDocuments = new ArrayList<>();
        for (Speech speech : speeches) {
            Document speechDocument = new Document("sessionId", speech.getSessionId())
                    .append("agendaItemId", speech.getAgendaItemId())
                    .append("speechId", speech.getSpeechId())
                    .append("speakerId", speech.getSpeakerId())
                    .append("speakerName", speech.getSpeakerName())
                    .append("fraction", speech.getFraction())
                    .append("speechKey", speech.getSpeechKey());

            // Convert speechContents to a list of Documents
            List<Document> contentDocuments = new ArrayList<>();
            for (Content content : speech.getSpeechContents()) {
                if (content instanceof Comment_File_Impl) {
                    Comment_File_Impl commentContent = (Comment_File_Impl) content;
                    contentDocuments.add(new Document("type", "comment")
                            .append("contentId", commentContent.getContentId())
                            .append("speechId", commentContent.getSpeechId())
                            .append("commentatorName", commentContent.getCommentatorName())
                            .append("comment", commentContent.getComment()));
                } else if (content instanceof Line_File_Impl) {
                    Line_File_Impl lineContent = (Line_File_Impl) content;
                    contentDocuments.add(new Document("type", "line")
                            .append("contentId", lineContent.getContentId())
                            .append("speechId", lineContent.getSpeechId())
                            .append("content", lineContent.getContent()));
                } else if (content instanceof Speaker_File_Impl) {
                    Speaker_File_Impl speakerContent = (Speaker_File_Impl) content;
                    contentDocuments.add(new Document("type", "speaker")
                            .append("contentId", speakerContent.getContentId())
                            .append("speechId", speakerContent.getSpeechId())
                            .append("speakerId", speakerContent.getSpeakerId())
                            .append("speakerName", speakerContent.getSpeakerName())
                            .append("fraction", speakerContent.getFraction()));
                }
            }

            // Add the speech contents to the speech document
            speechDocument.append("speechContents", contentDocuments);

            // Add the speech document to the list
            speechDocuments.add(speechDocument);
        }

        // Insert all documents at once using insertMany
        speechesCollection.insertMany(speechDocuments);
    }


    public List<Speech> retrieveAllSpeeches() {
        List<Document> speeches = speechesCollection.find().into(new ArrayList<>());
        List<Speech> result = new ArrayList<>();
        for (Document speech : speeches) {
            result.add(new Speech_MongoDB_Impl(speech, true));
        }

        return result;
    }

    public List<Speech> retrieveAllSpeeches(Bson filter) {
        List<Document> speeches = speechesCollection.find(filter).into(new ArrayList<>());
        List<Speech> result = new ArrayList<>();
        for (Document speech : speeches) {
            result.add(new Speech_MongoDB_Impl(speech, true));
        }

        return result;
    }

    public List<Session> retrieveAllSessions() {
        List<Document> sessions = sessionsCollection.find().into(new ArrayList<>());
        List<Session> result = new ArrayList<>();
        for (Document session : sessions) {
            result.add(new Session_MongoDB_Impl(session));
        }

        return result;
    }

    public List<Session> retrieveAllSessions(Bson filter) {
        List<Document> speeches = sessionsCollection.find(filter).into(new ArrayList<>());
        List<Session> result = new ArrayList<>();
        for (Document speech : speeches) {
            result.add(new Session_MongoDB_Impl(speech));
        }

        return result;
    }

    public List<AgendaItem> retrieveAllAgendaItems() {
        List<Document> agendaItems = agendaItemsCollection.find().into(new ArrayList<>());
        List<AgendaItem> result = new ArrayList<>();
        for (Document agendaItem : agendaItems) {
            result.add(new AgendaItem_MongoDB_Impl(agendaItem));
        }

        return result;
    }

    public List<AgendaItem> retrieveAllAgendaItems(Bson filter) {
        List<Document> speeches = agendaItemsCollection.find(filter).into(new ArrayList<>());
        List<AgendaItem> result = new ArrayList<>();
        for (Document speech : speeches) {
            result.add(new AgendaItem_MongoDB_Impl(speech));
        }

        return result;
    }

    public void updateXmiData(String speechKey, String xmiContent) {
        speechesCollection.updateOne(
                Filters.eq("speechKey", speechKey),
                Updates.set("xmiData", xmiContent)
        );
    }

    public void deleteSpeechRelatedDocuments() {
        speechesCollection.deleteMany(new Document());
        sessionsCollection.deleteMany(new Document());
        agendaItemsCollection.deleteMany(new Document());
        //historyCollection.deleteMany(new Document());
    }

    public void bulkWriteNlpData(List<WriteModel<Document>> bulkOperations) {
        if (!bulkOperations.isEmpty()) {
            BulkWriteOptions options = new BulkWriteOptions().ordered(false);
            // Optional: Setze einen weniger strengen Write Concern
            BulkWriteResult result = speechesCollection.bulkWrite(bulkOperations, options);
            int modifiedCount = result.getModifiedCount();
            int matchedCount = result.getMatchedCount();
            int upsertCount = result.getUpserts().size();
            Logger.info("Bulk write operation completed: "
                    + modifiedCount + " documents updated, "
                    + matchedCount + " documents matched, "
                    + upsertCount + " upserts.");
        } else {
            Logger.warn("No operations to write.");
        }
    }

    public long checkAnalysisResultsField() {
        return speechesCollection.countDocuments(Filters.exists("analysisResults"));
    }

    public String loadMemberImageFromFileById(String memberId) {
        // lookup the first and lastname of the memberId in the speaker collection in the DB
        Document speaker = speakerCollection.find(eq("speakerId", memberId)).first();
        if (speaker == null) {
            return null;
        }
        String name = speaker.getString("name");
        String firstName = speaker.getString("firstName");

        return loadMemberImageFromFileByName(firstName, name);
    }

    public String loadMemberImageFromFileByName(String firstName, String name) {
        // get the member photo from the resources/membersOfParliamentImages folder
        File photo = new File(MEMBER_IMAGES_DIR + name + "_" + firstName + ".jpg");

        // get the base64 encoded string from the photo
        String image_data;
        try {
            // get base64 string of the image photo.path()
            image_data = Base64.getEncoder().encodeToString(Files.readAllBytes(photo.toPath()));
        } catch (IOException e) {
            Logger.warn("Couldn't read file: " + e.getMessage());
            image_data = "";
        }

        return image_data;
    }

    public void uploadMemberPhoto(String memberId, String base64String) {
        if (memberPhotoCollection.find(eq("memberId", memberId)).first() != null) {
            Logger.warn("Member photo for " + memberId + " already exists in the database. Overwriting...");
            memberPhotoCollection.deleteOne(eq("memberId", memberId));
        }
        Document photoDocument = new Document("memberId", memberId)
                .append("base64", base64String);
        memberPhotoCollection.insertOne(photoDocument);
    }

    public void uploadMemberPhotos() {
        Logger.info("Found " + PPRUtils.listFilesInDirectory(MEMBER_IMAGES_DIR).size() + " member photos to upload.");
        // loop over file names in the directory
        // for each file name, extract the name of the member
        for (String filename : PPRUtils.listFilesInDirectory(MEMBER_IMAGES_DIR)) {
            String firstname = filename.split("_")[1].split(".jpg")[0];
            String name = filename.split("_")[0];

            Document speaker = speakerCollection.find(Filters.and(eq("firstName", firstname), eq("name", name))).first();
            if (speaker == null) {
                continue;
            }

            uploadMemberPhoto(speaker.getString("_id"), loadMemberImageFromFileByName(firstname, name));
        }
    }

    public boolean sessionExists(String sessionNumber) {
        Document filter = new Document("sessionId", Integer.valueOf(sessionNumber));
        long count = sessionsCollection.countDocuments(filter);
        return count > 0;
    }

    public String getMemberPhoto(String memberId) {
        Document photoDocument = memberPhotoCollection.find(eq("memberId", memberId)).first();
        if (photoDocument == null) {
            return null;
        }
        return photoDocument.getString("base64");
    }

    public void close() {
        mongoClient.close();
    }

}
