"""
Worktable Service - Django Signals.

Signals for record lifecycle events and audit logging.
"""
from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import Signal, receiver

from .models import TableRecord, RecordAuditLog, AuditAction


# Custom signals
record_created = Signal()  # Sent when a record is created
record_updated = Signal()  # Sent when a record is updated
record_deleted = Signal()  # Sent when a record is deleted (soft)
record_restored = Signal()  # Sent when a record is restored
bulk_action_completed = Signal()  # Sent after bulk operations


@receiver(pre_save, sender=TableRecord)
def track_record_changes(sender, instance, **kwargs):
    """
    Track changes before saving for audit log.
    Stores old values in instance._old_data for comparison.
    """
    if instance.pk:
        try:
            old_instance = TableRecord.objects.get(pk=instance.pk)
            instance._old_data = old_instance.data.copy()
            instance._old_status = old_instance.status
            instance._old_is_deleted = old_instance.is_deleted
        except TableRecord.DoesNotExist:
            instance._old_data = None
            instance._old_status = None
            instance._old_is_deleted = None
    else:
        instance._old_data = None
        instance._old_status = None
        instance._old_is_deleted = None


@receiver(post_save, sender=TableRecord)
def handle_record_saved(sender, instance, created, **kwargs):
    """
    Handle record save events.
    - Emit signals for integrations
    - Create audit log if enabled
    """
    # Skip if audit is disabled for this table
    if not instance.table.enable_audit:
        return
    
    # Skip if we're just incrementing version (internal save)
    if hasattr(instance, '_skip_audit') and instance._skip_audit:
        return
    
    # Get request context if available
    request = getattr(instance, '_request', None)
    user = getattr(request, 'user', None) if request else instance.owner
    ip_address = _get_client_ip(request) if request else None
    user_agent = request.META.get('HTTP_USER_AGENT', '') if request else ''
    
    if created:
        # New record created
        action = AuditAction.CREATE
        changes = {'__all__': {'old': None, 'new': instance.data}}
        
        # Emit signal
        record_created.send(
            sender=sender,
            instance=instance,
            user=user,
        )
    else:
        # Check if this is a restore
        old_is_deleted = getattr(instance, '_old_is_deleted', None)
        if old_is_deleted and not instance.is_deleted:
            action = AuditAction.RESTORE
            changes = {'is_deleted': {'old': True, 'new': False}}
            record_restored.send(sender=sender, instance=instance, user=user)
        
        # Check if this is a soft delete
        elif not old_is_deleted and instance.is_deleted:
            action = AuditAction.DELETE
            changes = {'is_deleted': {'old': False, 'new': True}}
            record_deleted.send(sender=sender, instance=instance, user=user)
        
        # Check if status changed
        elif getattr(instance, '_old_status', None) != instance.status:
            action = AuditAction.STATUS_CHANGE
            changes = {
                'status': {
                    'old': getattr(instance, '_old_status', None),
                    'new': instance.status
                }
            }
            record_updated.send(sender=sender, instance=instance, user=user)
        
        # Regular update
        else:
            action = AuditAction.UPDATE
            old_data = getattr(instance, '_old_data', {}) or {}
            changes = _compute_changes(old_data, instance.data)
            
            # Only log if there are actual changes
            if not changes:
                return
            
            record_updated.send(sender=sender, instance=instance, user=user)
    
    # Create audit log
    RecordAuditLog.objects.create(
        table=instance.table,
        record_id=instance.id,
        user=user,
        action=action,
        changes=changes,
        ip_address=ip_address,
        user_agent=user_agent,
    )


def _compute_changes(old_data: dict, new_data: dict) -> dict:
    """
    Compute the differences between old and new data.
    Returns a dict of {field: {old, new}} for changed fields.
    """
    changes = {}
    
    all_keys = set(old_data.keys()) | set(new_data.keys())
    
    for key in all_keys:
        old_value = old_data.get(key)
        new_value = new_data.get(key)
        
        if old_value != new_value:
            changes[key] = {'old': old_value, 'new': new_value}
    
    return changes


def _get_client_ip(request) -> str | None:
    """Extract client IP from request."""
    if not request:
        return None
    
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0].strip()
    else:
        ip = request.META.get('REMOTE_ADDR')
    
    return ip


def create_bulk_audit_log(table, record_ids, action, user, request=None, metadata=None):
    """
    Create audit log for bulk operations.
    
    Args:
        table: TableDefinition instance
        record_ids: List of affected record IDs
        action: AuditAction value
        user: User who performed the action
        request: Optional request object
        metadata: Optional additional metadata
    """
    if not table.enable_audit:
        return
    
    ip_address = _get_client_ip(request) if request else None
    user_agent = request.META.get('HTTP_USER_AGENT', '') if request else ''
    
    # Convert UUIDs to strings for JSON serialization
    record_ids_str = [str(rid) for rid in record_ids]
    
    # Create a single log entry for bulk operations
    RecordAuditLog.objects.create(
        table=table,
        record_id=record_ids[0] if record_ids else None,  # Use first ID as reference
        user=user,
        action=action,
        changes={'affected_records': record_ids_str},
        metadata=metadata or {},
        ip_address=ip_address,
        user_agent=user_agent,
    )
    
    # Emit bulk action signal
    bulk_action_completed.send(
        sender=TableRecord,
        table=table,
        record_ids=record_ids,
        action=action,
        user=user,
    )
