using System.Reflection;
using adas_core.Domain.Enums;
using MongoDB.Bson;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;

namespace adas_core.Domain.Models.MongoModels;

/// <summary>
/// Represents a custom JSON converter for serializing and deserializing historical configuration changes.
/// </summary>
/// <remarks>
/// This converter extends <see cref="JsonConverter"/> to provide tailored JSON conversion behavior for historical config change data.
/// </remarks>
public class HistoricalConfigChanges : JsonConverter
{
    public DateTime Time;

    public string Username = string.Empty;

    public ObjectId Id { get; set; }

    public DisplayConfigEnums.ConfigTypes? ConfigType { get; set; }

    public string? OldConfig { get; set; }

    public string NewConfig { get; set; } = string.Empty;

    /// <summary>
    /// Serializes the specified object to JSON, skipping output when the value is <c>null</c>.
    /// The object is first projected into a <see cref="JObject"/> and annotated with a string enum converter for the <c>ConfigType</c> property before being written to the <paramref name="writer"/>.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> that receives the serialized JSON output.</param>
    /// <param name="value">The object to serialize. If <c>null</c>, the method does nothing.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used during serialization.</param>
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            if (value != null)
            {
                var jo = JObject.FromObject(value);
    
                AddStringEnumConverterAttribute(value, nameof(ConfigType));
                jo.WriteTo(writer);
            }
        }

    /// <summary>
    /// Adds a <see cref="JsonConverterAttribute"/> using <see cref="StringEnumConverter"/> to the specified property via reflection, but only if the property does not already have a <see cref="JsonConverterAttribute"/> applied. The new attribute is appended to the existing custom attributes array using a non-public field on <see cref="PropertyInfo"/>.
    /// </summary>
    /// <param name="value">The object instance whose type defines the target property.</param>
    /// <param name="propertyName">The name of the property to which the converter attribute should be added.</param>
    private static void AddStringEnumConverterAttribute(object value, string propertyName)
        {
            var prop = value.GetType().GetProperty(propertyName);
            var attr = new JsonConverterAttribute(typeof(StringEnumConverter));
    
            if (prop != null)
            {
                var attrs = prop.GetCustomAttributes(false);
    
                // Check if the attribute is not already applied
                if (Array.Find(attrs, a => a is JsonConverterAttribute) == null)
                {
                    // Create a new array that includes the existing attributes and the new one
                    var newAttrs = new object[attrs.Length + 1];
                    Array.Copy(attrs, newAttrs, attrs.Length);
                    newAttrs[attrs.Length] = attr;
    
                    // Use reflection to set the new attributes array
                    var field = typeof(PropertyInfo).GetField("m_customAttributes",
                        BindingFlags.Instance | BindingFlags.NonPublic);
                    field?.SetValue(prop, newAttrs);
                }
            }
        }

    /// <summary>
    /// Reads the JSON representation of the object and converts it to the target type as part of a custom <see cref="JsonConverter"/>. This implementation is not yet provided.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> used to read the JSON content.</param>
    /// <param name="objectType">The type of the object to deserialize to.</param>
    /// <param name="existingValue">The existing value of the object being read, or <c>null</c> if no value exists.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used to deserialize nested objects.</param>
    /// <returns>The deserialized object value.</returns>
    /// <exception cref="NotImplementedException">Always thrown because the method has not been implemented.</exception>
    public override object ReadJson(JsonReader reader, Type objectType, object? existingValue,
            JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

    /// <summary>
    /// Determines whether the converter can convert the specified type. The override has not been implemented and always throws a <see cref="NotImplementedException"/> when invoked.
    /// </summary>
    /// <param name="objectType">The type to evaluate for convertibility.</param>
    /// <returns><see langword="true"/> when the converter supports <paramref name="objectType"/>; otherwise, <see langword="false"/>.</returns>
    /// <exception cref="NotImplementedException">Thrown in all cases because the method body is not implemented.</exception>
    public override bool CanConvert(Type objectType)
        {
            throw new NotImplementedException();
        }
}