using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json.Linq;

namespace adas_core.Domain.Models.BsonConverters;

/// <summary>
/// A BSON converter responsible for serializing and deserializing
/// <see cref="Dictionary{TKey, TValue}"/> instances with string keys and object values,
/// extending the base serializer infrastructure to support BSON format conversion.
/// </summary>
/// <remarks>
/// This type specializes the generic base serializer for the specific dictionary shape
/// <c>Dictionary&lt;string, object&gt;</c>, providing a focused implementation for
/// BSON-based persistence and data interchange scenarios.
/// </remarks>
public class DictionaryBsonConverter : SerializerBase<Dictionary<string, object>>
{
    /// <summary>
    /// Deserializes a BSON document into a dictionary where each top-level field name is mapped to its corresponding value.
    /// Recognized keys (<c>STEPS</c>, <c>BEACONS</c>, <c>CAMERAS</c>, <c>RELAY</c>) are deserialized into their strongly-typed collections or objects, while unknown keys are deserialized based on the current BSON type (numeric values, strings, booleans, arrays of mixed primitive types, or generic BSON documents).
    /// If the current BSON value is not a document, an empty dictionary is returned.
    /// </summary>
    /// <param name="context">The BSON deserialization context providing the reader used to traverse the BSON payload.</param>
    /// <param name="args">The BSON deserialization arguments carrying additional configuration for the deserialization process.</param>
    /// <returns>A dictionary containing the deserialized key/value pairs extracted from the BSON document.</returns>
    public override Dictionary<string, object> Deserialize(BsonDeserializationContext context,
            BsonDeserializationArgs args)
        {
            var reader = context.Reader;
            var stepsDictionary = new Dictionary<string, object>();
    
            if (reader.CurrentBsonType == BsonType.Document)
            {
                reader.ReadStartDocument();
    
                while (reader.ReadBsonType() != BsonType.EndOfDocument)
                {
                    var key = reader.ReadName();
    
                    switch (key.ToUpper())
                    {
                        case "STEPS":
                            var stepList = BsonSerializer.Deserialize<List<Step>>(reader);
                            stepsDictionary[key] = stepList;
                            break;
                        case "BEACONS":
                            var beaconList = BsonSerializer.Deserialize<List<LightBeacon>>(reader);
                            stepsDictionary[key] = beaconList;
                            break;
                        case "CAMERAS":
                            var cameraList = BsonSerializer.Deserialize<List<Camera>>(reader);
                            stepsDictionary[key] = cameraList;
                            break;
                        case "RELAY":
                            var relayList = BsonSerializer.Deserialize<Relay>(reader);
                            stepsDictionary[key] = relayList;
                            break;
    
                        default:
                            switch (reader.CurrentBsonType)
                            {
                                case BsonType.Int32:
                                case BsonType.Int64:
                                    stepsDictionary[key] = BsonSerializer.Deserialize<long>(reader);
                                    break;
                                case BsonType.Double:
                                    stepsDictionary[key] = BsonSerializer.Deserialize<double>(reader);
                                    break;
                                case BsonType.String:
                                    stepsDictionary[key] = BsonSerializer.Deserialize<string>(reader);
                                    break;
                                case BsonType.Boolean:
                                    stepsDictionary[key] = BsonSerializer.Deserialize<bool>(reader);
                                    break;
                                case BsonType.Array:
                                    reader.ReadStartArray();
                                    var arrayItems = new List<object>();
                                    while (reader.ReadBsonType() != BsonType.EndOfDocument)
                                        switch (reader.CurrentBsonType)
                                        {
                                            case BsonType.String:
                                                arrayItems.Add(BsonSerializer.Deserialize<string>(reader));
                                                break;
                                            case BsonType.Int32:
                                                arrayItems.Add(BsonSerializer.Deserialize<int>(reader));
                                                break;
                                            case BsonType.Int64:
                                                arrayItems.Add(BsonSerializer.Deserialize<long>(reader));
                                                break;
                                            case BsonType.Boolean:
                                                arrayItems.Add(BsonSerializer.Deserialize<bool>(reader));
                                                break;
                                            case BsonType.Double:
                                                arrayItems.Add(BsonSerializer.Deserialize<double>(reader));
                                                break;
                                            case BsonType.ObjectId:
                                                arrayItems.Add(BsonSerializer.Deserialize<ObjectId>(reader));
                                                break;
                                            case BsonType.DateTime:
                                                arrayItems.Add(BsonSerializer.Deserialize<DateTime>(reader));
                                                break;
    
                                            default:
                                                reader.SkipValue();
                                                break;
                                        }
    
                                    reader.ReadEndArray();
                                    stepsDictionary[key] = arrayItems;
                                    break;
                                default:
                                    var bsonDoc = BsonSerializer.Deserialize<BsonDocument>(reader);
                                    stepsDictionary[key] = bsonDoc.ToDictionary();
                                    break;
                            }
    
                            break;
                    }
                }
    
                reader.ReadEndDocument();
            }
    
            return stepsDictionary;
        }

