import sys
import logging
from functools import wraps
import petl
from enum import StrEnum
from typing import Optional
from collections.abc import Callable
from pydantic import BaseModel, Field
from scim_filter_petl import compile_scim_filter
from logging.config import dictConfig
dictConfig(
    {
        "version": 1,
        "formatters": {
            "default": {
                "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
            }
        },
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout",
                "formatter": "default",
            }
        },
        "root": {"level": "DEBUG", "handlers": ["console"]},
    }
)
# logging.StreamHandler(sys.stdout)
logger = logging.getLogger(__name__)


class EnumSCIMAction(StrEnum):
    SCHEMA = 'schema' 
    USERS = 'users' 
    GROUPS = 'groups'
    USER_ROLES = 'user_roles'


class SCIMAction(BaseModel):
    getter: Callable


class App(BaseModel):
    actions: Optional[dict[EnumSCIMAction, SCIMAction]] = Field(default_factory=lambda: {})
    app_schema: Callable = Field(default=lambda request: {})


class SCIM_APPS_MODEL(BaseModel):
    apps: Optional[dict[str, App]] = Field(default_factory=lambda: {})

    def get_app_action(self, appname, action):
        if action == 'schema':
            return self.apps[appname].app_schema

        return self.apps[appname].actions[action].getter


# SCIM_APPS = {}
SCIM_APPS = SCIM_APPS_MODEL()

def scim_function(_func=None, app_label=None, action=None, **kwargs):

    def inner(func):
        
        nonlocal app_label

        @wraps(func)
        def wrapper(*args, **kwargs):
            """A wrapper function"""

            # Extend some capabilities of func
            

            return func(*args, **kwargs)

        # do something before the decorator is returned
        # HOOK_EVENTS[event]['hooks'].append(wrapper)
        

        SCIM_APPS.apps.setdefault(app_label, App())
        SCIM_APPS.apps[app_label].actions[action] = SCIMAction(getter=wrapper)

        return wrapper

    if _func is None:
        return inner
    else:
        return inner(_func)


def scim_schema(_func=None, app_label=None, **kwargs):

    def inner(func):
        
        nonlocal app_label

        @wraps(func)
        def wrapper(request, *args, **kwargs):
            """A wrapper function"""

            # Extend some capabilities of func
            print('in wrapper...', request)
            

            return func(*args, **kwargs)

        # do something before the decorator is returned
        # HOOK_EVENTS[event]['hooks'].append(wrapper)

        SCIM_APPS.apps.setdefault(app_label, App())
        SCIM_APPS.apps[app_label].app_schema = wrapper

        return wrapper

    if _func is None:
        return inner
    else:
        return inner(_func)


def scim_template(schemas=['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
                  totalResults=None,
                  startIndex=None,
                  itemsPerPage=None,
                  resources=[]):

    return {
        'schemas': schemas,
        'totalResults': totalResults,
        'startIndex': startIndex,
        'itemsPerPage': itemsPerPage,
        'Resources': resources 
    }


class SCIMBase:

    """Docstring for SCIMBase. """

    def __init__(self, app_name=None, scim_action=None, *args, **kwargs):
        """TODO: to be defined1. """
        self._app_name = app_name
        self._scim_action = scim_action

    def run_custom(self, request, page_params={}, filters=None, **kwargs):
        # this should handle the request, taking into account any flask request
        # query parameters
        # Run should return a list of results
        raise NotImplementedError()

    def convert_page_params(self, request):
        # raise NotImplementedError()

        return {}

    def get_filters(self, request):
        '''Filter the list of results by using the SCIM filter string. This is
        more complex than it sounds.
        '''
        # from scim2_filter_parser.queries import SQLQuery
        
        # some filters will be compatible with the underlying application,
        # others will not.

        return []

    def get_total_results(self, request, resources=None, filters=None, startIndex=None):
        '''This should get the total results that match the resource type and
        accounting for any added filters.

        If this is not defined, then we may consider returning the count of
        current resources in the query.'''
        # raise NotImplementedError()

        return len(resources)

    # def get_start_index(self, request):
    #     # raise NotImplementedError()

    #     return 0

    def get_start_index(self, request):
        '''
        SCIM pagination uses startIndex and count to do pagination. We may
        need to convert this to be compatible with the underlying connector
        API.
        '''

        return self.convert_page_params(request).get('startIndex', 0)

    # def get_items_per_page(self, request):
    #     raise NotImplementedError()

    def get_items_per_page(self, request):
        return self.convert_page_params(request).get('count', 100)

    def get_one(self, request, obj_id):
        raise NotImplementedError()

    def run(self, request):
        filters = self.get_filters(request)
        resources = self.run_custom(request=request,
                                    filters=filters,
                                    page_params=self.convert_page_params(request))

        # import petl
        stream = petl.fromdicts(resources)

        # Get the SCIm filter query. If defined, use it to filter the results
        # here. In the future, it might be good to pass this filter or some
        # conversion of it to the underlying connector. For now, we'll just
        # filter the raw results here.
        filter_query = request.args.get('filter', '')

        if filter_query:
            logger.debug('filter_query: %s', filter_query)
            filter_function_here = compile_scim_filter(filter_query)
            # logger.debug('filter_function_here: %s', filter_function_here)
            stream = stream.select(filter_function_here)
            # logger.debug('stream %s', stream)

        startIndex = self.get_start_index(request)
        resources = list(petl.dicts(stream))

        number_resources = self.get_total_results(request,
                                                  resources,
                                                  startIndex=startIndex)

        res = scim_template(totalResults=number_resources,
                            startIndex=startIndex,
                            itemsPerPage=self.get_items_per_page(request),
                            resources=resources)

        return res
