using System.Text.Json;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;

namespace ColaFlow.Modules.Mcp.Domain.Services;

/// <summary>
/// Domain service for creating and comparing diff previews
/// </summary>
public sealed class DiffPreviewService
{
    /// <summary>
    /// Generate a diff preview for a CREATE operation
    /// </summary>
    public DiffPreview GenerateCreateDiff<T>(
        string entityType,
        T afterEntity,
        string? entityKey = null) where T : class
    {
        if (afterEntity == null)
            throw new ArgumentNullException(nameof(afterEntity));

        var afterData = JsonSerializer.Serialize(afterEntity, new JsonSerializerOptions
        {
            WriteIndented = true
        });

        return DiffPreview.ForCreate(
            entityType: entityType,
            afterData: afterData,
            entityKey: entityKey
        );
    }

    /// <summary>
    /// Generate a diff preview for a DELETE operation
    /// </summary>
    public DiffPreview GenerateDeleteDiff<T>(
        string entityType,
        Guid entityId,
        T beforeEntity,
        string? entityKey = null) where T : class
    {
        if (beforeEntity == null)
            throw new ArgumentNullException(nameof(beforeEntity));

        var beforeData = JsonSerializer.Serialize(beforeEntity, new JsonSerializerOptions
        {
            WriteIndented = true
        });

        return DiffPreview.ForDelete(
            entityType: entityType,
            entityId: entityId,
            beforeData: beforeData,
            entityKey: entityKey
        );
    }

    /// <summary>
    /// Generate a diff preview for an UPDATE operation by comparing two objects
    /// </summary>
    public DiffPreview GenerateUpdateDiff<T>(
        string entityType,
        Guid entityId,
        T beforeEntity,
        T afterEntity,
        string? entityKey = null) where T : class
    {
        if (beforeEntity == null)
            throw new ArgumentNullException(nameof(beforeEntity));

        if (afterEntity == null)
            throw new ArgumentNullException(nameof(afterEntity));

        // Serialize both entities
        var beforeData = JsonSerializer.Serialize(beforeEntity, new JsonSerializerOptions
        {
            WriteIndented = true
        });

        var afterData = JsonSerializer.Serialize(afterEntity, new JsonSerializerOptions
        {
            WriteIndented = true
        });

        // Compare and find changed fields
        var changedFields = CompareObjects(beforeEntity, afterEntity);

        if (changedFields.Count == 0)
            throw new InvalidOperationException(
                "No fields have changed. UPDATE operation requires at least one changed field.");

        return DiffPreview.ForUpdate(
            entityType: entityType,
            entityId: entityId,
            beforeData: beforeData,
            afterData: afterData,
            changedFields: changedFields.AsReadOnly(),
            entityKey: entityKey
        );
    }

    /// <summary>
    /// Compare two objects and return list of changed fields
    /// Uses reflection to compare public properties
    /// </summary>
    private List<DiffField> CompareObjects<T>(T before, T after) where T : class
    {
        var changedFields = new List<DiffField>();
        var type = typeof(T);
        var properties = type.GetProperties();

        foreach (var property in properties)
        {
            // Skip non-readable properties
            if (!property.CanRead)
                continue;

            // Skip indexed properties
            if (property.GetIndexParameters().Length > 0)
                continue;

            var oldValue = property.GetValue(before);
            var newValue = property.GetValue(after);

            // Check if values are different
            if (!AreValuesEqual(oldValue, newValue))
            {
                changedFields.Add(new DiffField(
                    fieldName: property.Name,
                    displayName: FormatDisplayName(property.Name),
                    oldValue: oldValue,
                    newValue: newValue
                ));
            }
        }

        return changedFields;
    }

    /// <summary>
    /// Compare two values for equality
    /// Handles nulls and uses Equals method
    /// </summary>
    private bool AreValuesEqual(object? oldValue, object? newValue)
    {
        if (oldValue == null && newValue == null)
            return true;

        if (oldValue == null || newValue == null)
            return false;

        return oldValue.Equals(newValue);
    }

    /// <summary>
    /// Format property name to display name
    /// Example: "FirstName" -> "First Name"
    /// </summary>
    private string FormatDisplayName(string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
            return propertyName;

        // Insert space before uppercase letters (except first letter)
        var result = new System.Text.StringBuilder();
        for (int i = 0; i < propertyName.Length; i++)
        {
            if (i > 0 && char.IsUpper(propertyName[i]))
                result.Append(' ');

            result.Append(propertyName[i]);
        }

        return result.ToString();
    }