    /// <summary>
    /// Serializes a dictionary of string keys and object values into a BSON document. When a value's
    /// type cannot be handled by the underlying key/value serializer, the corresponding key is written
    /// with a null value as a fallback to preserve all dictionary entries.
    /// </summary>
    /// <param name="context">The BSON serialization context that provides the writer used to emit the document.</param>
    /// <param name="args">The BSON serialization arguments associated with the current operation.</param>
    /// <param name="value">The dictionary whose entries are written as fields of the BSON document.</param>
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args,
            Dictionary<string, object> value)
        {
            var writer = context.Writer;
            writer.WriteStartDocument();
    
            foreach (var item in value)
            {
                if (TrySerializeKeyValuePair(item, writer)) continue;
    
                // Si llegamos a este punto, encontramos un tipo no soportado.
                writer.WriteName(item.Key);
                writer.WriteNull(); // Escribe un valor null si el tipo no es soportado.
            }
    
    
            writer.WriteEndDocument();
        }

    /// <summary>
    /// Attempts to serialize a key-value pair to BSON format using the provided writer. Handles specific well-known keys (<c>steps</c>, <c>beacons</c>, <c>cameras</c>, <c>relay</c>, and <c>uiFontProperties</c>) with their strongly-typed models, and falls back to serializing nested dictionaries, JArrays, or primitive BSON values for any other key. Returns <c>false</c> when the value cannot be converted to a BSON value or when no matching case applies.
    /// </summary>
    /// <param name="item">The key-value pair to serialize, where the key determines the serialization strategy and the value holds the data to write.</param>
    /// <param name="writer">The BSON writer used to emit the serialized output for the key-value pair.</param>
    /// <returns><c>true</c> if the key-value pair was successfully serialized; otherwise, <c>false</c>.</returns>
    /// <exception cref="BsonSerializationException">Thrown when a BSON array contains an element whose BSON type is not supported.</exception>
    /// <exception cref="NotSupportedException">Thrown when the default branch encounters a BSON value type that is not supported.</exception>
    private static bool TrySerializeKeyValuePair(KeyValuePair<string, object> item, IBsonWriter writer)
        {
            writer.WriteName(item.Key);
    
            switch (item.Key)
            {
                case "steps":
                    if (item.Value is JArray jSteps)
                    {
                        var steps = jSteps.ToObject<List<Step>>();
                        BsonSerializer.Serialize(writer, typeof(List<Step>), steps);
                        return true;
                    }
    
                    break;
    
                case "beacons":
                    if (item.Value is JArray jBeaconConfigs)
                    {
                        var beaconConfigs = jBeaconConfigs.ToObject<List<LightBeacon>>();
                        BsonSerializer.Serialize(writer, typeof(List<LightBeacon>), beaconConfigs);
                        return true;
                    }
    
                    break;
                case "cameras":
                    if (item.Value is JArray jCamerasConfigs)
                    {
                        var cameraConfigs = jCamerasConfigs.ToObject<List<Camera>>();
                        BsonSerializer.Serialize(writer, typeof(List<Camera>), cameraConfigs);
                        return true;
                    }
    
                    break;
                case "relay":
                    if (item.Value is JObject jRelayConfigs)
                    {
                        var relayConfigs = jRelayConfigs.ToObject<Relay>();
                        BsonSerializer.Serialize(writer, relayConfigs);
                        return true;
                    }
    
                    break;
                case "uiFontProperties":
                {
                    if (item.Value is JObject jUiFontData)
                    {
                        var uiFontData = jUiFontData.ToObject<UiFontData>();
                        BsonSerializer.Serialize(writer, uiFontData);
                        return true;
                    }
                }
                    break;
                default:
                    if (item.Value is Dictionary<string, object> nestedDictionary)
                    {
                        var bsonDoc = new BsonDocument(nestedDictionary);
                        BsonSerializer.Serialize(writer, bsonDoc);
                        return true;
                    }
    
                    if (item.Value is JArray jArray)
                    {
                        var listObjects = jArray.ToObject<List<object>>();
                        BsonSerializer.Serialize(writer, listObjects);
                        return true;
                    }
    
                    // Para otros tipos primitivos, utilizamos BsonValue.Create
                    try
                    {
                        var bsonValue = BsonValue.Create(item.Value);
                        switch (bsonValue.BsonType)
                        {
                            case BsonType.String:
                                writer.WriteString(bsonValue.AsString);
                                break;
    
                            case BsonType.Int32:
                                writer.WriteInt32(bsonValue.AsInt32);
                                break;
    
                            case BsonType.Int64:
                                writer.WriteInt64(bsonValue.AsInt64);
                                break;
    
                            case BsonType.Double:
                                writer.WriteDouble(bsonValue.AsDouble);
                                break;
    
                            case BsonType.Boolean:
                                writer.WriteBoolean(bsonValue.AsBoolean);
                                break;
    
                            case BsonType.Array:
                                writer.WriteStartArray();
                                foreach (var arrayValue in bsonValue.AsBsonArray)
                                    switch (arrayValue.BsonType)
                                    {
                                        case BsonType.String:
                                            writer.WriteString(arrayValue.AsString);
                                            break;
    
                                        case BsonType.Int32:
                                            writer.WriteInt32(arrayValue.AsInt32);
                                            break;
    
                                        case BsonType.Int64:
                                            writer.WriteInt64(arrayValue.AsInt64);
                                            break;
    
                                        case BsonType.Double:
                                            writer.WriteDouble(arrayValue.AsDouble);
                                            break;
    
                                        case BsonType.Boolean:
                                            writer.WriteBoolean(arrayValue.AsBoolean);
                                            break;
    
                                        default:
                                            throw new BsonSerializationException(
                                                $"No se puede serializar el tipo Bson en el array: {arrayValue.BsonType}");
                                    }
    
                                writer.WriteEndArray();
                                break;
    
                            default:
                                throw new NotSupportedException($"BsonType {bsonValue.BsonType} no es compatible.");
                        }
    
                        return true;
                    }
                    catch (ArgumentException)
                    {
                        return false;
                    }
            }
    
            return false;
        }
}