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

This module defines Django signals for automatic activity logging.
It captures various events throughout the application and creates
appropriate activity log entries for audit trails and monitoring.

Signals:
    - User authentication signals (login, logout, failed login)
    - Model change signals (create, update, delete)
    - Admin action signals
    - Security event signals

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

import logging
from django.db.models.signals import post_save, post_delete, pre_save
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
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.serializers import serialize
from django.forms.models import model_to_dict
from django.utils import timezone
from django.conf import settings
import json

from .models import Activity, ActivityType, AuditLog, SecurityEvent
from apps.core.utils import get_client_ip, get_user_agent

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

# Thread-local storage for request context
import threading
_thread_locals = threading.local()


def set_current_request(request):
    """
    Set the current request in thread-local storage.
    
    This function should be called from middleware to make
    request information available to signal handlers.
    
    Args:
        request: The current HTTP request
    """
    _thread_locals.request = request


def get_current_request():
    """
    Get the current request from thread-local storage.
    
    Returns:
        HttpRequest or None: The current request if available
    """
    return getattr(_thread_locals, 'request', None)


def get_request_info(request=None):
    """
    Extract request information for activity logging.
    
    Args:
        request: The HTTP request (optional)
        
    Returns:
        dict: Request information dictionary
    """
    if not request:
        request = get_current_request()
    
    if request:
        return {
            'ip_address': get_client_ip(request),
            'user_agent': get_user_agent(request),
            'session_key': getattr(request.session, 'session_key', ''),
        }
    
    return {
        'ip_address': None,
        'user_agent': '',
        'session_key': '',
    }


# Authentication Signals
@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
    """
    Log user login activity.
    
    Args:
        sender: The sender of the signal
        request: The HTTP request
        user: The user who logged in
        **kwargs: Additional keyword arguments
    """
    try:
        request_info = get_request_info(request)
        
        Activity.log_activity(
            user=user,
            action='login',
            description=f'User {user.email} logged in successfully',
            activity_type_code='authentication',
            **request_info
        )
        
        logger.info(f'User login logged: {user.email} from {request_info["ip_address"]}')
        
    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 sender of the signal
        request: The HTTP request
        user: The user who logged out
        **kwargs: Additional keyword arguments
    """
    try:
        if user:  # User might be None for anonymous sessions
            request_info = get_request_info(request)
            
            Activity.log_activity(
                user=user,
                action='logout',
                description=f'User {user.email} logged out',
                activity_type_code='authentication',
                **request_info
            )
            
            logger.info(f'User logout logged: {user.email}')
            
    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 sender of the signal
        credentials: The login credentials used
        request: The HTTP request
        **kwargs: Additional keyword arguments
    """
    try:
        request_info = get_request_info(request)
        email = credentials.get('email', credentials.get('username', 'Unknown'))
        
        # Log as activity
        Activity.log_activity(
            user=None,
            action='login',
            description=f'Failed login attempt for {email}',
            success=False,
            error_message='Invalid credentials',
            activity_type_code='security',
            extra_data={'attempted_email': email},
            **request_info
        )
        
        # Log as security event
        SecurityEvent.objects.create(
            event_type='failed_login',
            severity='medium',
            description=f'Failed login attempt for {email}',
            details={
                'attempted_email': email,
                'timestamp': timezone.now().isoformat(),
            },
            **request_info
        )
        
        logger.warning(f'Failed login attempt logged: {email} from {request_info["ip_address"]}')
        
    except Exception as e:
        logger.error(f'Error logging failed login: {e}')


