import json
from django.db.models.signals import pre_save, post_save, post_delete, pre_delete
from django.dispatch import receiver
from django.utils import timezone
from django.core.serializers.json import DjangoJSONEncoder
from .models import Client, ClientLog, User, Company
from .utils import serialize_instance
from django.http import JsonResponse
from django.db.models import Value
from django.db.models.functions import Concat
from users.middleware import get_current_user


# --- Helpers ---
def normalize_value(value):
    if value in [None, "", "None"]:
        return None
    if hasattr(value, "_meta") and hasattr(value, "pk"):
        if value._meta.model_name == "user":
            return value.username
        return value.pk
    if hasattr(value, "name"):
        return value.name or None
    return value

# def build_changes(old_data, new_data):
#     changes = {}
#     for field in new_data.keys():
#         old_val = normalize_value(old_data.get(field)) if old_data else None
#         new_val = normalize_value(new_data.get(field))
#         if old_val != new_val:
#             changes[field] = {"old": old_val, "new": new_val}
#     return changes

def build_changes(old_data, new_data):
    """Compare two dicts and return only real changes. Handles None for new_data (deletion)."""
    changes = {}
    changes_1 = {}

    # Ensure both are dicts
    old_data = old_data or {}
    new_data = new_data or {}

    # Combine keys from both old and new
    all_keys = set(old_data.keys()).union(new_data.keys())

    for field in all_keys:
        old_val = normalize_value(old_data.get(field))
        new_val = normalize_value(new_data.get(field))

        if old_val != new_val:
            changes[field] = {"old": old_val, "new": new_val}

    return changes



# --- Pre-save ---
@receiver(pre_save, sender=Client)
def cache_old_client_data(sender, instance, **kwargs):
    if instance.pk:
        try:
            old_instance = sender.objects.get(pk=instance.pk)
            instance._old_data = serialize_instance(old_instance, exclude_fields=["id"])
            instance._old_status = old_instance.client_status
        except sender.DoesNotExist:
            instance._old_data = None
            instance._old_status = None
    else:
        instance._old_data = None
        instance._old_status = None

# --- Post-save: create/update/status logging ---
# @receiver(post_save, sender=Client)
# def log_client_activity(sender, instance, created, **kwargs):
#     # --- CREATED ---
#     if created:
#         client_name = f"{instance.client_first_name or ''} {instance.client_last_name or ''}".strip()
#         new_data = serialize_instance(instance, exclude_fields=["id"])
#         new_data["client_name"] = client_name

#         ClientLog.objects.create(
#             ref_client_id=instance,
#             ref_table_name=instance._meta.db_table,
#             ref_id=instance.pk,
#             action_type="CREATED",
#             changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
#             performed_by=getattr(instance, "created_by", None),
#             performed_at=timezone.now(),
#         )
#         return  # ✅ stop here: prevents update/status logging

#     # --- UPDATED ---
#     old_data = getattr(instance, "_old_data", None)
#     if old_data:
#         new_data = serialize_instance(instance, exclude_fields=["id"])
#         changes = build_changes(old_data, new_data)
#         for field in ["created_at", "updated_at"]:
#             changes.pop(field, None)
#         if changes:
#             ClientLog.objects.create(
#                 ref_client_id=instance,
#                 ref_table_name=instance._meta.db_table,
#                 ref_id=instance.pk,
#                 action_type="UPDATED",
#                 changed_data=json.loads(json.dumps(changes, cls=DjangoJSONEncoder)),
#                 performed_by=getattr(instance, "updated_by", None),
#                 performed_at=timezone.now(),
#             )

#     # --- STATUS CHANGE ---
#     old_status = getattr(instance, "_old_status", None)
#     new_status = instance.client_status
#     if old_status != new_status:
#         client_name = f"{instance.client_first_name or ''} {instance.client_last_name or ''}".strip()
#         ClientLog.objects.create(
#             ref_client_id=instance,
#             ref_table_name=instance._meta.db_table,
#             ref_id=instance.pk,
#             action_type="STATUS UPDATE",
#             changed_data=json.loads(json.dumps({
#                 "client_name": {"old": f"{instance.client_first_name} {instance.client_last_name}", "new": client_name},
#                 "client_status": {"old": old_status, "new": new_status}
#             }, cls=DjangoJSONEncoder)),
#             performed_by=getattr(instance, "updated_by", None),
#             performed_at=timezone.now(),
#         )



