import logging
from dataclasses import dataclass, field
from arango.database import StandardDatabase

from cag import logger
from cag.utils import utils
from arango import AnalyzerGetError
from nltk.corpus import stopwords
from typing import List, ClassVar


@dataclass
class EdgeNGram:
    min: int = 2
    max: int = 4
    preserve_original: bool = True

    def summary(self):
        result = utils.camel_nest_dict(utils.filter_dic(self))
        return result


@dataclass
class ArangoAnalyzer():
    #####################
    ### Instance Vars ###
    #####################
    name: str
    type:str = "identity"
    features: List = field(default_factory=lambda: ["frequency", "norm", "position"])

    ## Properties - loaded based on type
    # TEXT
    locale: str = "en"  # text, stem, norm
    case: str = "lower"  # text,  norm
    stopwords: List = field(default_factory=lambda: [])
    accent: bool = False  # text,  norm
    stemming: bool = True
    edge_ngram: "EdgeNGram | None" = None  #
    # type: delimiter
    delimiter: str = ','

    # type ngram
    min: int = 2
    max: int= 5
    preserve_original: bool = False
    start_marker: str = ""
    end_marker: str = ""
    stem_type: str = field(default_factory=lambda:ArangoAnalyzer._STEM_TYPE_BINARY)


    #################
    ### CONSTANTS ###
    #################

    ######################################
    ### Main types of arango analyzer ###
    #####################################
    _TYPE_IDENTITY: ClassVar = "identity"
    _TYPE_TEXT: ClassVar = "text"
    _TYPE_NGRAM: ClassVar ="ngram"
    _TYPE_STEM: ClassVar = "stem"
    _TYPE_NORM: ClassVar = "norm"
    _TYPE_DELIMITER: ClassVar ="delimiter"

    # For type Stem - there are 2 stem types
    _STEM_TYPE_BINARY: ClassVar = "binary"
    _STEM_TYPE_UTF8: ClassVar = "utf8"

    _MANDATORY_FIELDS: ClassVar = ['name', 'type', 'features']

    FIELDS: ClassVar = {
        _TYPE_IDENTITY : [],
        _TYPE_TEXT: ['locale', 'case', 'stopwords', 'accent', 'stemming', 'edge_ngram'],
        _TYPE_NGRAM: ['min', 'max', 'preserve_original',
                      'start_marker', 'end_marker', 'stem_type'],
        _TYPE_STEM: ['locale'],
        _TYPE_DELIMITER: ['delimiter'],
    }

    #############
    ## Setters ##
    #############
    def set_features(self, frequency= True, norm=True, position = True):
        '''
        Sets which features to return - It is set to have all three features: frequency, norm and position
        :param frequency: bool
        :param norm: bool
        :param position: bool
        :return:
        '''
        features = []
        if frequency: features.append("frequency")
        if norm: features.append("norm")
        if position: features.append("position")
        self.features = features

    def set_edge_ngrams(self, min =2, max = 5, preserve_original = False):
        self.edge_ngram =  EdgeNGram(min =min, max = max, preserve_original = preserve_original)


    def set_stopwords(self, language = "english", custom_stopwords= [], include_default = True):
        logger.info('The default stopword list is loaded from NLTK - if you get an error, run `nltk.download('
                     '"stopwords")`')
        if include_default:
            self.stopwords = stopwords.words(language)

        if len(custom_stopwords) > 0:
            self.stopwords.extend(custom_stopwords)


    #############
    ## Getters ##
    #############
    def get_type_fields(self) -> List:
        '''
        Gets the list of fields needed and set for the Analyzer based on the Type

        :return: list of fields
        '''
        return ArangoAnalyzer.FIELDS[self.type]

    def get_properties(self) -> dict:
        keep = ArangoAnalyzer.FIELDS[self.type]
        result = utils.camel_nest_dict(utils.filter_dic(self, keep))
        return result

    def summary(self) -> dict:
        keep = ArangoAnalyzer._MANDATORY_FIELDS + ArangoAnalyzer.FIELDS[self.type]
        result = utils.camel_nest_dict(utils.filter_dic(self, keep))
        return result


    def create(self, database: StandardDatabase):
        result = {}
        try:
            info = database.analyzer(self.name)
            logger.error("Analyzer {} already exists with the following info: {}".format(self.name, info))
            logger.error("You can delete it first using *database.delete_analyzer({})* and that create".format(self.name))
        except AnalyzerGetError:
            result = database.create_analyzer(self.name,
                                              self.type,
                                              self.get_properties(),
                                              self.features)
            logger.info("Analyzer was created!")
        return result



@dataclass
class AnalyzerList:
    analyzerList: List = field(default_factory=lambda:[])  # ["text_en"]
    database: StandardDatabase = None

    def get_invalid_analyzers(self, database: StandardDatabase, verbose=0):
        invalid_analyzer = []
        for analyzer in self.analyzerList:
            try:
                info = database.analyzer(analyzer)
                if verbose == 1:
                    logger.info(info)
            except AnalyzerGetError:
                invalid_analyzer.append(analyzer)
        return invalid_analyzer

    def filter_invalid_analyzers(self, database: StandardDatabase, verbose:int=0):
        invalid_analyzer = self.get_invalid_analyzers(database)
        if verbose == 1:
            if len(invalid_analyzer) > 0 :
                logger.warning("Filtered out the following invalid analyzers: "+ str(invalid_analyzer))
        self.analyzerList = [a for a in self.analyzerList if a not in invalid_analyzer]


    def add_analyzer(self, analyzer_name:str, force_add = False):
        '''
        Adds the analyzer name to a list. If force_add =True then it adds is even if this analyzer does not exist in the database
        '''
        try:
            if self.database is not None:
                _ = self.database.analyzer(analyzer_name)
            else:
                print("couldnt validate analyzer because database connection is not set.")
            self.analyzerList.append(analyzer_name)
        except AnalyzerGetError:
            print("Invalid Analyzer")
            if force_add:
                self.analyzerList.append(analyzer_name)

