import pymongo
import network_constants as netconst
from database.MongoRepositoryBase import MongoRepositoryBase
import json

from db.entities import *
# from processing.similarityFiles.miscFunctions import *
from typing import List

import logging
LOGGER = logging.getLogger(__name__)


class Repository(MongoRepositoryBase):
    '''This is a repository for MongoDb.'''

    def __init__(self):
        super().__init__(netconst.ROLESTAGE_DISCOVERY_DB_HOSTNAME,
                         netconst.ROLESTAGE_DISCOVERY_DB_PORT,
                         'roleStageDb')

        self._layer_collection = 'layers'
        self._layer_nodes_collection = 'layer_nodes'
        self._clusters_collection = 'clusters'
        self._time_slice_collection = 'time_slices'
        self._connected_clusters_collection ='connected_clusters'
        self._similarity_collection = 'similarity'
        self._connected_run = 'connected_run'


#region Layers
    def add_layer(self, layer: Layer):
        super().insert_entry(self._layer_collection, layer.to_serializable_dict())

    def get_layers(self) -> List[Layer]:
        '''Retrieves all layers from the db, independent of use-case.'''
        entries = super().get_entries(self._layer_collection, projection={'_id': 0})
        return [Layer(e) for e in entries]

    def get_layers_for_use_case(self, use_case: str) -> Layer:
        entries = super().get_entries(self._layer_collection, selection={'use_case': use_case})
        return [Layer(e) for e in entries]

    def get_layers_for_table(self, use_case: str, use_case_table: str) -> Layer:
        entries = super().get_entries(self._layer_collection, selection={'use_case': use_case, 'use_case_table': use_case_table})
        return [Layer(e) for e in entries]

    def get_layer_by_name(self, use_case:str, use_case_table:str, layer_name:str) -> Layer:
        '''Returns a singe layer for use-case and layer-name.'''
        entries = super().get_entries(self._layer_collection, selection={'use_case': use_case, 'use_case_table': use_case_table, 'layer_name': layer_name})
        entries = [Layer(e) for e in entries]

        if entries is not None and len(entries) > 0:
            if len(entries) > 1:
                LOGGER.error(f"Layer Key {use_case}, {layer_name} is not unique.")
            return entries[0]
        else:
            return None
    
    def delete_all_layers(self):
        super().drop_collection(self._layer_collection)
    
#endregion Layers

#region Layer Nodes

    def delete_all_nodes(self):
        super().drop_collection(self._layer_nodes_collection)
    
    def add_layer_node(self, node: dict):
        super().insert_entry(self._layer_nodes_collection, node)

    def add_layer_nodes(self, nodes:List[dict]):
        super().insert_many(self._layer_nodes_collection, nodes)

    def get_layer_nodes(self, use_case: str, use_case_table: str, layer_name: str) -> dict:
        '''Returns all nodes for the use-case and layer.'''
        entries = super().get_entries(self._layer_nodes_collection, selection={'use_case': use_case, 'use_case_table': use_case_table, 'layer_name': layer_name}, projection={'_id': 0})
        return [e for e in entries]

#endregion

#region Clusters
    def add_cluster(self, cluster: Cluster):
        super().insert_entry(self._clusters_collection, cluster.to_serializable_dict(for_db=True))
        
    def add_clusters(self, clusters: List[Cluster]):
        cluster_dicts = [c.to_serializable_dict(for_db=True) for c in clusters]
        super().insert_many(self._clusters_collection, cluster_dicts)
        
    def get_clusters_for_layer(self, use_case: str, use_case_table: str, layer_name: str) -> List[Cluster]:
        entries = super().get_entries(self._clusters_collection, selection={'use_case': use_case, 'use_case_table': use_case_table, 'layer_name': layer_name}, projection={'_id': 0})
        return [Cluster(cluster_dict=e, from_db=True) for e in entries]
    
    def delete_all_clusters(self):
        super().drop_collection(self._clusters_collection)

#endregion

#region TimeSlice
    def add_time_slice(self, timeslice: TimeSlice):
        super().insert_entry(self._time_slice_collection, timeslice.to_serializable_dict(for_db=True))

    def get_time_slices(self) -> List[TimeSlice]:
        '''Returns all time slices.'''
        entries = super().get_entries(self._time_slice_collection)
        return [TimeSlice(None, None, time_slice_dict=e, from_db=True) for e in entries]

    def get_time_slices_by_name(self, use_case: str, use_case_table: str, layer_name: str) -> List[TimeSlice]:
        '''Returns all time slices with the given layer_name.'''
        entries = super().get_entries(self._time_slice_collection, selection={'use_case': use_case, 'use_case_table': use_case_table, 'layer_name': layer_name})
        return [TimeSlice(time_slice_dict=e, from_db=True) for e in entries]

    def remove_all_time_slices(self):
        super().drop_collection(self._time_slice_collection)

#endregion

