"""Core Signals

This module contains Django signal handlers for the core application.
Signals are used to perform actions when certain events occur in the application.
"""

import logging
from django.db.models.signals import post_save, post_delete, pre_save, pre_delete
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.core.signals import request_started, request_finished
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.conf import settings

from .models import ActivityLog, Setting
from .utils import get_client_ip, get_user_agent


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


# ============================================================================
# User Authentication Signals
# ============================================================================

@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
    """Log user login activity.
    
    Args:
        sender: The User model class
        request: HttpRequest object
        user: User instance that logged in
        **kwargs: Additional keyword arguments
    """
    try:
        # Log the login activity
        ActivityLog.log_activity(
            user=user,
            activity_type='login',
            description=f"User {user.username} logged in",
            ip_address=get_client_ip(request),
            user_agent=get_user_agent(request),
            extra_data={
                'login_method': 'standard',
                'session_key': request.session.session_key
            }
        )
        
        # Update user's last login IP and user agent if the model supports it
        if hasattr(user, 'last_login_ip'):
            user.last_login_ip = get_client_ip(request)
        
        if hasattr(user, 'last_login_user_agent'):
            user.last_login_user_agent = get_user_agent(request)
        
        if hasattr(user, 'last_activity'):
            user.last_activity = timezone.now()
        
        # Save user updates if any fields were modified
        if hasattr(user, 'last_login_ip') or hasattr(user, 'last_login_user_agent') or hasattr(user, 'last_activity'):
            user.save(update_fields=[
                field for field in ['last_login_ip', 'last_login_user_agent', 'last_activity']
                if hasattr(user, field)
            ])
        
        logger.info(f"User {user.username} logged in from {get_client_ip(request)}")
        
    except Exception as e:
        logger.error(f"Error logging user login: {e}")


@receiver(user_logged_out)
def log_user_logout(sender, request, user, **kwargs):
    """Log user logout activity.
    
    Args:
        sender: The User model class
        request: HttpRequest object
        user: User instance that logged out
        **kwargs: Additional keyword arguments
    """
    try:
        if user:
            # Log the logout activity
            ActivityLog.log_activity(
                user=user,
                activity_type='logout',
                description=f"User {user.username} logged out",
                ip_address=get_client_ip(request),
                user_agent=get_user_agent(request),
                extra_data={
                    'logout_method': 'standard'
                }
            )
            
            logger.info(f"User {user.username} logged out from {get_client_ip(request)}")
        
    except Exception as e:
        logger.error(f"Error logging user logout: {e}")


@receiver(user_login_failed)
def log_failed_login(sender, credentials, request, **kwargs):
    """Log failed login attempts.
    
    Args:
        sender: The User model class
        credentials: Login credentials used
        request: HttpRequest object
        **kwargs: Additional keyword arguments
    """
    try:
        username = credentials.get('username', 'Unknown')
        
        # Log the failed login activity
        ActivityLog.log_activity(
            user=None,  # No user since login failed
            activity_type='login',
            description=f"Failed login attempt for username: {username}",
            ip_address=get_client_ip(request),
            user_agent=get_user_agent(request),
            extra_data={
                'login_method': 'standard',
                'username_attempted': username,
                'success': False
            }
        )
        
        logger.warning(f"Failed login attempt for {username} from {get_client_ip(request)}")
        
        # Check for suspicious activity (multiple failed attempts)
        recent_failures = ActivityLog.objects.filter(
            activity_type='login',
            ip_address=get_client_ip(request),
            extra_data__success=False,
            created_at__gte=timezone.now() - timezone.timedelta(minutes=15)
        ).count()
        
        if recent_failures >= 5:
            logger.critical(
                f"Multiple failed login attempts detected from {get_client_ip(request)}. "
                f"Total attempts in last 15 minutes: {recent_failures}"
            )
            
            # Log security alert
            ActivityLog.log_activity(
                user=None,
                activity_type='error',
                description=f"Security Alert: Multiple failed login attempts from IP {get_client_ip(request)}",
                ip_address=get_client_ip(request),
                user_agent=get_user_agent(request),
                extra_data={
                    'alert_type': 'brute_force_attempt',
                    'failure_count': recent_failures,
                    'time_window': '15_minutes'
                }
            )
        
    except Exception as e:
        logger.error(f"Error logging failed login: {e}")