@receiver(post_save, sender=Client)
def log_client_activity(sender, instance, created, **kwargs):
    client_name = f"{instance.client_first_name or ''} {instance.client_last_name or ''}".strip()

    # --- CREATED ---
    if created:
        new_data = serialize_instance(instance, exclude_fields=["client_id"])
        new_data["client_name"] = client_name

        ClientLog.objects.create(
            ref_client_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="CLIENT CREATED",
            changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
            performed_by=instance.created_by,
            performed_at=timezone.now(),
        )
        return  # stop here: no update/status logs

    # --- STATUS CHANGE ---
    old_status = getattr(instance, "_old_status", None)
    new_status = instance.client_status
    status_changed = old_status != new_status

    # --- UPDATED (only if NOT a pure status change) ---
    if not status_changed:  # skip UPDATED log when status changed
        old_data = getattr(instance, "_old_data", None)
        if old_data:
            old_data_filtered = {
                k: v for k, v in old_data.items()
                if k not in ["client_status", "created_at", "updated_at"]
            }
            new_data_filtered = serialize_instance(
                instance, exclude_fields=["id", "client_status", "created_at", "updated_at"]
            )


            changes = build_changes(old_data_filtered, new_data_filtered)
          
            log_data = {
                "description": changes,
                "client_name": client_name
            }
            if changes:
                ClientLog.objects.create(
                    ref_client_id=instance,
                    ref_table_name=instance._meta.db_table,
                    ref_id=instance.pk,
                    action_type="CLIENT UPDATED",
                    changed_data=json.loads(json.dumps(log_data ,cls=DjangoJSONEncoder)),
                    performed_by=get_current_user(),
                    performed_at=timezone.now(),
                )

    # --- STATUS UPDATE ---
    if status_changed:
        ClientLog.objects.create(
            ref_client_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="CLIENT - STATUS UPDATED",
            changed_data=json.loads(json.dumps({
                "client_name": {
                    "old": f"{instance.client_first_name} {instance.client_last_name}",
                    "new": client_name
                },
                "client_status": {"old": old_status, "new": new_status}
            }, cls=DjangoJSONEncoder)),
            performed_by=get_current_user(),
            performed_at=timezone.now(),
        )



# --- Delete logging ---
@receiver(pre_delete, sender=Client)
def log_client_delete(sender, instance, **kwargs):
  
    old_data = serialize_instance(instance, exclude_fields=["id"])
    old_data["client_name"] = f"{normalize_value(instance.client_first_name)} {normalize_value(instance.client_last_name)}".strip()
    old_data["created_by"] = normalize_value(instance.created_by)
    old_data["updated_by"] = normalize_value(instance.updated_by)

    # performed_by = instance.updated_by

    ClientLog.objects.create(
        ref_client_id=None,   # client is gone, keep logs detached
        ref_table_name=instance._meta.db_table,
        ref_id=instance.pk,
        action_type="CLIENT DELETED",
        changed_data=json.loads(json.dumps(build_changes(old_data, None), cls=DjangoJSONEncoder)),
        performed_by=get_current_user(),
        performed_at=timezone.now(),
    )


# --------- For Company ---------

# @receiver(pre_save, sender=Company)
# def cache_old_company(sender, instance, **kwargs):
#     if instance.pk:
#         try:
#             old_instance = Company.objects.get(pk=instance.pk)
#             _old_company_cache[instance.pk] = old_instance
#         except Company.DoesNotExist:
#             _old_company_cache[instance.pk] = None