# Model Change Signals
@receiver(post_save)
def log_model_save(sender, instance, created, **kwargs):
    """
    Log model save operations (create/update).
    
    Args:
        sender: The model class
        instance: The model instance
        created: Whether the instance was created
        **kwargs: Additional keyword arguments
    """
    # Skip logging for activity models to avoid recursion
    if sender in [Activity, ActivityType, AuditLog, SecurityEvent]:
        return
    
    # Skip logging for certain system models
    skip_models = [
        'LogEntry',
        'Session',
        'ContentType',
        'Permission',
        'Group',
    ]
    
    if sender.__name__ in skip_models:
        return
    
    try:
        request = get_current_request()
        user = getattr(request, 'user', None) if request else None
        
        # Only log for authenticated users or important system changes
        if not user or not user.is_authenticated:
            return
        
        request_info = get_request_info(request)
        action = 'create' if created else 'update'
        
        # Log as activity
        Activity.log_activity(
            user=user,
            action=action,
            description=f'{action.title()} {sender.__name__}: {str(instance)}',
            content_object=instance,
            activity_type_code='model_change',
            extra_data={
                'model_name': sender.__name__,
                'object_id': str(instance.pk),
                'created': created,
            },
            **request_info
        )
        
        # Create detailed audit log for updates
        if not created and hasattr(instance, '_state'):
            # Get the previous state if available
            try:
                old_instance = sender.objects.get(pk=instance.pk)
                old_data = model_to_dict(old_instance)
                new_data = model_to_dict(instance)
                
                # Calculate changes
                changes = {}
                for field, new_value in new_data.items():
                    old_value = old_data.get(field)
                    if old_value != new_value:
                        changes[field] = {
                            'old': str(old_value) if old_value is not None else None,
                            'new': str(new_value) if new_value is not None else None,
                        }
                
                if changes:  # Only log if there are actual changes
                    AuditLog.objects.create(
                        user=user,
                        action='update',
                        model_name=sender.__name__,
                        object_id=str(instance.pk),
                        object_repr=str(instance),
                        changes=changes,
                        before_state=old_data,
                        after_state=new_data,
                        **request_info
                    )
                    
            except sender.DoesNotExist:
                # Object was just created or doesn't exist in DB yet
                pass
            except Exception as e:
                logger.error(f'Error creating audit log: {e}')
        
        elif created:
            # Log creation in audit log
            AuditLog.objects.create(
                user=user,
                action='create',
                model_name=sender.__name__,
                object_id=str(instance.pk),
                object_repr=str(instance),
                changes={},
                before_state={},
                after_state=model_to_dict(instance),
                **request_info
            )
        
    except Exception as e:
        logger.error(f'Error logging model save: {e}')


