import logging
from abc import ABC, abstractmethod
from typing import Optional, Type

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth.models import User

from notifications.models import Notification
from notifications.serializers import NotificationSerializer

_log = logging.getLogger(__name__)


class SenderABC(ABC):

    @abstractmethod
    def send_to_group(self, group: Optional[str], data: Optional[dict] = None):
        ...

    @abstractmethod
    def send_to_user(self, user: User, data: Optional[dict] = None):
        ...


class WebSockerSender(SenderABC):
    DEFAULT_GROUP = 'notification'

    def __init__(self):
        channel_layer = get_channel_layer()
        self._send_to_group_method = async_to_sync(channel_layer.group_send)
        self._send_to_user_method = async_to_sync(channel_layer.send)

    def send_to_group(self, group: Optional[str], data: Optional[dict] = None):
        data = data or dict()
        group = group or self.DEFAULT_GROUP
        self._send_to_group_method(
            group,
            {
                'type': 'notification',
                'data': data
            }
        )

    def send_to_user(self, user: User, data: Optional[dict] = None):
        data = data or dict()
        channel_name = user.userinfo.channel_name
        if channel_name:
            self._send_to_user_method(
                channel_name,
                {
                    'type': 'notification',
                    'data': data
                }
            )


class NotificationService:

    def __init__(self, sender_class: Optional[Type[SenderABC]] = WebSockerSender):
        self.sender = sender_class()

    def send(self,
             notification: Optional[Notification] = None,
             group: Optional[str] = None,
             user: Optional[User] = None,
             data: Optional[dict] = None):
        if not notification:
            if not self._create_notification(data):
                _log.warning('Notification not created')
                return
        if group:
            self._send_notification_to_group(notification, group)
        elif user:
            self._send_notification_to_user(notification, user)

    def _send_notification_to_group(self, notification: Notification, group: str):
        data = self._serialize_notification(notification)
        self.sender.send_to_group(group=group, data=data)

    def _send_notification_to_user(self, notification: Notification, user: User):
        data = self._serialize_notification(notification)
        self.sender.send_to_user(user=user, data=data)

    @staticmethod
    def _create_notification(data: dict) -> Optional[Notification]:
        serializer = NotificationSerializer(data=data)
        if serializer.is_valid():
            return serializer.save()
        _log.warning(f'Invalid Notification data: {serializer.errors}')

    @staticmethod
    def _serialize_notification(notification) -> dict:
        data = NotificationSerializer(instance=notification).data
        return data
