# -*- coding: utf-8 -*-
"""
Notifications Signals

This module contains Django signals for the notifications app,
handling automatic notification creation and delivery based on
various system events and user actions.

Signals:
    - User authentication events
    - Model change events
    - Security events
    - System events
    - Custom notification triggers

Author: AdTlas Development Team
Version: 1.0.0
Last Updated: 2024
"""

import logging
from datetime import datetime, timedelta
from django.db.models.signals import (
    post_save,
    post_delete,
    pre_save,
    m2m_changed
)
from django.contrib.auth.signals import (
    user_logged_in,
    user_logged_out,
    user_login_failed
)
from django.contrib.admin.models import LogEntry
from django.dispatch import receiver, Signal
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.core.cache import cache
from django.conf import settings
from django.db import transaction

from apps.core.utils import get_client_ip, get_user_agent
from .models import (
    NotificationType,
    Notification,
    NotificationPreference,
    NotificationTemplate,
    NotificationQueue,
)
from .services import NotificationService

User = get_user_model()
logger = logging.getLogger(__name__)

# Custom signals
notification_sent = Signal()
notification_read = Signal()
notification_archived = Signal()
security_event_detected = Signal()
system_maintenance_scheduled = Signal()
user_activity_detected = Signal()


# Utility functions
def get_or_create_notification_type(slug, defaults=None):
    """
    Get or create a notification type.
    
    Args:
        slug: Notification type slug
        defaults: Default values for creation
    
    Returns:
        NotificationType: The notification type
    """
    try:
        return NotificationType.objects.get_or_create(
            slug=slug,
            defaults=defaults or {}
        )[0]
    except Exception as e:
        logger.error(f"Error creating notification type {slug}: {e}")
        return None


def should_send_notification(user, notification_type):
    """
    Check if notification should be sent to user.
    
    Args:
        user: User instance
        notification_type: NotificationType instance
    
    Returns:
        bool: True if notification should be sent
    """
    if not user or not user.is_active:
        return False
    
    if not notification_type or not notification_type.is_active:
        return False
    
    # Check user preferences
    try:
        preference = NotificationPreference.objects.get(
            user=user,
            notification_type=notification_type
        )
        return preference.is_enabled
    except NotificationPreference.DoesNotExist:
        # Use default settings
        return notification_type.default_in_app_enabled


def create_notification_safe(recipient, notification_type_slug, title, message, **kwargs):
    """
    Safely create a notification with error handling.
    
    Args:
        recipient: User to receive notification
        notification_type_slug: Notification type slug
        title: Notification title
        message: Notification message
        **kwargs: Additional notification data
    
    Returns:
        Notification: Created notification or None
    """
    try:
        notification_type = NotificationType.objects.get(slug=notification_type_slug)
        
        if not should_send_notification(recipient, notification_type):
            return None
        
        notification = Notification.objects.create(
            recipient=recipient,
            notification_type=notification_type,
            title=title,
            message=message,
            **kwargs
        )
        
        # Send notification asynchronously
        from .tasks import send_notification_task
        transaction.on_commit(
            lambda: send_notification_task.delay(notification.id)
        )
        
        return notification
        
    except Exception as e:
        logger.error(f"Error creating notification: {e}")
        return None