# @receiver(pre_save, sender=Company)
# def store_old_data_1(sender, instance, **kwargs):
#     if instance.pk:
#         try:
#             old_instance = Company.objects.get(pk=instance.pk)
#             instance._old_data = serialize_instance(old_instance)
#             instance._old_status = old_instance.company_status
#         except Company.DoesNotExist:
#             instance._old_data = {}
#             instance._old_status = None

@receiver(pre_save, sender=Company)
def cache_old_company(sender, instance, **kwargs):
    if instance.pk:
        try:
            old_instance = Company.objects.get(pk=instance.pk)
            instance._old_data = serialize_instance(old_instance)
            instance._old_status = old_instance.company_status
        except Company.DoesNotExist:
            instance._old_data = {}
            instance._old_status = None
    else:
        instance._old_data = {}
        instance._old_status = None

# @receiver(post_save, sender=Company)
# def log_company_activity(sender, instance, created, **kwargs):
#     company_name = instance.company_name
#     # --- CREATED ---
#     if created:
#         new_data = serialize_instance(instance, exclude_fields=["id"])
#         new_data["company_name"] = company_name

#         ClientLog.objects.create(
#             ref_company_id=instance,
#             ref_table_name=instance._meta.db_table,
#             ref_id=instance.pk,
#             action_type="COMPANY CREATED",
#             changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
#             performed_by=instance.created_by,
#             performed_at=timezone.now(),
#         )
#         return  # stop here: no update/status logs

#     # --- STATUS CHANGE ---
#     old_status = getattr(instance, "_old_status", None)
#     # print("old_status", old_status)
#     new_status = instance.company_status
#     # print("new_status", new_status)
#     status_changed = old_status != new_status
#     # print("status_changed", status_changed)

#     # --- UPDATED (only if NOT a pure status change) ---
#     if not status_changed:  # skip UPDATED log when status changed
#         old_data = serialize_instance(instance, exclude_fields=["id"])
#         old_data = getattr(instance, "_old_data", {}) or {}
#         old_data_filtered = {
#             k: v for k, v in old_data.items()
#             if k not in ["id", "company_status", "created_at", "updated_at"]
#             }
#         new_data_filtered = serialize_instance(
#             instance, exclude_fields=["id", "company_status", "created_at", "updated_at","company_name"]
#             )
        
#         changes = build_changes(old_data_filtered, new_data_filtered)

#         # Force company_name in the log
#         changes["company_name"] = {
#             "new": old_data.get("company_name", None),
#             "old": instance.company_name
#         }


#             # changes = build_changes(old_data_filtered, new_data_filtered)
          
#         if changes:
#             ClientLog.objects.create(
#                 ref_company_id=instance,
#                 ref_table_name=instance._meta.db_table,
#                 ref_id=instance.pk,
#                 action_type="COMPANY UPDATED",
#                 changed_data=json.loads(json.dumps(changes,cls=DjangoJSONEncoder)),
#                 performed_by=instance.updated_by,
#                 performed_at=timezone.now(),
#             )

#     # --- STATUS UPDATE ---
#     if status_changed:
#         ClientLog.objects.create(
#             ref_company_id=instance,
#             ref_table_name=instance._meta.db_table,
#             ref_id=instance.pk,
#             action_type="COMPANY - STATUS UPDATED",
#             changed_data=json.loads(json.dumps({
#                 "company_name": {
#                     "old": instance.company_name,
#                     "new": company_name
#                 },
#                 "company_status": {"old": old_status, "new": new_status}
#             }, cls=DjangoJSONEncoder)),
#             performed_by=instance.updated_by,
#             performed_at=timezone.now(),
#         )    

