// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Qdrant.Client.Grpc;

namespace Microsoft.SemanticKernel.Connectors.Qdrant;

/// <summary>
/// Contains helper methods for mapping fields to and from the format required by the Qdrant client sdk.
/// </summary>
internal static class QdrantFieldMapping
{
    /// <summary>
    /// Convert the given <paramref name="payloadValue"/> to the correct native type based on its properties.
    /// </summary>
    /// <param name="payloadValue">The value to convert to a native type.</param>
    /// <param name="targetType">The target type to convert the value to.</param>
    /// <returns>The converted native value.</returns>
    /// <exception cref="InvalidOperationException">Thrown when an unsupported type is encountered.</exception>
    public static object? Deserialize(Value payloadValue, Type targetType)
    {
        if (Nullable.GetUnderlyingType(targetType) is Type unwrapped)
        {
            targetType = unwrapped;
        }

        return payloadValue.KindCase switch
        {
            Value.KindOneofCase.NullValue => null,

            Value.KindOneofCase.IntegerValue
                => targetType == typeof(int) ? (object)(int)payloadValue.IntegerValue : (object)payloadValue.IntegerValue,

            Value.KindOneofCase.StringValue when targetType == typeof(DateTimeOffset)
                => DeserializeDateTimeOffset(payloadValue.StringValue),

            Value.KindOneofCase.StringValue
                => payloadValue.StringValue,

            Value.KindOneofCase.DoubleValue
                => targetType == typeof(float) ? (object)(float)payloadValue.DoubleValue : (object)payloadValue.DoubleValue,

            Value.KindOneofCase.BoolValue
                => payloadValue.BoolValue,

            Value.KindOneofCase.ListValue => DeserializeCollection(payloadValue, targetType),

            _ => throw new InvalidOperationException($"Unsupported grpc value kind {payloadValue.KindCase}."),
        };

        static object? DeserializeCollection(Value payloadValue, Type targetType)
            => targetType switch
            {
                Type t when t == typeof(List<int>)
                    => payloadValue.ListValue.Values.Select(v => (int)v.IntegerValue).ToList(),
                Type t when t == typeof(int[])
                    => payloadValue.ListValue.Values.Select(v => (int)v.IntegerValue).ToArray(),
                Type t when t == typeof(List<long>)
                    => payloadValue.ListValue.Values.Select(v => v.IntegerValue).ToList(),
                Type t when t == typeof(long[])
                    => payloadValue.ListValue.Values.Select(v => v.IntegerValue).ToArray(),

                Type t when t == typeof(List<string>)
                    => payloadValue.ListValue.Values.Select(v => v.StringValue).ToList(),
                Type t when t == typeof(string[])
                    => payloadValue.ListValue.Values.Select(v => v.StringValue).ToArray(),

                Type t when t == typeof(List<double>)
                    => payloadValue.ListValue.Values.Select(v => v.DoubleValue).ToList(),
                Type t when t == typeof(double[])
                    => payloadValue.ListValue.Values.Select(v => v.DoubleValue).ToArray(),
                Type t when t == typeof(List<float>)
                    => payloadValue.ListValue.Values.Select(v => (float)v.DoubleValue).ToList(),
                Type t when t == typeof(float[])
                    => payloadValue.ListValue.Values.Select(v => (float)v.DoubleValue).ToArray(),

                Type t when t == typeof(List<bool>)
                    => payloadValue.ListValue.Values.Select(v => v.BoolValue).ToList(),
                Type t when t == typeof(bool[])
                    => payloadValue.ListValue.Values.Select(v => v.BoolValue).ToArray(),

                Type t when t == typeof(List<DateTimeOffset>)
                    => payloadValue.ListValue.Values.Select(v => DeserializeDateTimeOffset(v.StringValue)).ToList(),
                Type t when t == typeof(DateTimeOffset[])
                    => payloadValue.ListValue.Values.Select(v => DeserializeDateTimeOffset(v.StringValue)).ToArray(),

                _ => throw new UnreachableException($"Unsupported collection type {targetType.Name}"),
            };

        static DateTimeOffset DeserializeDateTimeOffset(string s) => DateTimeOffset.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
    }

    /// <summary>
    /// Convert the given <paramref name="sourceValue"/> to a <see cref="Value"/> object that can be stored in Qdrant.
    /// </summary>
    /// <param name="sourceValue">The object to convert.</param>
    /// <returns>The converted Qdrant value.</returns>
    /// <exception cref="InvalidOperationException">Thrown when an unsupported type is encountered.</exception>
    public static Value ConvertToGrpcFieldValue(object? sourceValue)
    {
        var value = new Value();
        if (sourceValue is null)
        {
            value.NullValue = NullValue.NullValue;
        }
        else if (sourceValue is int intValue)
        {
            value.IntegerValue = intValue;
        }
        else if (sourceValue is long longValue)
        {
            value.IntegerValue = longValue;
        }
        else if (sourceValue is string stringValue)
        {
            value.StringValue = stringValue;
        }
        else if (sourceValue is float floatValue)
        {
            value.DoubleValue = floatValue;
        }
        else if (sourceValue is double doubleValue)
        {
            value.DoubleValue = doubleValue;
        }
        else if (sourceValue is bool boolValue)
        {
            value.BoolValue = boolValue;
        }
        else if (sourceValue is DateTimeOffset dateTimeOffsetValue)
        {
            value.StringValue = dateTimeOffsetValue.ToString("O");
        }
        else if (sourceValue is IEnumerable<int> or
            IEnumerable<long> or
            IEnumerable<string> or
            IEnumerable<float> or
            IEnumerable<double> or
            IEnumerable<bool> or
            IEnumerable<DateTime> or
            IEnumerable<DateTimeOffset>)
        {
            var listValue = sourceValue as IEnumerable;
            value.ListValue = new ListValue();
            foreach (var item in listValue!)
            {
                value.ListValue.Values.Add(ConvertToGrpcFieldValue(item));
            }
        }
        else
        {
            throw new InvalidOperationException($"Unsupported source value type {sourceValue?.GetType().FullName}.");
        }

        return value;
    }
}
