from typing import Type
from urllib import request
from django.http import HttpRequest, HttpResponse
from django.db.models.query import QuerySet, ModelIterable
from django.db.models.base import ModelState
from inspect import signature, isclass

class Thing():
    '''Determine the real type of a thing

    A thing can be one of four types: 
    ['queryset', 'view', 'function', 'django_cbv', 'serializer']
    '''

    def __init__(self, thing, execution_dict, *args, **kwargs):
        self.thing = thing
        self.kwargs = kwargs
        self.execution_dict = execution_dict

        self.thing_type = self.find_thing_type(thing)
        self.executor = self.find_executor_type(self.thing_type)

    def find_thing_type(self, thing) -> str:
        if type(thing) == QuerySet:
            thing_type = 'queryset'
        elif isclass(thing):
            if self.check_django_class_based_view(thing):
                thing_type = 'django_cbv'
            elif self.check_serializer(thing):
                thing_type = 'serializer'
            else:
                raise TypeError('Invalid Class')
        elif callable(thing): # all classes are callable but not all callables are classes
            if self.check_django_view(thing):
                thing_type = 'view'
            else:
                thing_type = 'function'
        elif self.check_model_instance(thing):
            thing_type = 'model_instance'
        else:
            raise TypeError('Invalid Thing. Valid things are querysets, django class based views, serializers, django views, functions, and model instances.')
        return thing_type

    def get_request_or_create(self) -> HttpRequest:
        request = self.execution_dict['request']
        if request == None:
            request = HttpRequest()
        return request

    def check_model_instance(self, thing):
        try:
            state = thing.__dict__['_state']
            if type(state) == ModelState:
                return True
            else:
                return False
        except (KeyError, AttributeError):
            return False

    def check_django_view(self, thing):
        parameters = list(signature(thing).parameters)
        if 'request' not in parameters:
            return False
        
        request = self.get_request_or_create()
        response = thing(request)
        if type(response) == HttpResponse:
            return True
        else:
            return False

    def check_django_class_based_view(self, thing):
        valid_cbvs = [
            "View",
            "TemplateView",
            "RedirectView",
            "ArchiveIndexView",
            "YearArchiveView",
            "MonthArchiveView",
            "WeekArchiveView",
            "DayArchiveView",
            "TodayArchiveView",
            "DateDetailView",
            "DetailView",
            "ListView",
            "GenericViewError",
        ]
        mro = thing.__mro__
        intersection = [x for x in mro if any(cbv in str(x) for cbv in valid_cbvs)]
        if intersection:
            return True
        else:
            return False

    def check_serializer(self, thing):
        mro = thing.__mro__
        valid_parent_class = 'rest_framework.serializers.ModelSerializer'
        if any(valid_parent_class in str(parent_class) for parent_class in mro):
            return True
        else:
            return False

    def find_executor_type(self, thing_type):
        if thing_type == 'queryset':
            return self.execute_queryset
        elif thing_type == 'view':
            return self.execute_view
        elif thing_type == 'function':
            return self.execute_function
        elif thing_type == 'django_cbv':
            return self.execute_django_cbv
        elif thing_type == 'serializer':
            return self.execute_serializer
        elif thing_type == 'model_instance':
            return self.execute_model_instance
        else:
            raise TypeError(f'Invalid Type: {thing_type}')

    def execute_queryset(self, thing):
        '''Run a Queryset
        
        when list() is called on a queryset the queryset's _result_cache
        is checked first. By calling list on the an instance of ModelIterable
        we can be sure that the queries are actually being run. 
        '''
        try:
            list(ModelIterable(thing))
        except Exception as e:
            raise e

    def execute_view(self, thing):
        request = self.get_request_or_create()
        try:
            thing(request)
        except Exception as e:
            raise e

    def execute_function(self, thing):
        try:
            thing()
        except Exception as e:
            raise e

    def execute_django_cbv(self, thing):
        request = self.get_request_or_create()
        request.method = 'GET'
        try:
            response = thing.as_view()(request)
            response.render()
        except Exception as e:
            raise e

    def execute_serializer(self, thing):
        model = thing.Meta.model
        queryset = model.objects.all()
        many = True if len(queryset) > 1 else False
        try:
            serializer_execution = thing(instance=queryset, many=many)
            serializer_execution.data
        except Exception as e:
            raise e

    def execute_model_instance(self, thing):
        try:
            thing.refresh_from_db()
        except Exception as e:
            raise e

    def execute_thing(self):
        self.executor(self.thing)