"""
Notification Handler - Use this to send notifications from anywhere in the app.

Usage:
    from apps.notifications.handler import NotificationHandler

    # Send to a single user
    NotificationHandler.send(
        user=request.user,
        title="New Task Assigned",
        message="You have been assigned to Task #123",
        notification_type="task",
        priority="high",
        link="/tasks/123"
    )

    # Send to multiple users
    NotificationHandler.broadcast(
        users=[user1, user2, user3],
        title="System Maintenance",
        message="Scheduled maintenance tonight",
        notification_type="system"
    )

    # Send using a template
    NotificationHandler.send_from_template(
        user=request.user,
        template_name="task_assigned",
        context={"task_id": 123, "task_title": "Fix bug"}
    )
"""

from typing import Optional
from django.contrib.auth import get_user_model
from django.db import transaction
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

User = get_user_model()


class NotificationHandler:
    """Central handler for creating and sending notifications."""

    @staticmethod
    def send(
        user: User,
        title: str,
        message: str,
        notification_type: str = "info",
        priority: str = "medium",
        icon: str = "bell",
        link: str = "",
        metadata: Optional[dict] = None,
        related_user: Optional[User] = None,
        broadcast: bool = False
    ):
        """
        Create and optionally broadcast a notification to a user.

        Args:
            user: The user to notify
            title: Notification title
            message: Notification message body
            notification_type: Type of notification (info, success, warning, error, etc.)
            priority: Priority level (low, medium, high, urgent)
            icon: Icon name for the notification
            link: Optional URL to redirect when clicked
            metadata: Optional extra data to store with the notification
            related_user: Optional user related to this notification
            broadcast: If True, also send via WebSocket
        """
        from .models import Notification

        notification = Notification.objects.create(
            user=user,
            title=title,
            message=message,
            notification_type=notification_type,
            priority=priority,
            icon=icon,
            link=link,
            metadata=metadata or {},
            related_user=related_user
        )

        from apps.core.background import run_in_background

        # FCM/WebSocket are network round-trips — keep them off the request
        # thread, and dispatch only after commit so a rolled-back transaction
        # never produces a push for a row that doesn't exist.
        def _dispatch():
            if broadcast:
                run_in_background(NotificationHandler._broadcast_notification, notification)
            run_in_background(NotificationHandler._push_fcm, notification)

        transaction.on_commit(_dispatch)

        return notification

    @staticmethod
    def broadcast(
        users: list,
        title: str,
        message: str,
        notification_type: str = "info",
        priority: str = "medium",
        icon: str = "bell",
        link: str = "",
        metadata: Optional[dict] = None
    ):
        """
        Create notifications for multiple users at once.

        Args:
            users: List of users to notify
            title: Notification title
            message: Notification message body
            notification_type: Type of notification
            priority: Priority level
            icon: Icon name
            link: Optional URL
            metadata: Optional extra data
        """
        from .models import Notification

        notifications = []
        for user in users:
            notifications.append(Notification(
                user=user,
                title=title,
                message=message,
                notification_type=notification_type,
                priority=priority,
                icon=icon,
                link=link,
                metadata=metadata or {}
            ))

        created = Notification.objects.bulk_create(notifications)

        from apps.core.background import run_in_background

        # Fan-out runs on the background pool after commit: WebSocket per
        # notification, FCM as one batched dispatch instead of one network
        # round-trip per user.
        def _dispatch(batch):
            for notification in batch:
                NotificationHandler._broadcast_notification(notification)
            NotificationHandler._push_fcm_batch(batch)

        transaction.on_commit(lambda: run_in_background(_dispatch, created))

        return created

    @staticmethod
    def send_to_role(
        role_name: str,
        title: str,
        message: str,
        notification_type: str = "info",
        priority: str = "medium",
        icon: str = "bell",
        link: str = "",
        metadata: Optional[dict] = None
    ):
        """
        Send notification to all users with a specific role.

        Args:
            role_name: Django auth role name (e.g., 'admin', 'manager')
            title: Notification title
            message: Notification message
            notification_type: Type of notification
            priority: Priority level
            icon: Icon name
            link: Optional URL
            metadata: Optional extra data
        """
        from django.contrib.auth.models import Group
        users = User.objects.filter(groups__name=role_name)
        return NotificationHandler.broadcast(
            users=list(users),
            title=title,
            message=message,
            notification_type=notification_type,
            priority=priority,
            icon=icon,
            link=link,
            metadata=metadata
        )

    @staticmethod
    def send_from_template(
        user: User,
        template_name: str,
        context: Optional[dict] = None,
        notification_type: Optional[str] = None,
        priority: str = "medium",
        link: str = "",
        metadata: Optional[dict] = None
    ):
        """
        Send notification using a predefined template.

        Args:
            user: User to notify
            template_name: Name of the NotificationTemplate
            context: Dict to interpolate into template
            notification_type: Override template's type if provided
            priority: Override priority if provided
            link: Optional URL
            metadata: Optional extra data
        """
        from .models import NotificationTemplate

        try:
            template = NotificationTemplate.objects.get(name=template_name)
        except NotificationTemplate.DoesNotExist:
            raise ValueError(f"Notification template '{template_name}' not found")

        context = context or {}
        title = template.title_template.format(**context)
        message = template.message_template.format(**context)

        return NotificationHandler.send(
            user=user,
            title=title,
            message=message,
            notification_type=notification_type or template.notification_type,
            priority=priority,
            icon=template.icon,
            link=link,
            metadata=metadata
        )

    @staticmethod
    def _broadcast_notification(notification):
        """Send notification via WebSocket to the user's channel."""
        try:
            channel_layer = get_channel_layer()
            if channel_layer is None:
                return

            async_to_sync(channel_layer.group_send)(
                f"notifications_{notification.user.id}",
                {
                    "type": "notification",
                    "notification": {
                        "id": str(notification.id),
                        "title": notification.title,
                        "message": notification.message,
                        "notification_type": notification.notification_type,
                        "priority": notification.priority,
                        "icon": notification.icon,
                        "link": notification.link,
                        "metadata": notification.metadata,
                        "is_read": notification.is_read,
                        "created_at": notification.created_at.isoformat(),
                    }
                }
            )
        except Exception:
            pass

    @staticmethod
    def _push_fcm(notification):
        """Push the notification to the user's devices via FCM.

        Skips silently when the user disabled push or has it muted for this
        notification type. Best-effort: never raises into the caller.
        """
        try:
            from .models import NotificationPreference
            from . import fcm

            prefs = NotificationPreference.objects.filter(
                user=notification.user
            ).first()
            if prefs and (
                not prefs.push_enabled
                or not prefs.is_type_enabled(notification.notification_type)
            ):
                return

            fcm.send_to_user(
                notification.user,
                title=notification.title,
                body=notification.message,
                data={
                    "id": str(notification.id),
                    "notification_type": notification.notification_type,
                    "link": notification.link,
                },
            )
        except Exception:
            pass

    @staticmethod
    def _push_fcm_batch(notifications):
        """Push a batch of notifications via FCM in a single dispatch.

        Same per-user preference checks as _push_fcm, but with one preference
        query and one FCM call for the whole batch. Best-effort: never raises.
        """
        try:
            from .models import NotificationPreference
            from . import fcm

            user_ids = {n.user_id for n in notifications}
            prefs = {
                p.user_id: p
                for p in NotificationPreference.objects.filter(user_id__in=user_ids)
            }

            payloads = []
            for n in notifications:
                p = prefs.get(n.user_id)
                if p and (
                    not p.push_enabled
                    or not p.is_type_enabled(n.notification_type)
                ):
                    continue
                payloads.append({
                    "user": n.user,
                    "title": n.title,
                    "body": n.message,
                    "data": {
                        "id": str(n.id),
                        "notification_type": n.notification_type,
                        "link": n.link,
                    },
                })

            if payloads:
                fcm.send_to_users(payloads)
        except Exception:
            pass

    @staticmethod
    def mark_as_read(notification_id: str, user: User) -> bool:
        """Mark a notification as read."""
        from .models import Notification

        try:
            notification = Notification.objects.get(id=notification_id, user=user)
            notification.mark_as_read()
            return True
        except Notification.DoesNotExist:
            return False

    @staticmethod
    def mark_all_as_read(user: User) -> int:
        """Mark all notifications as read for a user."""
        from .models import Notification
        from django.utils import timezone

        updated = Notification.objects.filter(
            user=user,
            is_read=False
        ).update(is_read=True, read_at=timezone.now())

        return updated

    @staticmethod
    def get_unread_count(user: User) -> int:
        """Get count of unread notifications for a user."""
        from .models import Notification
        return Notification.objects.filter(user=user, is_read=False).count()

    @staticmethod
    def get_unread(user: User, limit: int = 10) -> list:
        """Get recent unread notifications for a user."""
        from .models import Notification
        return list(Notification.objects.filter(
            user=user,
            is_read=False
        ).order_by('-created_at')[:limit])
