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>
/// Provides conversion logic for <see cref="PatientObservation"/> values, supporting JSON serialization through <see cref="JsonConverter{T}"/> and BSON serialization through <see cref="IBsonSerializer"/>.
/// </summary>
/// <remarks>
/// The optional <see cref="Type"/> constructor parameter specifies the target value type used during conversion.
/// </remarks>
public class PatientObservationConverter(Type? valueType) : JsonConverter<PatientObservation>, IBsonSerializer
{
    /// <summary>
    /// Deserializes a BSON value using the provided deserialization context and arguments.
    /// </summary>
    /// <param name="context">The deserialization context that provides the BSON reader and configuration.</param>
    /// <param name="args">The deserialization arguments containing additional information for the operation.</param>
    /// <returns>An object representing the deserialized value.</returns>
    /// <exception cref="NotImplementedException">Always thrown because the method is not yet implemented.</exception>
    public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            throw new NotImplementedException();
        }

    /// <summary>
    /// Serializes the specified value into a BSON format using the provided serialization context and arguments. This implementation is not yet provided and currently throws a <see cref="NotImplementedException"/>.
    /// </summary>
    /// <param name="context">The BSON serialization context that provides access to the writer and configuration used during serialization.</param>
    /// <param name="args">The BSON serialization arguments containing additional information that may influence the serialization process.</param>
    /// <param name="value">The object to be serialized into BSON.</param>
    /// <exception cref="NotImplementedException">Thrown because the serialization logic has not been implemented.</exception>
    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
        {
            throw new NotImplementedException();
        }

    public Type? ValueType { get; } = valueType;

    /// <summary>
    /// Serializes a <see cref="PatientObservation"/> instance to JSON, applying a string enum converter to the <c>Status</c> property and excluding the <c>MessageTime</c> property from the output. If the supplied value is <c>null</c>, nothing is written to the <paramref name="writer"/>.
    /// </summary>
    /// <param name="writer">The JSON writer that receives the serialized output.</param>
    /// <param name="value">The patient observation to serialize. When <c>null</c>, the method performs no serialization.</param>
    /// <param name="serializer">The JSON serializer used during the conversion process.</param>
    public override void WriteJson(JsonWriter writer, PatientObservation? value, JsonSerializer serializer)
        {
            if (value != null)
            {
                var jo = JObject.FromObject(value);
    
                // Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
                AddStringEnumConverterAttribute(value, "Status");
    
                jo.Property("MessageTime")?.Remove();
                jo.WriteTo(writer);
            }
        }

    /// <summary>
    /// Adds a JsonConverterAttribute for StringEnumConverter to the specified property if one is not already present. If the property cannot be found on the object's type, the method does nothing.
    /// </summary>
    /// <param name="value">The object instance whose property should be annotated.</param>
    /// <param name="propertyName">The name of the property to which the StringEnumConverter 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>
    /// Deserializes a JSON representation into a <see cref="PatientObservation"/> instance.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> to read the JSON from.</param>
    /// <param name="objectType">The type of the object being deserialized.</param>
    /// <param name="existingValue">The existing value of the object to populate, or <see langword="null"/> if none.</param>
    /// <param name="hasExistingValue">Indicates whether <paramref name="existingValue"/> contains a value to reuse.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used to read nested values.</param>
    /// <returns>A <see cref="PatientObservation"/> instance produced from the JSON.</returns>
    /// <exception cref="NotImplementedException">Thrown because the method has not been implemented yet.</exception>
    public override PatientObservation ReadJson(JsonReader reader, Type objectType, PatientObservation? existingValue,
            bool hasExistingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
}