using System.Reflection;
using JsonRpcEndpointSample.Rpc.Abstractions;

namespace JsonRpcEndpointSample.Rpc.Discovery;

public sealed class RpcJsonSchemaGenerator
{
    public Dictionary<string, object?> CreateSchema(Type type)
    {
        Dictionary<string, object?> properties = new Dictionary<string, object?>();
        List<string> required = new List<string>();

        PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);

        foreach (PropertyInfo propertyInfo in propertyInfos)
        {
            RpcSchemaPropertyAttribute? attribute = propertyInfo.GetCustomAttribute<RpcSchemaPropertyAttribute>();

            Dictionary<string, object?> propertySchema = CreatePropertySchema(propertyInfo.PropertyType);

            if (attribute != null)
            {
                if (!string.IsNullOrWhiteSpace(attribute.Description))
                {
                    propertySchema.Add("description", attribute.Description);
                }

                if (attribute.HasMinimum)
                {
                    propertySchema.Add("minimum", attribute.Minimum);
                }

                if (attribute.Required)
                {
                    required.Add(RpcNameConverter.ToCamelCase(propertyInfo.Name));
                }
            }

            properties.Add(RpcNameConverter.ToCamelCase(propertyInfo.Name), propertySchema);
        }

        Dictionary<string, object?> schema = new Dictionary<string, object?>();
        schema.Add("type", "object");
        schema.Add("additionalProperties", false);
        schema.Add("properties", properties);
        schema.Add("required", required.ToArray());

        return schema;
    }

    private static Dictionary<string, object?> CreatePropertySchema(Type propertyType)
    {
        Type actualType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;

        Dictionary<string, object?> schema = new Dictionary<string, object?>();

        if (actualType == typeof(string) || actualType == typeof(Guid) || actualType == typeof(DateTime))
        {
            schema.Add("type", "string");
            return schema;
        }

        if (actualType == typeof(int) || actualType == typeof(long) || actualType == typeof(short))
        {
            schema.Add("type", "integer");
            return schema;
        }

        if (actualType == typeof(decimal) || actualType == typeof(double) || actualType == typeof(float))
        {
            schema.Add("type", "number");
            return schema;
        }

        if (actualType == typeof(bool))
        {
            schema.Add("type", "boolean");
            return schema;
        }

        schema.Add("type", "object");
        return schema;
    }
}
