from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal
from threading import local

from django.db import DatabaseError
from django.db.models.fields.files import FieldFile
from django.db.models import Model


_state = local()


AUDITED_APPS = {"accounts", "business", "inventory", "sales", "finance"}


def set_current_user(user):
    if user and getattr(user, "is_authenticated", False):
        _state.user = user
    else:
        _state.user = None


def get_current_user():
    return getattr(_state, "user", None)


def clear_current_user():
    if hasattr(_state, "user"):
        delattr(_state, "user")


def _normalize_value(value):
    if isinstance(value, FieldFile):
        return value.name or ""
    if isinstance(value, Decimal):
        return str(value)
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, Model):
        return str(value.pk)
    if isinstance(value, dict):
        return {k: _normalize_value(v) for k, v in value.items()}
    if isinstance(value, (list, tuple, set)):
        return [_normalize_value(v) for v in value]
    return value


def serialize_instance(instance):
    payload = {}
    for field in instance._meta.fields:
        value = getattr(instance, field.attname, None)
        payload[field.name] = _normalize_value(value)
    return payload


def compute_changes(old_values, new_values):
    changes = {}
    for key in sorted(set(old_values.keys()) | set(new_values.keys())):
        old = old_values.get(key)
        new = new_values.get(key)
        if old != new:
            changes[key] = {"old": old, "new": new}
    return changes


def create_audit_log(action, instance, old_values=None, new_values=None, metadata=None):
    """
    Write an audit row safely; it should never break business operations.
    """

    model = instance.__class__
    if model._meta.app_label not in AUDITED_APPS:
        return
    if model._meta.app_label == "core" and model._meta.model_name == "auditlog":
        return

    old_values = old_values or {}
    new_values = new_values or {}
    metadata = metadata or {}
    changes = compute_changes(old_values, new_values)
    actor = get_current_user()

    try:
        from core.models import AuditLog

        AuditLog.objects.create(
            actor=actor,
            action=action,
            app_label=model._meta.app_label,
            model_name=model._meta.model_name,
            object_id=str(getattr(instance, "pk", "") or ""),
            object_repr=str(instance),
            old_values=old_values,
            new_values=new_values,
            changes=changes,
            metadata=metadata,
        )
    except DatabaseError:
        # Keep operations non-blocking if audit table is unavailable (e.g. pre-migration).
        return