# ============================================================================
# Model Change Signals
# ============================================================================

@receiver(post_save)
def log_model_creation(sender, instance, created, **kwargs):
    """Log model instance creation.
    
    Args:
        sender: Model class
        instance: Model instance
        created: Boolean indicating if instance was created
        **kwargs: Additional keyword arguments
    """
    # Skip logging for certain models to avoid infinite loops
    skip_models = [ActivityLog, Setting]
    
    if sender in skip_models:
        return
    
    # Only log creation, not updates
    if not created:
        return
    
    try:
        # Try to get the current user from the instance or request
        user = None
        if hasattr(instance, 'created_by'):
            user = instance.created_by
        elif hasattr(instance, '_current_user'):
            user = instance._current_user
        
        # Log the creation activity
        ActivityLog.log_activity(
            user=user,
            activity_type='create',
            description=f"Created {sender.__name__}: {str(instance)}",
            object_type=sender.__name__,
            object_id=str(instance.pk),
            extra_data={
                'model': sender.__name__,
                'app_label': sender._meta.app_label
            }
        )
        
        logger.debug(f"Created {sender.__name__} instance: {instance.pk}")
        
    except Exception as e:
        logger.error(f"Error logging model creation: {e}")


@receiver(post_delete)
def log_model_deletion(sender, instance, **kwargs):
    """Log model instance deletion.
    
    Args:
        sender: Model class
        instance: Model instance that was deleted
        **kwargs: Additional keyword arguments
    """
    # Skip logging for certain models
    skip_models = [ActivityLog]
    
    if sender in skip_models:
        return
    
    try:
        # Try to get the current user
        user = None
        if hasattr(instance, 'deleted_by'):
            user = instance.deleted_by
        elif hasattr(instance, '_current_user'):
            user = instance._current_user
        
        # Log the deletion activity
        ActivityLog.log_activity(
            user=user,
            activity_type='delete',
            description=f"Deleted {sender.__name__}: {str(instance)}",
            object_type=sender.__name__,
            object_id=str(instance.pk),
            extra_data={
                'model': sender.__name__,
                'app_label': sender._meta.app_label
            }
        )
        
        logger.debug(f"Deleted {sender.__name__} instance: {instance.pk}")
        
    except Exception as e:
        logger.error(f"Error logging model deletion: {e}")


@receiver(pre_save)
def track_model_changes(sender, instance, **kwargs):
    """Track changes to model instances.
    
    Args:
        sender: Model class
        instance: Model instance being saved
        **kwargs: Additional keyword arguments
    """
    # Skip tracking for certain models
    skip_models = [ActivityLog]
    
    if sender in skip_models:
        return
    
    # Only track changes for existing instances
    if not instance.pk:
        return
    
    try:
        # Get the original instance from database
        try:
            original = sender.objects.get(pk=instance.pk)
        except sender.DoesNotExist:
            return
        
        # Track changed fields
        changed_fields = []
        
        for field in sender._meta.fields:
            field_name = field.name
            
            # Skip certain fields
            if field_name in ['updated_at', 'last_activity']:
                continue
            
            original_value = getattr(original, field_name, None)
            current_value = getattr(instance, field_name, None)
            
            if original_value != current_value:
                changed_fields.append({
                    'field': field_name,
                    'old_value': str(original_value) if original_value is not None else None,
                    'new_value': str(current_value) if current_value is not None else None
                })
        
        # Store changed fields in instance for later use
        if changed_fields:
            instance._changed_fields = changed_fields
        
    except Exception as e:
        logger.error(f"Error tracking model changes: {e}")