@receiver(post_save, sender=Company)
def log_company_activity(sender, instance, created, **kwargs):
    company_name = instance.company_name
    # --- CREATED ---
    if created:
        new_data = serialize_instance(instance, exclude_fields=["id"])
        new_data["company_name"] = company_name

        ClientLog.objects.create(
            ref_company_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="COMPANY CREATED",
            changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
            performed_by=instance.created_by,
            performed_at=timezone.now(),
        )
        return  # stop here

    # --- STATUS CHANGE ---
    old_status = getattr(instance, "_old_status", None)
    new_status = instance.company_status
    status_changed = old_status != new_status



    old_data = getattr(instance, "_old_data", {}) or {}
    old_data_filtered = {k: v for k, v in old_data.items() if k not in ["id", "company_status", "created_at", "updated_at","is_active"]}
    new_data_filtered = serialize_instance(instance, exclude_fields=["id", "company_status", "created_at", "updated_at","is_active"])
    changes = build_changes(old_data_filtered, new_data_filtered)

    # Log normal updates only if there are changes
    if changes:
        ClientLog.objects.create(
            ref_company_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="COMPANY UPDATED",
            changed_data=json.loads(json.dumps({
                "description": changes,
                "company_name": company_name
            }, cls=DjangoJSONEncoder)),
            performed_by=instance.updated_by,
            performed_at=timezone.now(),
        )

    # Log status change separately
    if status_changed:
        ClientLog.objects.create(
            ref_company_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="COMPANY - STATUS UPDATED",
            changed_data=json.loads(json.dumps({
                "company_name": {
                    "old": instance.company_name,
                    "new": company_name
                },
                "company_status": {"old": old_status, "new": new_status}
            }, cls=DjangoJSONEncoder)),
            performed_by=instance.updated_by,
            performed_at=timezone.now(),
        )


@receiver(post_delete, sender=Company)
def log_company_delete(sender, instance, **kwargs):

    old_data = serialize_instance(instance, exclude_fields=["id"])
    old_data["company_name"] = normalize_value(instance.company_name)
    old_data["created_by"] = normalize_value(instance.created_by)
    old_data["updated_by"] = normalize_value(instance.updated_by)

    performed_by = instance.updated_by

    ClientLog.objects.create(
        ref_company_id=None,   # client is gone, keep logs detached
        ref_table_name=instance._meta.db_table,
        ref_id=instance.pk,
        action_type="COMPANY DELETED",
        changed_data=json.loads(json.dumps(build_changes(old_data, None), cls=DjangoJSONEncoder)),
        performed_by=performed_by,
        performed_at=timezone.now(),
    )


# --------------- For User Log ------------------
@receiver(pre_save, sender=User)
def store_old_data(sender, instance, **kwargs):
    if instance.pk:
        try:
            old_instance = User.objects.get(pk=instance.pk)
            instance._old_data = serialize_instance(old_instance)
            instance._old_status = old_instance.status
            instance._old_last_login = old_instance.last_login
            instance._old_password = old_instance.password
        except User.DoesNotExist:
            instance._old_data = {}
            instance._old_status = None
            instance._old_last_login = None
            # instance._old_password = old_instance.password

@receiver(post_save, sender=User)
def log_user_activity(sender, instance, created, **kwargs):
    username = instance.username

    # --- CREATED ---
    if created:
        new_data = serialize_instance(instance, exclude_fields=["id", "password","is_staff", "first_login", "last_login"])
        new_data["username"] = username

        ClientLog.objects.create(
            ref_user_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="USER CREATED",
            changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
            performed_by=instance.created_by,
            performed_at=timezone.now(),
        )
        return

    # --- STATUS CHANGE ---
    old_status = getattr(instance, "_old_status", None)
    new_status = instance.status
    status_changed = old_status != new_status

    # --- UPDATED (excluding pure status changes) ---
    if not status_changed:
        old_data = serialize_instance(instance, exclude_fields=["id", "password", "is_staff", "first_login", "last_login"])
        old_data = getattr(instance, "_old_data", None) or {}
        old_data_filtered = {
            k: v for k, v in old_data.items()
            if k not in ["id", "status", "created_at", "updated_at", "password", "first_login", "last_login"]
        }
        new_data_filtered = serialize_instance(
            instance, exclude_fields=["id", "status", "created_at", "updated_at", "password", "first_login", "last_login"]
        )

        # Force username into changed_data
        changes = build_changes(old_data_filtered, new_data_filtered)
        changes["username"] = {
            "old": old_data.get("username"),
            "new": instance.username
        }

        if changes:
            ClientLog.objects.create(
                ref_user_id=instance,
                ref_table_name=instance._meta.db_table,
                ref_id=instance.pk,
                action_type="USER UPDATED",
                changed_data=json.loads(json.dumps(changes, cls=DjangoJSONEncoder)),
                performed_by=instance.updated_by,
                performed_at=timezone.now(),
            )

    # --- STATUS UPDATE ---
    if status_changed:
        ClientLog.objects.create(
            ref_user_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="USER - STATUS UPDATED",
            changed_data=json.loads(json.dumps({
                "username": {
                    # "old": old_data.get("username") if 'old_data' in locals() else username,
                    "old": instance.username,
                    "new": username
                },
                "status": {"old": old_status, "new": new_status}
            }, cls=DjangoJSONEncoder)),
            performed_by=instance.updated_by,
            performed_at=timezone.now(),
        )


