# -*- coding: utf-8 -*-
"""
Adtlas Accounts Signals

This module contains Django signals for the accounts app to handle
automatic actions when user-related events occur.

Features:
    - Auto-create user profiles
    - Log user activities
    - Manage user sessions
    - Handle role assignments
    - Password change tracking

Author: Adtlas Development Team
Version: 2.0.0
Last Updated: 2025-07-08
"""

from django.db.models.signals import post_save, pre_save, post_delete
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.dispatch import receiver
from django.utils import timezone
from django.contrib.sessions.models import Session
from django.contrib.auth import get_user_model

from .models import User, Profile, UserActivity, UserSession, UserRole

User = get_user_model()


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    """
    Create a Profile when a new user is created.
    """
    if created:
        Profile.objects.get_or_create(user=instance)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    """
    Save the Profile when the user is saved.
    """
    if hasattr(instance, "profile"):
        instance.profile.save()


@receiver(pre_save, sender=User)
def track_password_change(sender, instance, **kwargs):
    """
    Track when a user"s password is changed.
    """
    if instance.pk:
        try:
            old_instance = User.objects.get(pk=instance.pk)
            if old_instance.password != instance.password:
                instance.password_changed_at = timezone.now()
                # Reset failed login attempts on password change
                instance.failed_login_attempts = 0
                instance.account_locked_until = None
        except User.DoesNotExist:
            pass


@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
    """
    Log user login activity and create/update session tracking.
    """
    # Get client IP address
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        ip_address = x_forwarded_for.split(",")[0]
    else:
        ip_address = request.META.get("REMOTE_ADDR")
    
    # Get user agent
    user_agent = request.META.get("HTTP_USER_AGENT", "")
    
    # Update user"s last activity
    user.last_activity = timezone.now()
    user.failed_login_attempts = 0  # Reset on successful login
    user.save(update_fields=["last_activity", "failed_login_attempts"])
    
    # Create user activity log
    UserActivity.objects.create(
        user=user,
        action="login",
        ip_address=ip_address,
        user_agent=user_agent,
        details={"login_method": "web"}
    )
    
    # Create or update session tracking
    if hasattr(request, "session"):
        session_key = request.session.session_key
        if session_key:
            session = Session.objects.filter(session_key=session_key).first()
            expires_at = session.expire_date if session else timezone.now() + timezone.timedelta(days=1)
            
            UserSession.objects.update_or_create(
                session_key=session_key,
                defaults={
                    "user": user,
                    "ip_address": ip_address,
                    "user_agent": user_agent,
                    "is_active": True,
                    "last_activity": timezone.now(),
                    "expires_at": expires_at
                }
            )


@receiver(user_logged_out)
def log_user_logout(sender, request, user, **kwargs):
    """
    Log user logout activity and update session tracking.
    """
    if user:
        # Get client IP address
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        if x_forwarded_for:
            ip_address = x_forwarded_for.split(",")[0]
        else:
            ip_address = request.META.get("REMOTE_ADDR")
        
        # Get user agent
        user_agent = request.META.get("HTTP_USER_AGENT", "")
        
        # Create user activity log
        UserActivity.objects.create(
            user=user,
            action="logout",
            ip_address=ip_address,
            user_agent=user_agent,
            details={"logout_method": "web"}
        )
        
        # Mark session as inactive
        if hasattr(request, "session") and request.session.session_key:
            UserSession.objects.filter(
                session_key=request.session.session_key
            ).update(is_active=False)


@receiver(user_login_failed)
def log_failed_login(sender, credentials, request, **kwargs):
    """
    Log failed login attempts and implement account lockout.
    """
    # Get client IP address
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        ip_address = x_forwarded_for.split(",")[0]
    else:
        ip_address = request.META.get("REMOTE_ADDR")
    
    # Get user agent
    user_agent = request.META.get("HTTP_USER_AGENT", "")
    
    # Try to find the user by email/username
    email = credentials.get("email") or credentials.get("username")
    if email:
        try:
            user = User.objects.get(email=email)
            
            # Increment failed login attempts
            user.failed_login_attempts += 1
            
            # Lock account after 5 failed attempts for 30 minutes
            if user.failed_login_attempts >= 5:
                user.account_locked_until = timezone.now() + timezone.timedelta(minutes=30)
            
            user.save(update_fields=["failed_login_attempts", "account_locked_until"])
            
            # Create user activity log
            UserActivity.objects.create(
                user=user,
                action="login",
                ip_address=ip_address,
                user_agent=user_agent,
                details={
                    "success": False,
                    "reason": "invalid_credentials",
                    "failed_attempts": user.failed_login_attempts
                }
            )
        except User.DoesNotExist:
            # Skip logging for unknown emails since UserActivity requires a valid user
            # This prevents IntegrityError when user=None is passed
            # Consider implementing a separate SecurityLog model for tracking
            # failed attempts with non-existent emails if needed
            pass


@receiver(post_save, sender=UserRole)
def log_role_assignment(sender, instance, created, **kwargs):
    """
    Log when roles are assigned to users.
    """
    if created:
        UserActivity.objects.create(
            user=instance.user,
            action="admin",
            object_type="UserRole",
            object_id=str(instance.id),
            object_repr=str(instance),
            details={
                "action": "role_assigned",
                "role": instance.role.name,
                "role_code": instance.role.code,
                "assigned_by": instance.assigned_by.email if instance.assigned_by else None
            }
        )


@receiver(post_delete, sender=UserRole)
def log_role_removal(sender, instance, **kwargs):
    """
    Log when roles are removed from users.
    """
    UserActivity.objects.create(
        user=instance.user,
        action="admin",
        object_type="UserRole",
        object_id=str(instance.id),
        object_repr=str(instance),
        details={
            "action": "role_removed",
            "role": instance.role.name,
            "role_code": instance.role.code
        }
    )


def cleanup_expired_sessions():
    """
    Utility function to clean up expired sessions.
    This can be called by a management command or celery task.
    """
    expired_sessions = UserSession.objects.filter(
        expires_at__lt=timezone.now(),
        is_active=True
    )
    
    count = expired_sessions.count()
    expired_sessions.update(is_active=False)
    
    return count


def cleanup_old_activities(days=90):
    """
    Utility function to clean up old activity logs.
    Keep only activities from the last X days.
    """
    cutoff_date = timezone.now() - timezone.timedelta(days=days)
    deleted_count, _ = UserActivity.objects.filter(
        created_at__lt=cutoff_date
    ).delete()
    
    return deleted_count
