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;

public class DictionaryBsonConverter : SerializerBase<Dictionary<string, object>>
{
    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;
    }

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

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