from typing import List, Union
from django.db.models import Model
from balder.types.subscription.meta import BalderSubscriptionMeta
from balder.types.utils import classToString

try:
    import channels_graphql_ws

    
    class BaseSubscription(channels_graphql_ws.Subscription):

        @classmethod
        def subscribe(cls, root, info, *args, **kwargs):
            raise NotImplementedError("Should never be called directly")

        @classmethod
        def publish(cls, payload, info, *args, **kwargs):
            raise NotImplementedError("Should never be called directly")


except ImportError as e:

    BaseSubscription = None
    
import graphene


class IllConfigured(Exception):
    pass

class NoMutateException(Exception):
    pass


class BalderSubscription(metaclass=BalderSubscriptionMeta):
    Arguments = None # Should be overwriten by the actual mutation


    class Meta:
        abstract = True


    @classmethod
    def _get_description(cls) -> str:
        return cls.__doc__

    @classmethod
    def _get_publish_or_none(cls) -> str:
        if hasattr(cls, "publish"):
            return cls.publish
        else:
            return None

    @classmethod
    def _get_subscribe_or_none(cls) -> str:
        if hasattr(cls, "subscribe"):
            return cls.subscribe
        else:
            return None

    @classmethod
    def _get_operation(cls) -> str:
        """Gets the opereration name (string literal in query)

        Returns:
            str: the operation name
        """
        if cls._meta.operation is not None: return cls._meta.operation
        return classToString(cls)

    @classmethod
    def broadcast(cls, payload: Union[dict, str, Model], groups: List[str]):
        if cls._meta.serializer is not None:
            payload = cls._meta.serializer.pack(payload)
        for group in groups:
            cls.subscription.broadcast(payload=payload, group=group)

    @classmethod
    async def broadcast_async(cls, payload: Union[dict, str, Model], groups: List[str]):
        if cls._meta.serializer is not None:
            payload = cls._meta.serializer.pack(payload)
        for group in groups:
            await cls.subscription.broadcast(payload=payload, group=group)



    @classmethod
    def _to_field(cls) -> graphene.Field:
        meta = cls._meta
        assert meta.type is not None, f"Please provide a Meta class with at least the attribute type in {cls.__name__}"
        assert cls.Arguments is not None, f'Please provide a Arguments class (can just pass in body if no arguments are needed) in  {cls.__name__}'

        subscribe_function = cls._get_subscribe_or_none()
        assert subscribe_function is not None, f"Please provide a subscribe function in  {cls.__name__}"


        publish_function = cls._get_publish_or_none()
        if publish_function is None:
            assert meta.serializer is not None, "If you omit publish please provide a Meta field serializer in {cls.__name__}"
            publish_function =  lambda payload, info, *args, **kwargs: meta.serializer.unpack(payload, basemodel=meta.model)

        cls.subscription = type("Subscription", (BaseSubscription,), {"Arguments": cls.Arguments, "Output": meta.type, "subscribe": subscribe_function, "publish": publish_function, "__doc__": cls._get_description()})
        return cls.subscription.Field(description=cls._get_description())




    