import logging, operator
from enum import Enum
from typing import Any, Dict, Iterable, List, Set, Tuple
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from .Exceptions import (
    UnsatisfiedFilterException, UnsupportedServiceHandlerClassException, UnsupportedFilterFieldException,
    UnsupportedFilterFieldValueException)
from .FilterFields import FILTER_FIELD_ALLOWED_VALUES, FilterFieldEnum

LOGGER = logging.getLogger(__name__)

class ServiceHandlerFactory:
    def __init__(self, service_handlers : List[Tuple[type, List[Dict[FilterFieldEnum, Any]]]]) -> None:
        # Dict{field_name => Dict{field_value => Set{ServiceHandler}}}
        self.__indices : Dict[str, Dict[str, Set[_ServiceHandler]]] = {}

        for service_handler_class,filter_field_sets in service_handlers:
            for filter_fields in filter_field_sets:
                filter_fields = {k.value:v for k,v in filter_fields.items()}
                self.register_service_handler_class(service_handler_class, **filter_fields)

    def register_service_handler_class(self, service_handler_class, **filter_fields):
        if not issubclass(service_handler_class, _ServiceHandler):
            raise UnsupportedServiceHandlerClassException(str(service_handler_class))

        service_handler_name = service_handler_class.__name__
        supported_filter_fields = set(FILTER_FIELD_ALLOWED_VALUES.keys())
        unsupported_filter_fields = set(filter_fields.keys()).difference(supported_filter_fields)
        if len(unsupported_filter_fields) > 0:
            raise UnsupportedFilterFieldException(
                unsupported_filter_fields, service_handler_class_name=service_handler_name)

        for field_name, field_values in filter_fields.items():
            field_indice = self.__indices.setdefault(field_name, dict())
            field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
            if not isinstance(field_values, Iterable) or isinstance(field_values, str):
                field_values = [field_values]
            for field_value in field_values:
                if isinstance(field_value, Enum): field_value = field_value.value
                if field_enum_values is not None and field_value not in field_enum_values:
                    raise UnsupportedFilterFieldValueException(
                        field_name, field_value, field_enum_values, service_handler_class_name=service_handler_name)
                field_indice_service_handlers = field_indice.setdefault(field_value, set())
                field_indice_service_handlers.add(service_handler_class)

    def get_service_handler_class(self, **filter_fields) -> _ServiceHandler:
        supported_filter_fields = set(FILTER_FIELD_ALLOWED_VALUES.keys())
        unsupported_filter_fields = set(filter_fields.keys()).difference(supported_filter_fields)
        if len(unsupported_filter_fields) > 0: raise UnsupportedFilterFieldException(unsupported_filter_fields)

        candidate_service_handler_classes : Dict[_ServiceHandler, int] = None # num. filter hits per service_handler
        for field_name, field_values in filter_fields.items():
            field_indice = self.__indices.get(field_name)
            if field_indice is None: continue
            if not isinstance(field_values, Iterable) or isinstance(field_values, str):
                field_values = [field_values]

            field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)

            field_candidate_service_handler_classes = set()
            for field_value in field_values:
                if field_enum_values is not None and field_value not in field_enum_values:
                    raise UnsupportedFilterFieldValueException(field_name, field_value, field_enum_values)
                field_indice_service_handlers = field_indice.get(field_value)
                if field_indice_service_handlers is None: continue
                field_candidate_service_handler_classes = field_candidate_service_handler_classes.union(
                    field_indice_service_handlers)

            if candidate_service_handler_classes is None:
                candidate_service_handler_classes = {k:1 for k in field_candidate_service_handler_classes}
            else:
                for candidate_service_handler_class in candidate_service_handler_classes:
                    if candidate_service_handler_class not in field_candidate_service_handler_classes: continue
                    candidate_service_handler_classes[candidate_service_handler_class] += 1

        if len(candidate_service_handler_classes) == 0: raise UnsatisfiedFilterException(filter_fields)
        candidate_service_handler_classes = sorted(
            candidate_service_handler_classes.items(), key=operator.itemgetter(1), reverse=True)
        return candidate_service_handler_classes[0][0]