#region clusterConnected
    def add_connected_cluster(self, clusterDict):
        super().insert_entry(self._connected_clusters_collection, clusterDict)

    def add_connected_clusters(self, clusterDictArray):
        ''' Add Connected Clusters Data to DB '''
        result = super().insert_many(self._connected_clusters_collection, clusterDictArray)
        return result
        
    def get_connected_clusters(self, run_id: str=None):#, layer_name: str):
        ''' Get Connected Clusters Data from DB '''
        if (run_id == None):
            entries = super().get_entries(self._connected_clusters_collection, projection={'_id': 0})
        else:
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_runId' : run_id}, projection={'_id': 0})
        
        output = []
        for ent in entries:
            output.append(ent)
        return output
        # print(ent)
        #return [Cluster(cluster_dict=e, from_db=True) for e in entries]

    def get_connected_clusters_for_use_case(self,use_case, run_id: str=None):#, layer_name: str):
        ''' Get Connected Clusters Data given the Use-Case from DB '''
        if (run_id == None):
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_use_case': use_case}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_runId' : run_id, 'cluster_use_case': use_case}, projection={'_id': 0})
        
        output = []
        for ent in entries:
            output.append(ent)
        return output
        # print(ent)
        #return [Cluster(cluster_dict=e, from_db=True) for e in entries]

    def get_connected_clusters_for_table(self,use_case,table, run_id: str=None):#, layer_name: str):
        ''' Get Connected Clusters Data given the Use-Case and Table from DB '''
        if (run_id == None):
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_use_case': use_case,'cluster_table': table}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_runId' : run_id,'cluster_use_case': use_case,'cluster_table': table}, projection={'_id': 0})
        
        output = []
        for ent in entries:
            output.append(ent)
        return output
        # print(ent)
        #return [Cluster(cluster_dict=e, from_db=True) for e in entries]

    def get_connected_clusters_by_name(self,use_case, table, layer_name, run_id: str=None):#, layer_name: str):
        ''' Get Connected Clusters Data from DB '''
        if (run_id == None):
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_use_case': use_case,'cluster_table': table, 'cluster_layer' : layer_name}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._connected_clusters_collection, selection={'cluster_runId' : run_id,'cluster_use_case': use_case,'cluster_table': table, 'cluster_layer' : layer_name}, projection={'_id': 0})
        
        output = []
        for ent in entries:
            output.append(ent)
        return output
        # print(ent)
        #return [Cluster(cluster_dict=e, from_db=True) for e in entries]

#endregion

#region similarity
    def add_single_similarity(self, input_):
        super().insert_entry(self._similarity_collection, input_)

    def add_similarity(self, inputDict):
        ''' Add Similarity Data to DB '''
        #checkIfConnClustDictIsSerializable(outputJSON)      
        result = super().insert_many(self._similarity_collection, inputDict)
        #print(str(result))
        #super().insert_entry(self._connected_clusters_collection, outputJSON)
        return result
    
    def get_similarity(self,skipNr,batchSize, cluster_layer: str= None, run_id: str=None):
        ''' Get Similarity Data from DB '''
        
        if (run_id == None):
            if(cluster_layer == None):
                entries = super().get_entries(self._similarity_collection, projection={'_id': 0})
            else:
                entries = super().get_entries(self._similarity_collection, selection={'cluster_layer' : cluster_layer}, projection={'_id': 0})
        else:
            if(cluster_layer == None):
                entries = super().get_entries(self._similarity_collection, selection={'runId' : run_id}, projection={'_id': 0})
            else:
                entries = super().get_entries(self._similarity_collection, selection={'cluster_layer' : cluster_layer, 'runId' : run_id}, projection={'_id': 0})
            
        #
        return list(entries.sort([('_id', -1)]).skip(skipNr).limit(batchSize))
        """
        output = []
        for e in entries:
            output.append(e)
        return output
        """
    def get_similarity_use_case(self,skipNr,batchSize,use_case, run_id: str=None):
        ''' Get Similarity Data from DB '''
        
        if (run_id == None):
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case, 'runId' : run_id}, projection={'_id': 0})
            
        #
        return list(entries.sort([('_id', -1)]).skip(skipNr).limit(batchSize))
    
    def get_similarity_table(self,skipNr,batchSize,use_case,table, run_id: str=None):
        ''' Get Similarity Data from DB '''
        
        if (run_id == None):
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case, 'table': table}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case, 'table': table, 'runId' : run_id}, projection={'_id': 0})
            
        #
        return list(entries.sort([('_id', -1)]).skip(skipNr).limit(batchSize))

    def get_similarity_layer(self,skipNr,batchSize,use_case,table,layer, run_id: str=None):
        ''' Get Similarity Data from DB '''
        
        if (run_id == None):
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case, 'table': table, 'cluster_layer' : layer}, projection={'_id': 0})
        else:
            entries = super().get_entries(self._similarity_collection, selection={'use_case' : use_case, 'table': table, 'cluster_layer' : layer, 'runId' : run_id}, projection={'_id': 0})
            
        #
        return list(entries.sort([('_id', -1)]).skip(skipNr).limit(batchSize))
#endregion

#region connected_run

    def convert_run_to_json_support(self, run_from_db: dict) -> dict:
        ''' Converts the ObjectId from MongoDb to its string repr. '''
        run_from_db['_id'] = str(run_from_db['_id'])
        return run_from_db

    def add_connected_run(self, conRunTimestamp):
        ''' Add Connected Run Data to DB '''
        result = super().insert_entry(self._connected_run, conRunTimestamp)
        return result

    def get_connected_run(self, run_id: str= None):
        ''' Get Connected Run Data from DB '''
        if (run_id == None):
            entries = super().get_entries(self._connected_run)
        else:
            entries = super().get_entries(self._connected_run, selection={'_id' : run_id}, projection={'_id': 1, 'Datetime': 1})

        output = []
        for e in entries:
            output.append(self.convert_run_to_json_support(e))
        return output


#endregion