"""
Notification Service.
Main service for sending and managing notifications.
"""
import logging
from typing import Any

from django.db import transaction
from django.utils import timezone

from .models import (
    Notification,
    NotificationCategory,
    NotificationLog,
    NotificationTemplate,
    UserNotificationPreference,
    NotificationChannel,
    NotificationStatus,
    NotificationPriority,
)
from .providers import ProviderResponse
from .providers.sms import SMSProvider
from .providers.email import EmailProvider
from .providers.panel import PanelProvider
from .template_engine import template_engine, TemplateRenderError
from .signals import (
    notification_sent,
    notification_delivered,
    notification_failed,
    notification_read,
)

logger = logging.getLogger('notification.service')


class NotificationService:
    """
    Main service for sending notifications.
    Handles template rendering, channel selection, and delivery.
    """
    
    def __init__(self):
        self.providers = {
            NotificationChannel.SMS: SMSProvider(),
            NotificationChannel.EMAIL: EmailProvider(),
            NotificationChannel.PANEL: PanelProvider(),
        }
    
    def send(
        self,
        user,
        template_name: str | None = None,
        template: NotificationTemplate | None = None,
        channel: str | None = None,
        title: str | None = None,
        content: str | None = None,
        variables: dict[str, Any] | None = None,
        priority: str = NotificationPriority.NORMAL,
        metadata: dict[str, Any] | None = None,
        scheduled_at=None,
        tenant=None,
    ) -> Notification | None:
        """
        Send a notification to a user.
        
        Args:
            user: User instance to send notification to
            template_name: Name of template to use
            template: NotificationTemplate instance (alternative to template_name)
            channel: Override channel (if not using template's channel)
            title: Override title
            content: Override content (or content if no template)
            variables: Variables for template rendering
            priority: Notification priority
            metadata: Additional metadata (link, action, etc.)
            scheduled_at: Schedule for later delivery
            tenant: Tenant instance (if not using request context)
            
        Returns:
            Notification instance if created, None on error
        """
        tenant = tenant or self._get_current_tenant()
        if not tenant:
            logger.error("No tenant context for notification")
            return None
        
        # Get template if template_name provided
        if template_name and not template:
            template = self._get_template(tenant, template_name)
            if not template:
                logger.error(f"Template not found: {template_name}")
                return None
        
        # Determine channel
        if not channel:
            channel = template.channel if template else NotificationChannel.PANEL
        
        # Check user preferences
        category = template.category if template else None
        if not self._check_user_preference(user, category, channel, tenant):
            logger.info(f"User {user} has disabled {channel} for category {category}")
            return None
        
        # Render content
        try:
            rendered_title, rendered_content = self._render_notification(
                template=template,
                title=title,
                content=content,
                variables=variables or {},
                user=user,
                tenant=tenant,
            )
        except TemplateRenderError as e:
            logger.error(f"Template render failed: {e}")
            return None
        
        # Create notification
        with transaction.atomic():
            notification = Notification.objects.create(
                tenant=tenant,
                user=user,
                category=category,
                template=template,
                channel=channel,
                title=rendered_title,
                content=rendered_content,
                priority=priority,
                status=NotificationStatus.PENDING,
                metadata=metadata or {},
                scheduled_at=scheduled_at,
            )
            
            # Create initial log
            NotificationLog.objects.create(
                tenant=tenant,
                notification=notification,
                channel=channel,
                status=NotificationStatus.PENDING,
            )
        
        # If scheduled, don't send now
        if scheduled_at and scheduled_at > timezone.now():
            logger.info(f"Notification {notification.id} scheduled for {scheduled_at}")
            return notification
        
        # Send immediately
        self._deliver(notification, user)
        
        return notification
    
    def send_bulk(
        self,
        users,
        template_name: str,
        variables: dict[str, Any] | None = None,
        priority: str = NotificationPriority.NORMAL,
        tenant=None,
    ) -> list[Notification]:
        """
        Send notification to multiple users.
        
        Args:
            users: List of User instances
            template_name: Name of template to use
            variables: Base variables (user-specific vars added automatically)
            priority: Notification priority
            tenant: Tenant instance
            
        Returns:
            List of created Notification instances
        """
        notifications = []
        
        for user in users:
            # Add user-specific variables
            user_vars = {
                'user_name': user.get_full_name() or user.email,
                'user_email': user.email,
                'user_phone': getattr(user, 'phone', ''),
                **(variables or {}),
            }
            
            notification = self.send(
                user=user,
                template_name=template_name,
                variables=user_vars,
                priority=priority,
                tenant=tenant,
            )
            
            if notification:
                notifications.append(notification)
        
        return notifications
    
    def mark_as_read(self, notification: Notification) -> None:
        """
        Mark notification as read.
        
        Args:
            notification: Notification instance to mark as read
        """
        if not notification.is_read:
            notification.mark_as_read()
            notification_read.send(sender=self.__class__, notification=notification)
    
    def mark_all_as_read(self, user, tenant=None) -> int:
        """
        Mark all user's notifications as read.
        
        Args:
            user: User instance
            tenant: Tenant instance
            
        Returns:
            Number of notifications marked as read
        """
        tenant = tenant or self._get_current_tenant()
        
        count = Notification.objects.filter(
            tenant=tenant,
            user=user,
            is_read=False,
        ).update(
            is_read=True,
            read_at=timezone.now(),
            status=NotificationStatus.READ,
        )
        
        return count
    
    def get_unread_count(self, user, tenant=None) -> int:
        """
        Get count of unread notifications for a user.
        
        Args:
            user: User instance
            tenant: Tenant instance
            
        Returns:
            Number of unread notifications
        """
        tenant = tenant or self._get_current_tenant()
        
        return Notification.objects.filter(
            tenant=tenant,
            user=user,
            is_read=False,
            channel=NotificationChannel.PANEL,
        ).count()
    
    def _deliver(self, notification: Notification, user) -> bool:
        """
        Deliver notification through appropriate channel.
        
        Args:
            notification: Notification to deliver
            user: User instance
            
        Returns:
            True if delivered successfully
        """
        provider = self.providers.get(notification.channel)
        if not provider:
            logger.error(f"No provider for channel: {notification.channel}")
            self._update_status(notification, NotificationStatus.FAILED, "No provider")
            return False
        
        # Get recipient based on channel
        recipient = self._get_recipient(user, notification.channel)
        if not recipient:
            logger.warning(f"No recipient for {notification.channel}")
            self._update_status(notification, NotificationStatus.FAILED, "No recipient")
            return False
        
        # Send through provider
        response = provider.send(
            recipient=recipient,
            content=notification.content,
            subject=notification.title,
        )
        
        # Update notification status
        if response.success:
            self._update_status(
                notification,
                NotificationStatus.SENT,
                response=response,
            )
            notification_sent.send(sender=self.__class__, notification=notification)
            notification_delivered.send(
                sender=self.__class__,
                notification=notification,
                channel=notification.channel,
            )
            return True
        else:
            self._update_status(
                notification,
                NotificationStatus.FAILED,
                error=response.error,
                response=response,
            )
            notification_failed.send(
                sender=self.__class__,
                notification=notification,
                channel=notification.channel,
                error=response.error,
            )
            return False
    
    def _render_notification(
        self,
        template: NotificationTemplate | None,
        title: str | None,
        content: str | None,
        variables: dict[str, Any],
        user,
        tenant,
    ) -> tuple[str, str]:
        """
        Render notification title and content.
        
        Returns:
            Tuple of (rendered_title, rendered_content)
        """
        # Build context with user and tenant info
        context = {
            'user_name': user.get_full_name() or user.email,
            'user_email': user.email,
            'user_phone': getattr(user, 'phone', ''),
            'tenant_name': tenant.name,
            **variables,
        }
        
        if template:
            rendered_title = title or template.title
            rendered_content = template_engine.render(template.content, context)
        else:
            rendered_title = title or "Notification"
            rendered_content = template_engine.render(content or "", context)
        
        return rendered_title, rendered_content
    
    def _get_template(self, tenant, name: str) -> NotificationTemplate | None:
        """Get template by name for tenant."""
        try:
            return NotificationTemplate.objects.get(
                tenant=tenant,
                name=name,
                is_active=True,
            )
        except NotificationTemplate.DoesNotExist:
            return None
    
    def _check_user_preference(
        self,
        user,
        category: NotificationCategory | None,
        channel: str,
        tenant,
    ) -> bool:
        """Check if user has enabled this channel for category."""
        if not category:
            return True  # No category = no preference check
        
        try:
            pref = UserNotificationPreference.objects.get(
                tenant=tenant,
                user=user,
                category=category,
            )
            
            if channel == NotificationChannel.SMS:
                return pref.sms_enabled
            elif channel == NotificationChannel.EMAIL:
                return pref.email_enabled
            elif channel == NotificationChannel.PANEL:
                return pref.panel_enabled
            
        except UserNotificationPreference.DoesNotExist:
            return True  # No preference = enabled by default
        
        return True
    
    def _get_recipient(self, user, channel: str) -> str | None:
        """Get recipient identifier based on channel."""
        if channel == NotificationChannel.SMS:
            return getattr(user, 'phone', None)
        elif channel == NotificationChannel.EMAIL:
            return user.email
        elif channel == NotificationChannel.PANEL:
            return str(user.id)
        return None
    
    def _update_status(
        self,
        notification: Notification,
        status: str,
        error: str | None = None,
        response: ProviderResponse | None = None,
    ) -> None:
        """Update notification and log status."""
        notification.status = status
        if status == NotificationStatus.SENT:
            notification.sent_at = timezone.now()
        notification.save(update_fields=['status', 'sent_at'])
        
        # Update or create log
        log = notification.logs.order_by('-created_at').first()
        if log:
            log.status = status
            log.error_message = error
            if response:
                log.provider_response = response.raw_response or {}
            log.save()
        else:
            NotificationLog.objects.create(
                tenant=notification.tenant,
                notification=notification,
                channel=notification.channel,
                status=status,
                error_message=error,
                provider_response=response.raw_response if response else {},
            )
    
    def _get_current_tenant(self):
        """Get current tenant from request context."""
        from apps.core.tenant.middleware import get_current_tenant
        return get_current_tenant()


# Singleton instance
notification_service = NotificationService()
