using System.Collections;
using System.Text.RegularExpressions;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Masters;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json;
using JsonConvert = Newtonsoft.Json.JsonConvert;

namespace adas_core.Domain.Utils;

/// <summary>
/// Represents a serializer for complex object value types, deriving from <see cref="SerializerBase{T}"/> with <see cref="object"/> as the type argument.
/// </summary>
/// <remarks>
/// Declared as a partial class, allowing its definition to be split across multiple files.
/// </remarks>
public partial class ComplexObjectValueTypeSerializer : SerializerBase<object>
{
    /// <summary>
    /// Provides a compiled regular expression for matching MongoDB ObjectId references, such as <c>ObjectId("...")</c>, that wrap a 24-character hexadecimal identifier.
    /// </summary>
    /// <returns>A <see cref="Regex"/> that matches strings containing an <c>ObjectId</c> call with a 24-character hexadecimal value enclosed in delimiters.</returns>
    [GeneratedRegex("ObjectId\\((.[a-f0-9]{24}.)\\)")]
        private static partial Regex ObjectIdRegex();

    public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        try
        {
            switch (context.Reader.GetCurrentBsonType())
            {
                case BsonType.Null:
                    context.Reader.ReadNull();
                    return false;
                case BsonType.Boolean: return context.Reader.ReadBoolean();
                case BsonType.Double: return context.Reader.ReadDouble();
                case BsonType.String: return context.Reader.ReadString();
                case BsonType.Int32: return context.Reader.ReadInt32();
                case BsonType.Int64: return context.Reader.ReadInt64();
                case BsonType.DateTime: return context.Reader.ReadDateTime();
                case BsonType.Decimal128: return context.Reader.ReadDecimal128();
                case BsonType.ObjectId: return context.Reader.ReadObjectId();
                case BsonType.EndOfDocument:
                    context.Reader.ReadEndDocument();
                    return false;
                case BsonType.Binary: return context.Reader.ReadBytes();
                case BsonType.Undefined:
                    context.Reader.ReadUndefined();
                    return false;
                case BsonType.RegularExpression: return context.Reader.ReadRegularExpression();
                case BsonType.JavaScript: return context.Reader.ReadJavaScript();
                case BsonType.Symbol: return context.Reader.ReadSymbol();
                case BsonType.JavaScriptWithScope: return context.Reader.ReadJavaScriptWithScope();
                case BsonType.Timestamp: return context.Reader.ReadTimestamp();
                case BsonType.MinKey:
                    context.Reader.ReadMinKey();
                    return false;
                case BsonType.MaxKey:
                    context.Reader.ReadMaxKey();
                    return false;
                case BsonType.Document:
                    //default:
                {
                    //if (context.Reader.GetCurrentBsonType() != BsonType.Document)
                    //    throw new Exception("Unexpected BsonType: " + context.Reader.GetCurrentBsonType());
                    var bsonDocument =
                        BsonSerializer.Deserialize(context.Reader, typeof(BsonDocument)) as BsonDocument ??
                        throw new Exception("BsonDocument is null");
                    var t = bsonDocument["_t"].AsString ??
                            throw new Exception("Can't deserialize object. No type found.");
                    var isArray = t.EndsWith("]");

                    string dirtyJson;
                    if (isArray)
                    {
                        if (!bsonDocument.TryGetValue("_v", out var arrayValue))
                            throw new Exception("Can't deserialize object. No array value found.");
                        dirtyJson = arrayValue.ToJson();
                        bsonDocument.Remove("_v");

                        if (t.Contains("String[]"))
                            t = "System.String[], mscorlib";
                    }
                    else
                    {
                        if (!t.Contains('.'))
                            t = $"adas-core.Domain.Models.{t}";
                        if (t.Contains(
                                "adas-core.Models")) //esta comprobación se hace porque al cambiar a clean code en la BBDD se estaba guardando el espacio de nombres antiguo
                            t = t.Replace("adas-core.Models", "adas-core.Domain.Models");
                        bsonDocument.Remove("_t");

                        dirtyJson = bsonDocument.ToJson();
                    }
                    var settings = new JsonSerializerSettings
                    {
                        Converters = { new ObjectIdConverter() }
                    };
                    var cleanJson = ObjectIdRegex().Replace(dirtyJson, m => m.Groups[1].Value);

                    var type = Type.GetType(t) ?? throw new Exception($"Type not found: {t}");

                    return JsonConvert.DeserializeObject(cleanJson, type, settings) ??
                           throw new Exception("Can't deserialize object");
                }
                case BsonType.Array:
                {
                    var bsonArray = BsonSerializer.Deserialize<BsonArray>(context.Reader);
                    var dirtyJsonArray = bsonArray.ToJson(new JsonWriterSettings
                    {
                        OutputMode = JsonOutputMode.CanonicalExtendedJson
                    });

                    // Convertir el BsonArray completo a una cadena JSON (dirtyJsonArray contiene {"$oid": "..."})
                    //var dirtyJsonArray = bsonArray.ToJson();

                    // Obtener el tipo de los valores del array (necesitas esto incluso para el caso vacío)
                    var elementType = GetElementTypeFromBsonArray(bsonArray);

                    if (bsonArray.Count == 0)
                    {
                        // Caso Array Vacío
                        // Si el tipo se pudo determinar (p. ej., si la información está en metadatos), usa ese tipo.
                        // Si no, devuelve una List<object> vacía.
                        if (elementType == null) return new List<object>();

                        var elementTypeListEmpty = typeof(List<>).MakeGenericType(elementType);
                        return Activator.CreateInstance(elementTypeListEmpty)!;
                    }

                    // El array no está vacío. Ahora revisamos el tipo.
                    if (elementType == null)
                        throw new Exception("No se pudo determinar el tipo de los elementos del array.");

                    // APLICAR LA LIMPIEZA DE ObjectId AL JSON COMPLETO DEL ARRAY
                    // Esto cambia: {"_id": {"$oid": "..."}}  a: {"_id": "..."}
                    var cleanJsonArray = ObjectIdRegex().Replace(dirtyJsonArray, m => m.Groups[1].Value);
                    cleanJsonArray = Regex.Replace(
                        cleanJsonArray,
                        "\"_id\"\\s*:\\s*\\{\\s*\"\\$oid\"\\s*:\\s*\"([^\"]+)\"\\s*\\}",
                        "\"id\":\"$1\""
                    );
                    var elementTypeList = typeof(List<>).MakeGenericType(elementType);
                    var settings = new JsonSerializerSettings
                    {
                        Converters = { new ObjectIdConverter() }
                    };
                    // Usar el JSON limpio para la deserialización
                    return JsonConvert.DeserializeObject(cleanJsonArray, elementTypeList, settings)
                           ?? throw new Exception("Can't deserialize BSON array.");
                }
                default:
                    throw new Exception("Unexpected BsonType: " + context.Reader.GetCurrentBsonType());
            }
        }
        catch (Exception ex)
        {
            throw new Exception("Unexpected BsonType: " + ex.GetBaseException());
        }
    }

    /// <summary>
    /// Serializes a value of an unknown runtime type into BSON, handling common primitives, <see cref="OptionList"/>, <see cref="PatientAllergiesValue"/>, and their list variants directly, and using reflection to dispatch generic <see cref="ArraySerializer{T}"/> for arbitrary <see cref="ICollection"/> instances. For any other type, the value is converted to JSON, parsed into a <see cref="BsonDocument"/>, tagged with its full type name in the <c>_t</c> field, and then written out.
    /// </summary>
    /// <param name="context">The BSON serialization context that provides the writer used to emit the serialized output.</param>
    /// <param name="args">Additional BSON serialization arguments passed through to the underlying serializers.</param>
    /// <param name="value">The runtime value to serialize. A <c>null</c> value is written as a BSON null; all other values are dispatched based on their concrete type.</param>
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
        {
            switch (value)
            {
                case null:
                    context.Writer.WriteNull();
                    return;
                case bool b:
                    BooleanSerializer.Instance.Serialize(context, args, b);
                    return;
                case double b:
                    DoubleSerializer.Instance.Serialize(context, args, b);
                    return;
                case string b:
                    StringSerializer.Instance.Serialize(context, args, b);
                    return;
                case int b:
                    Int32Serializer.Instance.Serialize(context, args, b);
                    return;
                case long b:
                    Int64Serializer.Instance.Serialize(context, args, b);
                    return;
                case Decimal128 b:
                    new Decimal128Serializer().Serialize(context, args, b);
                    return;
                case DateTime b:
                    new DateTimeSerializer().Serialize(context, args, b);
                    return;
                case OptionList optionList:
                    BsonSerializer.Serialize(context.Writer, optionList.GetType(), optionList);
                    return;
                case IList<OptionList> optionListList:
                    BsonSerializer.Serialize(context.Writer, optionListList.GetType(), optionListList);
                    return;
                case PatientAllergiesValue pav:
                    BsonSerializer.Serialize(context.Writer, pav.GetType(), pav);
                    return;
                case IList<PatientAllergiesValue> pavList:
                    BsonSerializer.Serialize(context.Writer, pavList.GetType(), pavList);
                    return;
    
                case ICollection i:
                    var t = i.GetType().GetElementType() ?? typeof(object);
                    var serializerType = typeof(ArraySerializer<>).MakeGenericType(t);
                    var ctor = serializerType.GetConstructor([]);
                    var serializer = ctor?.Invoke([]);
                    var serializeMethod = serializerType.GetMethod("Serialize");
                    var array = CollectionsUtils.ToArray(i, t);
                    serializeMethod?.Invoke(serializer!, [context, args, array]);
                    return;
                default:
                    var json = JsonConvert.SerializeObject(value);
                    var document = BsonDocument.Parse(json);
                    document["_t"] = value.GetType().FullName;
                    BsonSerializer.Serialize(context.Writer, typeof(BsonDocument), document);
                    return;
            }
        }

    /// <summary>
    /// Determines the .NET <see cref="Type"/> represented by the elements of a BSON array, handling both null-valued single-element arrays and BSON documents that may carry a discriminator (<c>_t</c>) or match known domain types (such as <see cref="OptionList"/>, <see cref="List{OptionList}"/>, <see cref="PatientAllergiesValue"/>, <see cref="PatientDrainagesValue"/>, and <see cref="PatientIntravenousLinesValue"/>).
    /// When the discriminator contains a dot, it is resolved as a type under the <c>adas-core_Domain.Models</c> namespace, with legacy <c>adas-core_Models</c> segments rewritten to the current namespace.
    /// </summary>
    /// <param name="bsonArray">The BSON array whose element type needs to be resolved.</param>
    /// <returns>The resolved <see cref="Type"/> for the array elements, or <c>null</c> when no matching type can be determined.</returns>
    private Type? GetElementTypeFromBsonArray(BsonArray bsonArray)
        {
            if (bsonArray.Count == 1 && bsonArray.Values.Any(c => c.IsBsonNull))
                return typeof(OptionList);
            foreach (var element in bsonArray)
                if (element is BsonDocument bsonDocument)
                {
                    if (bsonDocument.Contains("_t"))
                    {
                        var t = bsonDocument["_t"].AsString;
                        if (t.Contains('.'))
                        {
                            t = $"adas-core_Domain.Models.{t}";
                            t = t.Replace("adas-core_Models", "adas-core_Domain.Models");
                        }
    
                        var type = Type.GetType(t);
                        if (type != null) return type;
                    }
                    else
                    {
                        var instance = BsonSerializer.Deserialize(bsonDocument, typeof(OptionList));
                        if (instance != null && bsonDocument.Contains("name")) return typeof(OptionList);
                        instance = BsonSerializer.Deserialize(bsonDocument, typeof(List<OptionList>));
                        if (instance != null)
                            return typeof(List<OptionList>);
    
                        instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientAllergiesValue));
                        if (instance != null)
                            return typeof(PatientAllergiesValue);
    
                        instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientDrainagesValue));
                        if (instance != null)
                            return typeof(PatientDrainagesValue);
    
                        instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientIntravenousLinesValue));
                        if (instance != null)
                            return typeof(PatientIntravenousLinesValue);
                    }
                }
    
            return null;
        }

    /// <summary>
    /// Provides serialization for <see cref="List{T}"/> instances while ignoring null elements.
    /// </summary>
    /// <typeparam name="T">The type of elements contained in the list being serialized.</typeparam>
    public class NullIgnoringListSerializer<T> : SerializerBase<List<T>>
    {
        /// <summary>
        /// Deserializes a BSON array into a <see cref="List{T}"/>, returning an empty list when the current BSON value is null and skipping any null elements encountered while reading the array.
        /// </summary>
        /// <param name="context">The BSON deserialization context providing the reader used to read the BSON data.</param>
        /// <param name="args">Additional arguments that influence the deserialization process.</param>
        /// <returns>A <see cref="List{T}"/> containing the deserialized elements, or an empty list if the BSON value is null.</returns>
        public override List<T> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
                {
                    var bsonType = context.Reader.GetCurrentBsonType();
                    if (bsonType == BsonType.Null)
                    {
                        context.Reader.ReadNull();
                        return [];
                    }
        
                    var list = new List<T>();
                    context.Reader.ReadStartArray();
                    while (context.Reader.ReadBsonType() != BsonType.EndOfDocument)
                    {
                        var element = BsonSerializer.Deserialize<T>(context.Reader);
                        if (element != null) list.Add(element);
                    }
        
                    context.Reader.ReadEndArray();
                    return list;
                }

        /// <summary>
        /// Serializes a <see cref="List{T}"/> as a BSON array, writing each element using its actual runtime type to support polymorphic serialization. Null elements are handled safely through the null-conditional type resolution.
        /// </summary>
        /// <param name="context">The BSON serialization context providing the writer used to emit the array and its elements.</param>
        /// <param name="args">The BSON serialization arguments supplying additional options for the serialization process.</param>
        /// <param name="value">The list of items to serialize as a BSON array.</param>
        public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<T> value)
                {
                    context.Writer.WriteStartArray();
                    foreach (var item in value) BsonSerializer.Serialize(context.Writer, item?.GetType(), item);
                    context.Writer.WriteEndArray();
                }
    }
    // public class IgnoreEmptyStringSerializer : SerializerBase<string>
    // {
    //     public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
    //     {
    //         if (string.IsNullOrEmpty(value))
    //         {
    //             context.Writer.WriteNull();
    //         }
    //         else
    //         {
    //             context.Writer.WriteString(value);
    //         }
    //     }
    //
    //     public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    //     {
    //         if (string.IsNullOrEmpty(context.Reader.ReadString()))
    //         {
    //             return "";
    //         }
    //         else
    //         {
    //             return context.Reader.ReadString();
    //         }
    //     }
    // }
}