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;

public partial class ComplexObjectValueTypeSerializer : SerializerBase<object>
{
    [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());
        }
    }

    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;
        }
    }

    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;
    }

    public class NullIgnoringListSerializer<T> : SerializerBase<List<T>>
    {
        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;
        }

        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();
    //         }
    //     }
    // }
}