@receiver(post_save)
def log_model_updates(sender, instance, created, **kwargs):
    """Log model instance updates.
    
    Args:
        sender: Model class
        instance: Model instance
        created: Boolean indicating if instance was created
        **kwargs: Additional keyword arguments
    """
    # Skip logging for certain models
    skip_models = [ActivityLog, Setting]
    
    if sender in skip_models:
        return
    
    # Only log updates, not creation
    if created:
        return
    
    # Check if there were any changes
    if not hasattr(instance, '_changed_fields'):
        return
    
    try:
        # Try to get the current user
        user = None
        if hasattr(instance, 'updated_by'):
            user = instance.updated_by
        elif hasattr(instance, '_current_user'):
            user = instance._current_user
        
        # Log the update activity
        ActivityLog.log_activity(
            user=user,
            activity_type='update',
            description=f"Updated {sender.__name__}: {str(instance)}",
            object_type=sender.__name__,
            object_id=str(instance.pk),
            extra_data={
                'model': sender.__name__,
                'app_label': sender._meta.app_label,
                'changed_fields': instance._changed_fields
            }
        )
        
        logger.debug(f"Updated {sender.__name__} instance: {instance.pk}")
        
        # Clean up the changed fields
        delattr(instance, '_changed_fields')
        
    except Exception as e:
        logger.error(f"Error logging model update: {e}")


# ============================================================================
# Request Signals
# ============================================================================

@receiver(request_started)
def log_request_started(sender, environ, **kwargs):
    """Log when a request starts (for debugging/monitoring).
    
    Args:
        sender: The handler class
        environ: WSGI environ dictionary
        **kwargs: Additional keyword arguments
    """
    # Only log in debug mode to avoid performance issues
    if not settings.DEBUG:
        return
    
    try:
        method = environ.get('REQUEST_METHOD', 'UNKNOWN')
        path = environ.get('PATH_INFO', '/')
        
        logger.debug(f"Request started: {method} {path}")
        
    except Exception as e:
        logger.error(f"Error logging request start: {e}")


@receiver(request_finished)
def log_request_finished(sender, **kwargs):
    """Log when a request finishes (for debugging/monitoring).
    
    Args:
        sender: The handler class
        **kwargs: Additional keyword arguments
    """
    # Only log in debug mode to avoid performance issues
    if not settings.DEBUG:
        return
    
    try:
        logger.debug("Request finished")
        
    except Exception as e:
        logger.error(f"Error logging request finish: {e}")


# ============================================================================
# Custom Signals
# ============================================================================

# You can define custom signals here
# from django.dispatch import Signal

# Custom signal for security events
# security_event = Signal(providing_args=['event_type', 'user', 'ip_address', 'details'])

# @receiver(security_event)
# def handle_security_event(sender, event_type, user, ip_address, details, **kwargs):
#     """Handle security events."""
#     try:
#         ActivityLog.log_activity(
#             user=user,
#             activity_type='security',
#             description=f"Security event: {event_type}",
#             ip_address=ip_address,
#             extra_data={
#                 'event_type': event_type,
#                 'details': details
#             }
#         )
#         
#         logger.warning(f"Security event {event_type} for user {user} from {ip_address}")
#         
#     except Exception as e:
#         logger.error(f"Error handling security event: {e}")


# ============================================================================
# Signal Utilities
# ============================================================================

def disconnect_signals():
    """Disconnect all signals (useful for testing)."""
    user_logged_in.disconnect(log_user_login)
    user_logged_out.disconnect(log_user_logout)
    user_login_failed.disconnect(log_failed_login)
    post_save.disconnect(log_model_creation)
    post_save.disconnect(log_model_updates)
    post_delete.disconnect(log_model_deletion)
    pre_save.disconnect(track_model_changes)
    request_started.disconnect(log_request_started)
    request_finished.disconnect(log_request_finished)


def reconnect_signals():
    """Reconnect all signals (useful for testing)."""
    user_logged_in.connect(log_user_login)
    user_logged_out.connect(log_user_logout)
    user_login_failed.connect(log_failed_login)
    post_save.connect(log_model_creation)
    post_save.connect(log_model_updates)
    post_delete.connect(log_model_deletion)
    pre_save.connect(track_model_changes)
    request_started.connect(log_request_started)
    request_finished.connect(log_request_finished)