from django.db import models
from django.utils import timezone
from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save, post_delete
from django.forms.models import model_to_dict

class AuditLog(models.Model):
    ACTION_CREATE = "create"
    ACTION_UPDATE = "update"
    ACTION_DELETE = "delete"
    ACTIONS = [
        (ACTION_CREATE, "Created"),
        (ACTION_UPDATE, "Updated"),
        (ACTION_DELETE, "Deleted"),
    ]

    entry_id = models.IntegerField(db_index=True)
    action = models.CharField(max_length=10, choices=ACTIONS, db_index=True)
    username = models.CharField(max_length=255, blank=True, default="")
    timestamp = models.DateTimeField(default=timezone.now, db_index=True)
    # For updates: {"field": ["old","new"], ...}
    # For creates: {"__created__": {<field>: <value>, ...}}
    # For deletes: {"__deleted__": {<field>: <value>, ...}}
    changes = models.JSONField(null=True, blank=True)

    class Meta:
        ordering = ["-timestamp"]
        indexes = [
            models.Index(fields=["timestamp"]),
            models.Index(fields=["action"]),
            models.Index(fields=["entry_id"]),
        ]

    def __str__(self):
        return f"{self.timestamp:%Y-%m-%d %H:%M:%S} {self.action} #{self.entry_id} by {self.username or '-'}"


# ---- helpers for diffs (lazy: derive from sender) ----

def _tracked_field_names(sender):
    """Return concrete, editable, non-PK field names for the given model."""
    names = []
    for f in sender._meta.get_fields():
        if getattr(f, "many_to_many", False):
            continue
        if not getattr(f, "concrete", False):
            continue
        if getattr(f, "primary_key", False):
            continue
        if getattr(f, "editable", True) is False:
            continue
        names.append(f.name)
    return names

def _serialize_instance(sender, obj):
    """Serialize only tracked fields; stringify values for safety."""
    fields = _tracked_field_names(sender)
    d = model_to_dict(obj, fields=fields)
    for k, v in d.items():
        d[k] = "" if v is None else str(v)
    return d

def _is_core_entry(sender):
    """True iff the signal sender is core.Entry (no import-time lookups)."""
    return getattr(sender, "_meta", None) and sender._meta.label_lower == "core.entry"


# ---- current user from middleware ----
from .middleware import get_current_username


# ---- signals (note: no sender=... at decorator time) ----

@receiver(pre_save)
def _audit_entry_pre_save(sender, instance, **kwargs):
    if not _is_core_entry(sender):
        return
    # For updates, stash old snapshot on the instance
    if instance.pk:
        try:
            existing = sender.objects.get(pk=instance.pk)
            instance.__audit_old__ = _serialize_instance(sender, existing)
        except sender.DoesNotExist:
            instance.__audit_old__ = None
    else:
        instance.__audit_old__ = None


@receiver(post_save)
def _audit_entry_save(sender, instance, created, **kwargs):
    if not _is_core_entry(sender):
        return
    from .models_audit import AuditLog  # local import to avoid circulars
    if created:
        AuditLog.objects.create(
            entry_id=instance.id,
            action=AuditLog.ACTION_CREATE,
            username=get_current_username(),
            changes={"__created__": _serialize_instance(sender, instance)},
        )
    else:
        old = getattr(instance, "__audit_old__", None)
        new = _serialize_instance(sender, instance)
        if old is None:
            changes = {"__created__": new}
            action = AuditLog.ACTION_CREATE
        else:
            diffs = {}
            for field in _tracked_field_names(sender):
                ov = old.get(field, "")
                nv = new.get(field, "")
                if ov != nv:
                    diffs[field] = [ov, nv]
            changes = diffs or None
            action = AuditLog.ACTION_UPDATE
        AuditLog.objects.create(
            entry_id=instance.id,
            action=action,
            username=get_current_username(),
            changes=changes,
        )


@receiver(post_delete)
def _audit_entry_delete(sender, instance, **kwargs):
    if not _is_core_entry(sender):
        return
    from .models_audit import AuditLog
    AuditLog.objects.create(
        entry_id=instance.id,
        action=AuditLog.ACTION_DELETE,
        username=get_current_username(),
        changes={"__deleted__": _serialize_instance(sender, instance)},
    )