# User Authentication Signals
@receiver(user_logged_in)
def user_login_notification(sender, request, user, **kwargs):
    """
    Send notification when user logs in.
    
    Args:
        sender: Signal sender
        request: HTTP request
        user: User instance
        **kwargs: Additional arguments
    """
    try:
        # Check if this is a suspicious login
        ip_address = get_client_ip(request)
        user_agent = get_user_agent(request)
        
        # Get user's recent login history
        cache_key = f"user_login_history_{user.id}"
        login_history = cache.get(cache_key, [])
        
        # Check for suspicious activity
        is_suspicious = False
        if login_history:
            # Check for different IP or user agent
            last_login = login_history[-1] if login_history else {}
            if (last_login.get('ip') != ip_address or 
                last_login.get('user_agent') != user_agent):
                is_suspicious = True
        
        # Update login history
        login_history.append({
            'timestamp': timezone.now().isoformat(),
            'ip': ip_address,
            'user_agent': user_agent,
        })
        
        # Keep only last 10 logins
        login_history = login_history[-10:]
        cache.set(cache_key, login_history, 86400 * 7)  # 7 days
        
        # Send appropriate notification
        if is_suspicious:
            create_notification_safe(
                recipient=user,
                notification_type_slug='security_login',
                title='Suspicious Login Detected',
                message=f'A login from a new device or location was detected. IP: {ip_address}',
                metadata={
                    'ip_address': ip_address,
                    'user_agent': user_agent,
                    'timestamp': timezone.now().isoformat(),
                    'is_suspicious': True,
                },
                action_url='/accounts/security/'
            )
        else:
            # Regular login notification (if enabled)
            create_notification_safe(
                recipient=user,
                notification_type_slug='user_login',
                title='Welcome Back!',
                message=f'You have successfully logged in from {ip_address}',
                metadata={
                    'ip_address': ip_address,
                    'user_agent': user_agent,
                    'timestamp': timezone.now().isoformat(),
                }
            )
            
    except Exception as e:
        logger.error(f"Error in user_login_notification: {e}")


@receiver(user_login_failed)
def user_login_failed_notification(sender, credentials, request, **kwargs):
    """
    Handle failed login attempts.
    
    Args:
        sender: Signal sender
        credentials: Login credentials
        request: HTTP request
        **kwargs: Additional arguments
    """
    try:
        email = credentials.get('email') or credentials.get('username')
        if not email:
            return
        
        # Try to find user
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return
        
        ip_address = get_client_ip(request)
        user_agent = get_user_agent(request)
        
        # Track failed attempts
        cache_key = f"failed_login_attempts_{user.id}"
        attempts = cache.get(cache_key, [])
        
        attempts.append({
            'timestamp': timezone.now().isoformat(),
            'ip': ip_address,
            'user_agent': user_agent,
        })
        
        # Keep only last 24 hours
        cutoff = timezone.now() - timedelta(hours=24)
        attempts = [
            attempt for attempt in attempts
            if datetime.fromisoformat(attempt['timestamp'].replace('Z', '+00:00')) > cutoff
        ]
        
        cache.set(cache_key, attempts, 86400)  # 24 hours
        
        # Send notification if multiple failed attempts
        if len(attempts) >= 3:
            create_notification_safe(
                recipient=user,
                notification_type_slug='security_failed_login',
                title='Multiple Failed Login Attempts',
                message=f'There have been {len(attempts)} failed login attempts on your account in the last 24 hours.',
                metadata={
                    'failed_attempts': len(attempts),
                    'latest_ip': ip_address,
                    'latest_user_agent': user_agent,
                    'timestamp': timezone.now().isoformat(),
                },
                action_url='/accounts/security/'
            )
            
    except Exception as e:
        logger.error(f"Error in user_login_failed_notification: {e}")


@receiver(user_logged_out)
def user_logout_notification(sender, request, user, **kwargs):
    """
    Handle user logout (optional notification).
    
    Args:
        sender: Signal sender
        request: HTTP request
        user: User instance
        **kwargs: Additional arguments
    """
    try:
        # Only send logout notification if user has it enabled
        # This is typically disabled by default
        if user and hasattr(user, 'notification_preferences'):
            logout_pref = user.notification_preferences.filter(
                notification_type__slug='user_logout'
            ).first()
            
            if logout_pref and logout_pref.is_enabled:
                ip_address = get_client_ip(request)
                
                create_notification_safe(
                    recipient=user,
                    notification_type_slug='user_logout',
                    title='Account Logout',
                    message=f'You have been logged out from {ip_address}',
                    metadata={
                        'ip_address': ip_address,
                        'timestamp': timezone.now().isoformat(),
                    }
                )
                
    except Exception as e:
        logger.error(f"Error in user_logout_notification: {e}")


