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

namespace adas_core.Infrastructure.Utils;

/// <summary>
/// Converts <see cref="PatientObservationAlarm"/> instances to and from JSON representation.
/// </summary>
/// <remarks>
/// Implements <see cref="IBsonSerializer"/> to provide BSON serialization and deserialization support for <see cref="PatientObservationAlarm"/> objects.
/// </remarks>
public class PatientObservationAlarmConverter : JsonConverter<PatientObservationAlarm>, IBsonSerializer
{
    /// <summary>
    /// Deserializes a BSON value into a .NET object using the supplied deserialization context and arguments.
    /// </summary>
    /// <param name="context">The BSON deserialization context that provides access to the reader and configuration.</param>
    /// <param name="args">The deserialization arguments that influence how the value is read and converted.</param>
    /// <returns>The deserialized .NET object produced from the BSON input.</returns>
    /// <exception cref="NotImplementedException">Always thrown because the deserialization logic has not been implemented yet.</exception>
    public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            throw new NotImplementedException();
        }

    /// <summary>
    /// Serializes the specified value to BSON format using the provided serialization context and arguments.
    /// </summary>
    /// <param name="context">The BSON serialization context that holds the writer and configuration for the serialization operation.</param>
    /// <param name="args">The BSON serialization arguments containing additional information that influences the serialization behavior.</param>
    /// <param name="value">The object to be serialized into BSON.</param>
    /// <exception cref="System.NotImplementedException">Thrown because the method has not yet been implemented.</exception>
    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
        {
            throw new NotImplementedException();
        }

    public Type ValueType => typeof(PatientObservationAlarm);

    /// <summary>
    /// Deserializes a JSON token into a <see cref="PatientObservationAlarm"/> instance. Returns <c>null</c> when the JSON token is <see cref="JsonToken.Null"/>, otherwise extracts the <c>value</c> property from the JSON object and constructs a new alarm, falling back to a default <see cref="object"/> when the value is missing.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> positioned at the JSON token to read.</param>
    /// <param name="objectType">The type of the object being deserialized.</param>
    /// <param name="existingValue">The existing value of the object being deserialized, or <c>null</c> if none exists.</param>
    /// <param name="hasExistingValue">Indicates whether <paramref name="existingValue"/> contains a value to be used during deserialization.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used for nested object deserialization.</param>
    /// <returns>A <see cref="PatientObservationAlarm"/> populated from the JSON, or <c>null</c> if the JSON token is <see cref="JsonToken.Null"/>.</returns>
    public override PatientObservationAlarm? ReadJson(JsonReader reader, Type objectType,
            PatientObservationAlarm? existingValue,
            bool hasExistingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
    
            // Implement your custom deserialization logic here
            var jsonObject = JObject.Load(reader);
            // Deserialize properties from jsonObject to PatientObservationAlarm object
    
            // Example: Deserialize 'Value' property
            var value = jsonObject.GetValue("value")?.ToObject<object>();
    
            return new PatientObservationAlarm
            {
                Value = value ?? new object()
                // Other property assignments...
            };
        }

    /// <summary>
    /// Serializes the specified object to JSON, applying the <see cref="StringEnumConverter"/> to the <c>eventPhase</c>, <c>state</c>, <c>priority</c>, and <c>type</c> properties, while excluding the <c>messageTime</c> and <c>expired</c> properties from the output. Does nothing when the <paramref name="value"/> is <see langword="null"/>.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> to which the JSON output is written.</param>
    /// <param name="value">The object to serialize; if <see langword="null"/>, the method performs no action.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used to assist with the conversion.</param>
    public new void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            if (value != null)
            {
                var jo = JObject.FromObject(value);
    
                // Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
                AddStringEnumConverterAttribute(value, "eventPhase");
                AddStringEnumConverterAttribute(value, "state");
                AddStringEnumConverterAttribute(value, "priority");
                AddStringEnumConverterAttribute(value, "type");
    
                jo.Property("messageTime")?.Remove();
                jo.Property("expired")?.Remove();
                jo.WriteTo(writer);
            }
        }

    /// <summary>
    /// Adds a <see cref="StringEnumConverter"/> JSON converter attribute to the specified property of the given object, if one is not already present. Uses reflection to inject the attribute into the property's internal custom attributes array, avoiding the need to recompile the type with the attribute applied.
    /// </summary>
    /// <param name="value">The object instance whose type is inspected to locate the target property.</param>
    /// <param name="propertyName">The name of the property to which the <see cref="StringEnumConverter"/> attribute will 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>
    /// Writes the JSON representation of a PatientObservationAlarm using the specified writer and serializer.
    /// </summary>
    /// <param name="writer">The JsonWriter to which the JSON output is written.</param>
    /// <param name="value">The PatientObservationAlarm value to serialize.</param>
    /// <param name="serializer">The JsonSerializer used during the serialization process.</param>
    /// <exception cref="NotImplementedException">The method has not been implemented.</exception>
    public override void WriteJson(JsonWriter writer, PatientObservationAlarm? value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
}

/*How to use it:
    BsonSerializer.RegisterSerializer(new PatientObservationAlarmConverter());
    JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
        Converters = { new PatientObservationAlarmConverter() }
    };
*/