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 JSON conversion for the <see cref="Person"/> type via <see cref="JsonConverter{T}"/> and also implements BSON serialization through <see cref="IBsonSerializer"/>.
/// </summary>
/// <remarks>
/// The primary constructor accepts a <see cref="Type"/> that represents the value type associated with this converter.
/// </remarks>
public class PersonConverter(Type valueType) : JsonConverter<Person>, IBsonSerializer
{
    /// <summary>
    /// Deserializes a BSON value into a .NET object using the provided context and arguments.
    /// </summary>
    /// <param name="context">The BSON deserialization context.</param>
    /// <param name="args">The BSON deserialization arguments.</param>
    /// <returns>The deserialized object.</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 object to BSON format using the provided serialization context and arguments. The method is not implemented and always throws an exception.
    /// </summary>
    /// <param name="context">The BSON serialization context that provides the writer and configuration for the operation.</param>
    /// <param name="args">The BSON serialization arguments containing additional settings for the serialization.</param>
    /// <param name="value">The object to be serialized to BSON.</param>
    /// <exception cref="NotImplementedException">Always thrown because the method has not been implemented yet.</exception>
    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
        {
            throw new NotImplementedException();
        }

    public Type ValueType { get; } = valueType;

    /// <summary>
    /// Serializes a <see cref="Person"/> instance to JSON, ensuring the <c>Gender</c> property is written using string-based enum representation. Does nothing when the provided value is <see langword="null"/>.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> to which the JSON representation is written.</param>
    /// <param name="value">The <see cref="Person"/> instance to serialize. If <see langword="null"/>, the method returns without writing anything.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> used during the conversion to a <see cref="JObject"/>.</param>
    public override void WriteJson(JsonWriter writer, Person? value, JsonSerializer serializer)
        {
            if (value == null) return;
    
            var jo = JObject.FromObject(value);
    
            // Add [JsonConverter(typeof(StringEnumConverter))] attribute to specified properties
            AddStringEnumConverterAttribute(value, "Gender");
    
            jo.WriteTo(writer);
        }

    /// <summary>
    /// Adds a StringEnumConverter JSON converter attribute to the specified property if one is not already applied, ensuring enum values are serialized as strings. The method returns silently if the property is not found on the value's type or if a JsonConverterAttribute is already present.
    /// </summary>
    /// <param name="value">The object instance whose property's attributes will be inspected and modified.</param>
    /// <param name="propertyName">The name of the property to which the converter 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) return;
            var attrs = prop.GetCustomAttributes(false);
    
            // Check if the attribute is not already applied
            if (Array.Find(attrs, a => a is JsonConverterAttribute) != null) return;
    
            // 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 a <see cref="Person"/> object. This override is not yet implemented.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
    /// <param name="objectType">The type of the object to deserialize.</param>
    /// <param name="existingValue">The existing value of the object being read, or <c>null</c> if there is no existing value.</param>
    /// <param name="hasExistingValue">A value indicating whether <paramref name="existingValue"/> has a value.</param>
    /// <param name="serializer">The <see cref="JsonSerializer"/> calling the method.</param>
    /// <returns>A <see cref="Person"/> instance deserialized from the JSON.</returns>
    /// <exception cref="NotImplementedException">Always thrown because the method has not been implemented.</exception>
    public override Person ReadJson(JsonReader reader, Type objectType, Person? existingValue, bool hasExistingValue,
            JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
}