@receiver(post_delete)
def log_model_delete(sender, instance, **kwargs):
    """
    Log model delete operations.
    
    Args:
        sender: The model class
        instance: The model instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Skip logging for activity models
    if sender in [Activity, ActivityType, AuditLog, SecurityEvent]:
        return
    
    # Skip logging for certain system models
    skip_models = [
        'LogEntry',
        'Session',
        'ContentType',
        'Permission',
        'Group',
    ]
    
    if sender.__name__ in skip_models:
        return
    
    try:
        request = get_current_request()
        user = getattr(request, 'user', None) if request else None
        
        # Only log for authenticated users
        if not user or not user.is_authenticated:
            return
        
        request_info = get_request_info(request)
        
        # Log as activity
        Activity.log_activity(
            user=user,
            action='delete',
            description=f'Delete {sender.__name__}: {str(instance)}',
            activity_type_code='model_change',
            extra_data={
                'model_name': sender.__name__,
                'object_id': str(instance.pk),
                'object_repr': str(instance),
            },
            **request_info
        )
        
        # Create audit log
        AuditLog.objects.create(
            user=user,
            action='delete',
            model_name=sender.__name__,
            object_id=str(instance.pk),
            object_repr=str(instance),
            changes={},
            before_state=model_to_dict(instance),
            after_state={},
            **request_info
        )
        
    except Exception as e:
        logger.error(f'Error logging model delete: {e}')


# Admin Action Signals
@receiver(post_save, sender=LogEntry)
def log_admin_action(sender, instance, created, **kwargs):
    """
    Log Django admin actions.
    
    Args:
        sender: The LogEntry model
        instance: The LogEntry instance
        created: Whether the instance was created
        **kwargs: Additional keyword arguments
    """
    if not created:
        return
    
    try:
        # Map Django admin action flags to our action types
        action_map = {
            1: 'create',  # ADDITION
            2: 'update',  # CHANGE
            3: 'delete',  # DELETION
        }
        
        action = action_map.get(instance.action_flag, 'admin_action')
        
        Activity.log_activity(
            user=instance.user,
            action='admin_action',
            description=f'Admin action: {instance.change_message}',
            activity_type_code='admin',
            extra_data={
                'admin_action': action,
                'content_type': instance.content_type.model if instance.content_type else None,
                'object_id': instance.object_id,
                'object_repr': instance.object_repr,
                'change_message': instance.change_message,
            }
        )
        
    except Exception as e:
        logger.error(f'Error logging admin action: {e}')


# Security Event Signals
def log_security_event(event_type, severity, description, user=None, 
                      details=None, request=None):
    """
    Utility function to log security events.
    
    Args:
        event_type (str): Type of security event
        severity (str): Severity level
        description (str): Event description
        user (User, optional): User associated with the event
        details (dict, optional): Additional event details
        request (HttpRequest, optional): HTTP request
    """
    try:
        request_info = get_request_info(request)
        
        # Log as activity
        Activity.log_activity(
            user=user,
            action='security_event',
            description=description,
            activity_type_code='security',
            extra_data={
                'event_type': event_type,
                'severity': severity,
                'details': details or {},
            },
            **request_info
        )
        
        # Create security event
        SecurityEvent.objects.create(
            event_type=event_type,
            severity=severity,
            user=user,
            description=description,
            details=details or {},
            **request_info
        )
        
        logger.warning(f'Security event logged: {event_type} - {description}')
        
    except Exception as e:
        logger.error(f'Error logging security event: {e}')


# Custom signal for password changes
def log_password_change(user, request=None):
    """
    Log password change events.
    
    Args:
        user (User): The user who changed their password
        request (HttpRequest, optional): HTTP request
    """
    try:
        request_info = get_request_info(request)
        
        Activity.log_activity(
            user=user,
            action='password_change',
            description=f'User {user.email} changed their password',
            activity_type_code='security',
            **request_info
        )
        
        # Log as security event for monitoring
        log_security_event(
            event_type='password_change',
            severity='low',
            description=f'Password changed for user {user.email}',
            user=user,
            request=request
        )
        
    except Exception as e:
        logger.error(f'Error logging password change: {e}')


# Custom signal for email verification
def log_email_verification(user, request=None):
    """
    Log email verification events.
    
    Args:
        user (User): The user who verified their email
        request (HttpRequest, optional): HTTP request
    """
    try:
        request_info = get_request_info(request)
        
        Activity.log_activity(
            user=user,
            action='email_verify',
            description=f'User {user.email} verified their email address',
            activity_type_code='authentication',
            **request_info
        )
        
    except Exception as e:
        logger.error(f'Error logging email verification: {e}')


# Custom signal for suspicious activity
def log_suspicious_activity(description, user=None, severity='medium', 
                          details=None, request=None):
    """
    Log suspicious activity events.
    
    Args:
        description (str): Description of the suspicious activity
        user (User, optional): User associated with the activity
        severity (str): Severity level
        details (dict, optional): Additional details
        request (HttpRequest, optional): HTTP request
    """
    log_security_event(
        event_type='suspicious_activity',
        severity=severity,
        description=description,
        user=user,
        details=details,
        request=request
    )


# Initialize default activity types
def create_default_activity_types():
    """
    Create default activity types if they don't exist.
    
    This function should be called during app initialization
    to ensure basic activity types are available.
    """
    default_types = [
        {
            'code': 'authentication',
            'name': 'Authentication',
            'description': 'User authentication activities (login, logout, etc.)',
            'is_security_related': True,
            'retention_days': 730,  # 2 years
        },
        {
            'code': 'security',
            'name': 'Security',
            'description': 'Security-related activities and events',
            'is_security_related': True,
            'retention_days': 2555,  # 7 years
        },
        {
            'code': 'model_change',
            'name': 'Model Changes',
            'description': 'Database model create, update, delete operations',
            'is_security_related': False,
            'retention_days': 365,  # 1 year
        },
        {
            'code': 'admin',
            'name': 'Admin Actions',
            'description': 'Django admin interface actions',
            'is_security_related': True,
            'retention_days': 1095,  # 3 years
        },
        {
            'code': 'api',
            'name': 'API Calls',
            'description': 'API endpoint access and operations',
            'is_security_related': False,
            'retention_days': 90,  # 3 months
        },
        {
            'code': 'general',
            'name': 'General',
            'description': 'General application activities',
            'is_security_related': False,
            'retention_days': 365,  # 1 year
        },
    ]
    
    for type_data in default_types:
        ActivityType.objects.get_or_create(
            code=type_data['code'],
            defaults=type_data
        )