import json
from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict
from .models import AuditLog
from .middleware import get_current_user, get_current_request

# List of models to exclude from auditing
EXCLUDED_MODELS = [
    'AuditLog',
    'Session',
    'LogEntry',
    'Permission',
    'Group',
    'ContentType',
    'Migration',
]

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

def serialize_instance(instance):
    """Serialize instance to JSON-compatible dict with resolved FKs."""
    try:
        data = model_to_dict(instance)
        
        # Enriquecer datos resolviendo ForeignKeys
        for field in instance._meta.fields:
            if field.is_relation and field.many_to_one:
                field_name = field.name
                if field_name in data:
                    try:
                        related_obj = getattr(instance, field_name)
                        if related_obj:
                            # Guardamos "ID - Nombre" o solo Nombre si es preferible
                            # O un objeto pequeño: {"id": X, "label": "Nombre"}
                            # Para simplificar la vista en el audit, usamos string
                            val_str = str(related_obj)
                            data[field_name] = f"{related_obj.pk} - {val_str}"
                    except Exception:
                        pass
                        
        # Use DjangoJSONEncoder to handle dates, decimals, etc.
        serialized_data = json.dumps(data, cls=DjangoJSONEncoder)
        return json.loads(serialized_data)
    except Exception:
        return {}

def get_change_description(action, instance, old_data, new_data):
    """Generate a detailed description of what changed."""
    model_name = instance.__class__.__name__
    
    if action == 'CREATE':
        # For CREATE, show the main identifying field
        identifier = str(instance)
        return f"Created {model_name}: {identifier}"
    
    elif action == 'DELETE':
        identifier = str(instance)
        return f"Deleted {model_name}: {identifier}"
    
    elif action in ['UPDATE', 'CLOSE']:
        # For UPDATE/CLOSE, show what fields changed
        changes = []
        
        for field, new_value in new_data.items():
            old_value = old_data.get(field)
            
            # Skip if values are the same
            if old_value == new_value:
                continue
            
            # Format the change description
            old_str = str(old_value) if old_value is not None else 'None'
            new_str = str(new_value) if new_value is not None else 'None'
            
            # Truncate long values
            if len(old_str) > 50:
                old_str = old_str[:47] + '...'
            if len(new_str) > 50:
                new_str = new_str[:47] + '...'
            
            changes.append(f"{field}: '{old_str}' → '{new_str}'")
        
        if changes:
            identifier = str(instance)
            change_summary = '; '.join(changes[:5])  # Limit to first 5 changes
            if len(changes) > 5:
                change_summary += f' (and {len(changes) - 5} more)'
            return f"Updated {model_name} '{identifier}': {change_summary}"
        else:
            return f"Updated {model_name}: {str(instance)}"
    
    return f"{action} {model_name}: {str(instance)}"


@receiver(pre_save, dispatch_uid="audit_pre_save_handler")
def audit_pre_save(sender, instance, **kwargs):
    if sender.__name__ in EXCLUDED_MODELS:
        return
    
    # Skip if this is being called from within an audit operation
    if getattr(instance, '_audit_in_progress', False):
        return
        
    try:
        if instance.pk:
            try:
                old_instance = sender.objects.get(pk=instance.pk)
                instance._audit_old_data = serialize_instance(old_instance)
            except sender.DoesNotExist:
                instance._audit_old_data = {}
        else:
            instance._audit_old_data = {}
    except Exception:
        pass

@receiver(post_save, dispatch_uid="audit_post_save_handler")
def audit_post_save(sender, instance, created, **kwargs):
    if sender.__name__ in EXCLUDED_MODELS:
        return
    
    # Skip if this is being called from within an audit operation
    if getattr(instance, '_audit_in_progress', False):
        return

    request = get_current_request()
    # En scripts o shell, request puede ser None. 
    # Aun asi, podriamos querer loguear (system actions), o ignorar.
    # El usuario pidio "acciones que ocurren en el crm, en todos los endpoints".
    # Asumimos que si hay request, lo logueamos. Si no, quiza system?
    # Vamos a loguear siempre, user=None si no hay request.

    user = get_current_user()
    
    # Manejar el Proxy de autenticación si existe
    if user and hasattr(user, '_user'):
        user = user._user
        
    ip = get_client_ip(request) if request else None
    
    action = 'CREATE' if created else 'UPDATE'
    
    new_data = serialize_instance(instance)
    old_data = getattr(instance, '_audit_old_data', {})
    
    # If update, calculate differences? 
    # Or just store both full states? Storing diffs saves space but full state is easier.
    # Model allows JSONField. Let's store full state for simplicity or diff if desired.
    # For now: Store full state.
    
    # If UPDATE and no changes, maybe skip?
    if action == 'UPDATE' and new_data == old_data:
        return

    # Generate detailed description
    description = get_change_description(action, instance, old_data, new_data)
    
    # Check for OportunidadDeVenta closure
    if sender.__name__ == 'OportunidadDeVenta' and action == 'UPDATE':
        try:
            old_status_id = old_data.get('estado_oportunidad')
            new_status_id = new_data.get('estado_oportunidad')
            
            if old_status_id != new_status_id:
                # Import here to avoid circular dependencies if any
                from Models.estado_oportunidad_venta import EstadoOportunidadVenta
                new_status = EstadoOportunidadVenta.objects.get(pk=new_status_id)
                if new_status.es_final:
                    action = 'CLOSE'
                    description = f"Opportunity Closed (Status: {new_status.nombre_estado}) - {description}"
        except Exception as e:
            print(f"Error checking opportunity closure: {e}")

    try:
        content_type = ContentType.objects.get_for_model(sender)
        
        # Mark that we're creating an audit log to prevent recursion
        audit_log = AuditLog(
            user=user if user and getattr(user, 'is_authenticated', True) else None,
            action=action,
            ip_address=ip,
            content_type=content_type,
            object_id=instance.pk,
            data_before=old_data if action == 'UPDATE' or action == 'CLOSE' else None,
            data_after=new_data,
            description=description
        )
        audit_log._audit_in_progress = True
        audit_log.save()
    except Exception as e:
        # Avoid crashing the app if logging fails
        print(f"Error auditing {sender.__name__}: {e}")

@receiver(post_delete, dispatch_uid="audit_post_delete_handler")
def audit_post_delete(sender, instance, **kwargs):
    if sender.__name__ in EXCLUDED_MODELS:
        return
    
    # Skip if this is being called from within an audit operation
    if getattr(instance, '_audit_in_progress', False):
        return

    request = get_current_request()
    user = get_current_user()
    
    # Manejar el Proxy de autenticación si existe
    if user and hasattr(user, '_user'):
        user = user._user

    ip = get_client_ip(request) if request else None
    
    old_data = serialize_instance(instance)
    
    # Generate detailed description
    description = get_change_description('DELETE', instance, old_data, {})
    
    try:
        content_type = ContentType.objects.get_for_model(sender)
        
        # Mark that we're creating an audit log to prevent recursion
        audit_log = AuditLog(
            user=user if user and getattr(user, 'is_authenticated', True) else None,
            action='DELETE',
            ip_address=ip,
            content_type=content_type,
            object_id=instance.pk,
            data_before=old_data,
            data_after=None,
            description=description
        )
        audit_log._audit_in_progress = True
        audit_log.save()
    except Exception as e:
        print(f"Error auditing delete {sender.__name__}: {e}")