# User Model Signals
@receiver(post_save, sender=User)
def user_created_notification(sender, instance, created, **kwargs):
    """
    Send welcome notification when user is created.
    
    Args:
        sender: Signal sender
        instance: User instance
        created: Whether user was created
        **kwargs: Additional arguments
    """
    if created:
        try:
            # Create default notification preferences
            notification_types = NotificationType.objects.filter(is_active=True)
            for nt in notification_types:
                NotificationPreference.objects.get_or_create(
                    user=instance,
                    notification_type=nt,
                    defaults={
                        'is_enabled': True,
                        'email_enabled': nt.default_email_enabled,
                        'in_app_enabled': nt.default_in_app_enabled,
                    }
                )
            
            # Send welcome notification
            create_notification_safe(
                recipient=instance,
                notification_type_slug='user_welcome',
                title=f'Welcome to {settings.SITE_NAME}!',
                message='Thank you for joining us. Explore our features and get started!',
                metadata={
                    'user_id': instance.id,
                    'registration_date': timezone.now().isoformat(),
                },
                action_url='/dashboard/'
            )
            
        except Exception as e:
            logger.error(f"Error in user_created_notification: {e}")


@receiver(pre_save, sender=User)
def user_profile_updated_notification(sender, instance, **kwargs):
    """
    Send notification when user profile is updated.
    
    Args:
        sender: Signal sender
        instance: User instance
        **kwargs: Additional arguments
    """
    if instance.pk:  # Only for existing users
        try:
            old_instance = User.objects.get(pk=instance.pk)
            
            # Check for important changes
            important_changes = []
            
            if old_instance.email != instance.email:
                important_changes.append('email address')
            
            if old_instance.first_name != instance.first_name or old_instance.last_name != instance.last_name:
                important_changes.append('name')
            
            if old_instance.is_active != instance.is_active:
                important_changes.append('account status')
            
            # Send notification for important changes
            if important_changes:
                transaction.on_commit(lambda: create_notification_safe(
                    recipient=instance,
                    notification_type_slug='profile_updated',
                    title='Profile Updated',
                    message=f'Your {', '.join(important_changes)} has been updated.',
                    metadata={
                        'changes': important_changes,
                        'timestamp': timezone.now().isoformat(),
                    },
                    action_url='/accounts/profile/'
                ))
                
        except User.DoesNotExist:
            pass
        except Exception as e:
            logger.error(f"Error in user_profile_updated_notification: {e}")


# Admin Action Signals
@receiver(post_save, sender=LogEntry)
def admin_action_notification(sender, instance, created, **kwargs):
    """
    Send notification for important admin actions.
    
    Args:
        sender: Signal sender
        instance: LogEntry instance
        created: Whether entry was created
        **kwargs: Additional arguments
    """
    if created:
        try:
            # Only notify for certain actions
            important_actions = [
                'delete',
                'change',
            ]
            
            if instance.action_flag in [1, 2, 3]:  # Add, Change, Delete
                action_names = {1: 'added', 2: 'changed', 3: 'deleted'}
                action_name = action_names.get(instance.action_flag, 'modified')
                
                # Notify affected user if applicable
                if hasattr(instance.content_type.model_class(), 'user'):
                    try:
                        obj = instance.content_type.get_object_for_this_type(pk=instance.object_id)
                        if hasattr(obj, 'user') and obj.user:
                            create_notification_safe(
                                recipient=obj.user,
                                notification_type_slug='admin_action',
                                title='Account Action by Administrator',
                                message=f'An administrator has {action_name} your {instance.content_type.name}.',
                                metadata={
                                    'admin_user': instance.user.get_full_name() if instance.user else 'System',
                                    'action': action_name,
                                    'object_type': instance.content_type.name,
                                    'timestamp': timezone.now().isoformat(),
                                }
                            )
                    except Exception:
                        pass
                        
        except Exception as e:
            logger.error(f"Error in admin_action_notification: {e}")