'''
@receiver(post_save, sender=User)
def log_user_activity(sender, instance, created, **kwargs):
    username = instance.username
    # --- CREATED ---
    if created:
        new_data = serialize_instance(instance, exclude_fields=["id","password"])
        new_data["username"] = username

        ClientLog.objects.create(
            ref_user_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="USER CREATED",
            changed_data=json.loads(json.dumps(build_changes(None, new_data), cls=DjangoJSONEncoder)),
            performed_by=instance.created_by,
            performed_at=timezone.now(),
        )
        print("Created by", instance.created_by)
        return  # stop here: no update/status logs

    # --- STATUS CHANGE ---
    old_status = getattr(instance, "_old_status", None)
    new_status = instance.status
    status_changed = old_status != new_status

    # --- UPDATED (only if NOT a pure status change) ---
    if not status_changed:  # skip UPDATED log when status changed
        old_data = getattr(instance, "_old_data", None)
        old_data_filtered = {
            k: v for k, v in old_data.items()
            if k not in ["id","status", "created_at", "updated_at", "password"]
        }
        new_data_filtered = serialize_instance(
            instance, exclude_fields=["id", "status", "created_at", "updated_at", "password"]
        )

        old_data_filtered["username"] = old_data.get("username")
        new_data_filtered["username"] = instance.username

        changes = build_changes(old_data_filtered, new_data_filtered)
          
        if changes:
            ClientLog.objects.create(
                ref_user_id=instance,
                ref_table_name=instance._meta.db_table,
                ref_id=instance.pk,
                action_type="USER UPDATED",
                changed_data=json.loads(json.dumps(changes,cls=DjangoJSONEncoder)),
                performed_by=instance.updated_by,
                performed_at=timezone.now(),
            )

    # --- STATUS UPDATE ---
    if status_changed:
        ClientLog.objects.create(
            ref_user_id=instance,
            ref_table_name=instance._meta.db_table,
            ref_id=instance.pk,
            action_type="USER - STATUS UPDATED",
            changed_data=json.loads(json.dumps({
                "username": username,  # always include current username
                "status": {"old": old_status, "new": new_status}
            }, cls=DjangoJSONEncoder)),
            performed_by=instance.updated_by,
            performed_at=timezone.now(),
        )
'''


@receiver(post_delete, sender=User)
def log_user_delete(sender, instance, **kwargs):
    old_data = serialize_instance(instance, exclude_fields=["id", "password", "is_staff", "first_login", "last_login"])
    old_data["username"] = normalize_value(instance.username)
    old_data["created_by"] = normalize_value(instance.created_by)
    old_data["updated_by"] = normalize_value(instance.updated_by)

    performed_by = instance.updated_by

    ClientLog.objects.create(
        ref_user_id=None,   # client is gone, keep logs detached
        ref_table_name=instance._meta.db_table,
        ref_id=instance.pk,
        action_type="USER DELETED",
        changed_data=json.loads(json.dumps(build_changes(old_data, None), cls=DjangoJSONEncoder)),
        performed_by=performed_by,
        performed_at=timezone.now(),
    )
