using ColaFlow.Modules.ProjectManagement.Application.Common.Interfaces;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Entities;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.Interceptors;

/// <summary>
/// EF Core SaveChangesInterceptor that automatically creates audit logs for all entity changes
/// Tracks Create/Update/Delete operations with tenant and user context
/// Phase 2: Field-level change detection with JSON diff
/// </summary>
public class AuditInterceptor(ITenantContext tenantContext) : SaveChangesInterceptor
{
    public override InterceptionResult<int> SavingChanges(
        DbContextEventData eventData,
        InterceptionResult<int> result)
    {
        if (eventData.Context is not null)
        {
            AuditChanges(eventData.Context);
        }

        return base.SavingChanges(eventData, result);
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        if (eventData.Context is not null)
        {
            AuditChanges(eventData.Context);
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }


    private void AuditChanges(DbContext context)
    {
        // Remove try-catch temporarily to see actual errors
        var tenantId = TenantId.From(tenantContext.GetCurrentTenantId());
        var userId = tenantContext.GetCurrentUserId();
        UserId? userIdVO = userId.HasValue ? UserId.From(userId.Value) : null;

            var entries = context.ChangeTracker.Entries()
                .Where(e => e.State == EntityState.Added ||
                           e.State == EntityState.Modified ||
                           e.State == EntityState.Deleted)
                .Where(e => e.Entity is not AuditLog) // Prevent audit log recursion
                .Where(e => IsAuditable(e.Entity))
                .ToList();

            foreach (var entry in entries)
            {
                var entityType = entry.Entity.GetType().Name;
                var entityId = GetEntityId(entry);

                if (entityId == Guid.Empty)
                    continue;

                var action = entry.State switch
                {
                    EntityState.Added => "Create",
                    EntityState.Modified => "Update",
                    EntityState.Deleted => "Delete",
                    _ => "Unknown"
                };

                string? oldValues = null;
                string? newValues = null;

                // Phase 2: Field-level change detection
                try
                {
                    if (entry.State == EntityState.Modified)
                    {
                        // Get only changed scalar properties (excludes primary keys and navigation properties)
                        var changedProperties = entry.Properties
                            .Where(p => p.IsModified &&
                                       !p.Metadata.IsPrimaryKey() &&
                                       !p.Metadata.IsForeignKey() &&
                                       p.Metadata.PropertyInfo != null) // Exclude shadow properties
                            .ToList();

                        if (changedProperties.Any())
                        {
                            var oldDict = new Dictionary<string, object?>();
                            var newDict = new Dictionary<string, object?>();

                            foreach (var prop in changedProperties)
                            {
                                oldDict[prop.Metadata.Name] = SerializableValue(prop.OriginalValue);
                                newDict[prop.Metadata.Name] = SerializableValue(prop.CurrentValue);
                            }

                            oldValues = JsonSerializer.Serialize(oldDict, new JsonSerializerOptions
                            {
                                WriteIndented = false,
                                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                            });
                            newValues = JsonSerializer.Serialize(newDict, new JsonSerializerOptions
                            {
                                WriteIndented = false,
                                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                            });
                        }
                    }
                    else if (entry.State == EntityState.Added)
                    {
                        // For Create, capture all current scalar values (except PK, FK, and shadow properties)
                        var currentDict = new Dictionary<string, object?>();
                        foreach (var prop in entry.Properties.Where(p =>
                            !p.Metadata.IsPrimaryKey() &&
                            !p.Metadata.IsForeignKey() &&
                            p.Metadata.PropertyInfo != null))
                        {
                            currentDict[prop.Metadata.Name] = SerializableValue(prop.CurrentValue);
                        }

                        newValues = JsonSerializer.Serialize(currentDict, new JsonSerializerOptions
                        {
                            WriteIndented = false,
                            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                        });
                    }
                    else if (entry.State == EntityState.Deleted)
                    {
                        // For Delete, capture all original scalar values (except PK, FK, and shadow properties)
                        var originalDict = new Dictionary<string, object?>();
                        foreach (var prop in entry.Properties.Where(p =>
                            !p.Metadata.IsPrimaryKey() &&
                            !p.Metadata.IsForeignKey() &&
                            p.Metadata.PropertyInfo != null))
                        {
                            originalDict[prop.Metadata.Name] = SerializableValue(prop.OriginalValue);
                        }

                        oldValues = JsonSerializer.Serialize(originalDict, new JsonSerializerOptions
                        {
                            WriteIndented = false,
                            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                        });
                    }
                }
                catch (Exception)
                {
                    // If JSON serialization fails, continue without field-level changes
                    // but still create the audit log entry
                }

                var auditLog = AuditLog.Create(
                    tenantId: tenantId,
                    entityType: entityType,
                    entityId: entityId,
                    action: action,
                    userId: userIdVO,
                    oldValues: oldValues,
                    newValues: newValues
                );

                context.Add(auditLog);
            }
    }

    /// <summary>
    /// Determines if an entity should be audited
    /// Currently audits: Project, Epic, Story, WorkTask
    /// </summary>
    private bool IsAuditable(object entity)
    {
        return entity is Project or Epic or Story or WorkTask;
    }

    /// <summary>
    /// Extracts the entity ID from the EF Core change tracker
    /// For Added entities, the ID might be temporary, but we still capture it
    /// </summary>
    private Guid GetEntityId(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entry)
    {
        var idProperty = entry.Properties.FirstOrDefault(p => p.Metadata.Name == "Id");
        if (idProperty?.CurrentValue is Guid id && id != Guid.Empty)
        {
            return id;
        }
        return Guid.Empty;
    }

    /// <summary>
    /// Converts a value to a JSON-serializable format
    /// Handles value objects (TenantId, UserId) by extracting their underlying values
    /// </summary>
    private object? SerializableValue(object? value)
    {
        if (value == null)
            return null;

        // Handle value objects by extracting their Value property
        if (value is TenantId tenantId)
            return tenantId.Value;

        if (value is UserId userId)
            return userId.Value;

        // For other types, return as-is (primitives, strings, etc.)
        return value;
    }
}