# Custom Signal Handlers
@receiver(security_event_detected)
def handle_security_event(sender, user, event_type, details, **kwargs):
    """
    Handle security event notifications.
    
    Args:
        sender: Signal sender
        user: Affected user
        event_type: Type of security event
        details: Event details
        **kwargs: Additional arguments
    """
    try:
        severity_titles = {
            'low': 'Security Notice',
            'medium': 'Security Alert',
            'high': 'Security Warning',
            'critical': 'Critical Security Alert',
        }
        
        severity = details.get('severity', 'medium')
        title = severity_titles.get(severity, 'Security Alert')
        
        create_notification_safe(
            recipient=user,
            notification_type_slug='security_event',
            title=title,
            message=details.get('message', f'A {event_type} security event was detected.'),
            metadata={
                'event_type': event_type,
                'severity': severity,
                'details': details,
                'timestamp': timezone.now().isoformat(),
            },
            action_url='/accounts/security/'
        )
        
    except Exception as e:
        logger.error(f"Error in handle_security_event: {e}")


@receiver(system_maintenance_scheduled)
def handle_maintenance_notification(sender, maintenance_type, scheduled_time, duration, **kwargs):
    """
    Handle system maintenance notifications.
    
    Args:
        sender: Signal sender
        maintenance_type: Type of maintenance
        scheduled_time: When maintenance is scheduled
        duration: Expected duration
        **kwargs: Additional arguments
    """
    try:
        # Notify all active users
        active_users = User.objects.filter(is_active=True)
        
        for user in active_users:
            create_notification_safe(
                recipient=user,
                notification_type_slug='system_maintenance',
                title='Scheduled System Maintenance',
                message=f'System maintenance ({maintenance_type}) is scheduled for {scheduled_time}. Expected duration: {duration}.',
                metadata={
                    'maintenance_type': maintenance_type,
                    'scheduled_time': scheduled_time.isoformat() if hasattr(scheduled_time, 'isoformat') else str(scheduled_time),
                    'duration': duration,
                    'timestamp': timezone.now().isoformat(),
                },
                expires_at=scheduled_time + timedelta(hours=24) if hasattr(scheduled_time, '__add__') else None
            )
            
    except Exception as e:
        logger.error(f"Error in handle_maintenance_notification: {e}")


@receiver(notification_sent)
def handle_notification_sent(sender, notification, delivery_method, **kwargs):
    """
    Handle notification sent event.
    
    Args:
        sender: Signal sender
        notification: Notification instance
        delivery_method: How notification was sent
        **kwargs: Additional arguments
    """
    try:
        # Log notification delivery
        from .models import NotificationHistory
        
        NotificationHistory.objects.create(
            notification=notification,
            delivery_method=delivery_method,
            status='delivered',
            delivered_at=timezone.now(),
            metadata={
                'sender': str(sender),
                'timestamp': timezone.now().isoformat(),
            }
        )
        
    except Exception as e:
        logger.error(f"Error in handle_notification_sent: {e}")


@receiver(notification_read)
def handle_notification_read(sender, notification, **kwargs):
    """
    Handle notification read event.
    
    Args:
        sender: Signal sender
        notification: Notification instance
        **kwargs: Additional arguments
    """
    try:
        # Update read statistics or trigger follow-up actions
        # This could be used for analytics or user engagement tracking
        pass
        
    except Exception as e:
        logger.error(f"Error in handle_notification_read: {e}")