    /// <summary>
    /// Validate that a diff preview is valid for the operation
    /// </summary>
    public bool ValidateDiff(DiffPreview diff)
    {
        if (diff == null)
            return false;

        try
        {
            // Operation-specific validation
            if (diff.IsCreate() && string.IsNullOrWhiteSpace(diff.AfterData))
                return false;

            if (diff.IsUpdate() && diff.ChangedFields.Count == 0)
                return false;

            if (diff.IsUpdate() && diff.EntityId == null)
                return false;

            if (diff.IsDelete() && diff.EntityId == null)
                return false;

            if (diff.IsDelete() && string.IsNullOrWhiteSpace(diff.BeforeData))
                return false;

            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Generate HTML diff for changed fields in a diff preview
    /// Adds visual highlighting for additions, deletions, and modifications
    /// </summary>
    public string GenerateHtmlDiff(DiffPreview diff)
    {
        if (diff == null)
            throw new ArgumentNullException(nameof(diff));

        var html = new System.Text.StringBuilder();
        html.AppendLine("<div class=\"diff-preview\">");
        html.AppendLine($"  <div class=\"diff-header\">");
        html.AppendLine($"    <span class=\"diff-operation {diff.Operation.ToLowerInvariant()}\">{diff.Operation}</span>");
        html.AppendLine($"    <span class=\"diff-entity-type\">{diff.EntityType}</span>");

        if (diff.EntityKey != null)
            html.AppendLine($"    <span class=\"diff-entity-key\">{System.Net.WebUtility.HtmlEncode(diff.EntityKey)}</span>");

        html.AppendLine($"  </div>");

        if (diff.IsCreate())
        {
            html.AppendLine($"  <div class=\"diff-section\">");
            html.AppendLine($"    <h4>New {diff.EntityType}</h4>");
            html.AppendLine($"    <pre class=\"diff-added\">{System.Net.WebUtility.HtmlEncode(diff.AfterData ?? "")}</pre>");
            html.AppendLine($"  </div>");
        }
        else if (diff.IsDelete())
        {
            html.AppendLine($"  <div class=\"diff-section\">");
            html.AppendLine($"    <h4>Deleted {diff.EntityType}</h4>");
            html.AppendLine($"    <pre class=\"diff-removed\">{System.Net.WebUtility.HtmlEncode(diff.BeforeData ?? "")}</pre>");
            html.AppendLine($"  </div>");
        }
        else if (diff.IsUpdate())
        {
            html.AppendLine($"  <div class=\"diff-section\">");
            html.AppendLine($"    <h4>Changed Fields ({diff.ChangedFields.Count})</h4>");
            html.AppendLine($"    <table class=\"diff-table\">");
            html.AppendLine($"      <thead>");
            html.AppendLine($"        <tr>");
            html.AppendLine($"          <th>Field</th>");
            html.AppendLine($"          <th>Old Value</th>");
            html.AppendLine($"          <th>New Value</th>");
            html.AppendLine($"        </tr>");
            html.AppendLine($"      </thead>");
            html.AppendLine($"      <tbody>");

            foreach (var field in diff.ChangedFields)
            {
                html.AppendLine($"        <tr>");
                html.AppendLine($"          <td class=\"diff-field-name\">{System.Net.WebUtility.HtmlEncode(field.DisplayName)}</td>");
                html.AppendLine($"          <td class=\"diff-old-value\">");
                html.AppendLine($"            <span class=\"diff-removed\">{System.Net.WebUtility.HtmlEncode(FormatValue(field.OldValue))}</span>");
                html.AppendLine($"          </td>");
                html.AppendLine($"          <td class=\"diff-new-value\">");
                html.AppendLine($"            <span class=\"diff-added\">{System.Net.WebUtility.HtmlEncode(FormatValue(field.NewValue))}</span>");
                html.AppendLine($"          </td>");
                html.AppendLine($"        </tr>");
            }

            html.AppendLine($"      </tbody>");
            html.AppendLine($"    </table>");
            html.AppendLine($"  </div>");
        }

        html.AppendLine("</div>");
        return html.ToString();
    }

    /// <summary>
    /// Format a value for display in HTML
    /// Handles nulls, dates, and truncates long strings
    /// </summary>
    private string FormatValue(object? value)
    {
        if (value == null)
            return "(null)";

        if (value is DateTime dateTime)
            return dateTime.ToString("yyyy-MM-dd HH:mm:ss UTC");

        if (value is DateTimeOffset dateTimeOffset)
            return dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss UTC");

        var stringValue = value.ToString() ?? "";

        // Truncate long strings
        const int maxLength = 500;
        if (stringValue.Length > maxLength)
            return stringValue.Substring(0, maxLength) + "... (truncated)";

        return stringValue;
    }
}