# Notification Model Signals
@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
    """
    Handle notification creation.
    
    Args:
        sender: Signal sender
        instance: Notification instance
        created: Whether notification was created
        **kwargs: Additional arguments
    """
    if created:
        try:
            # Update user's unread count cache
            cache_key = f"unread_notifications_{instance.recipient.id}"
            cache.delete(cache_key)
            
            # Trigger real-time update if using WebSockets
            # This would integrate with Django Channels
            # channel_layer = get_channel_layer()
            # async_to_sync(channel_layer.group_send)(
            #     f"user_{instance.recipient.id}",
            #     {
            #         "type": "notification_created",
            #         "notification": {
            #             "id": instance.id,
            #             "title": instance.title,
            #             "message": instance.message,
            #         }
            #     }
            # )
            
        except Exception as e:
            logger.error(f"Error in notification_created: {e}")


# Initialize default notification types
def create_default_notification_types():
    """
    Create default notification types.
    
    This function is called during app initialization
    to ensure required notification types exist.
    """
    default_types = [
        {
            'slug': 'user_welcome',
            'name': 'Welcome',
            'description': 'Welcome message for new users',
            'icon': 'fas fa-hand-wave',
            'color': '#28a745',
            'priority': 5,
        },
        {
            'slug': 'user_login',
            'name': 'Login',
            'description': 'User login notifications',
            'icon': 'fas fa-sign-in-alt',
            'color': '#17a2b8',
            'priority': 3,
            'default_in_app_enabled': False,  # Usually disabled
        },
        {
            'slug': 'user_logout',
            'name': 'Logout',
            'description': 'User logout notifications',
            'icon': 'fas fa-sign-out-alt',
            'color': '#6c757d',
            'priority': 2,
            'default_in_app_enabled': False,  # Usually disabled
        },
        {
            'slug': 'security_login',
            'name': 'Security Login',
            'description': 'Suspicious login attempts',
            'icon': 'fas fa-shield-alt',
            'color': '#ffc107',
            'priority': 8,
        },
        {
            'slug': 'security_failed_login',
            'name': 'Failed Login',
            'description': 'Failed login attempts',
            'icon': 'fas fa-exclamation-triangle',
            'color': '#dc3545',
            'priority': 9,
        },
        {
            'slug': 'security_event',
            'name': 'Security Event',
            'description': 'General security events',
            'icon': 'fas fa-lock',
            'color': '#dc3545',
            'priority': 9,
        },
        {
            'slug': 'profile_updated',
            'name': 'Profile Updated',
            'description': 'Profile change notifications',
            'icon': 'fas fa-user-edit',
            'color': '#007bff',
            'priority': 6,
        },
        {
            'slug': 'admin_action',
            'name': 'Admin Action',
            'description': 'Administrator actions on user account',
            'icon': 'fas fa-user-shield',
            'color': '#6f42c1',
            'priority': 7,
        },
        {
            'slug': 'system_maintenance',
            'name': 'System Maintenance',
            'description': 'System maintenance notifications',
            'icon': 'fas fa-tools',
            'color': '#fd7e14',
            'priority': 6,
        },
        {
            'slug': 'password_reset',
            'name': 'Password Reset',
            'description': 'Password reset notifications',
            'icon': 'fas fa-key',
            'color': '#20c997',
            'priority': 8,
        },
        {
            'slug': 'email_verification',
            'name': 'Email Verification',
            'description': 'Email verification notifications',
            'icon': 'fas fa-envelope-check',
            'color': '#17a2b8',
            'priority': 8,
        },
    ]
    
    for type_data in default_types:
        try:
            NotificationType.objects.get_or_create(
                slug=type_data['slug'],
                defaults=type_data
            )
        except Exception as e:
            logger.error(f"Error creating notification type {type_data['slug']}: {e}")


# Connect signal to create default types after migration
from django.db.models.signals import post_migrate
from django.apps import apps


@receiver(post_migrate)
def create_default_types_after_migrate(sender, **kwargs):
    """
    Create default notification types after migration.
    
    Args:
        sender: Signal sender
        **kwargs: Additional arguments
    """
    if sender.name == 'apps.notifications':
        create_default_notification_types()