using System;
using AIRuntime.Runtime.Capabilities.Attributes;
using System.Reflection;
using Newtonsoft.Json.Linq;

namespace AIRuntime.Runtime.Capabilities
{
    /// <summary>
    /// Generates JSON Schema from strongly typed argument classes.
    /// Used when advertising capabilities to AI providers.
    /// </summary>
    public static class SchemaGenerator
    {
        /// <summary>
        /// Generates a schema for a generic type.
        /// </summary>
        public static JObject Generate<T>()
        {
            return Generate(typeof(T));
        }

        /// <summary>
        /// Generates a JSON schema for the supplied type.
        /// </summary>
        public static JObject Generate(Type type)
        {
            JObject properties = new JObject();
            JArray required = new JArray();

            foreach (PropertyInfo property in type.GetProperties(
                         BindingFlags.Instance |
                         BindingFlags.Public))
            {
                JObject schema = GenerateProperty(property);

                properties.Add(
                    ToCamelCase(property.Name),
                    schema);

                if (property.GetCustomAttribute<RequiredAttribute>() != null)
                {
                    required.Add(ToCamelCase(property.Name));
                }
            }

            JObject result = new JObject
            {
                ["type"] = "object",
                ["properties"] = properties
            };

            if (required.Count > 0)
            {
                result["required"] = required;
            }

            return result;
        }

        private static JObject GenerateProperty(PropertyInfo property)
        {
            Type type = Nullable.GetUnderlyingType(property.PropertyType)
                        ?? property.PropertyType;

            JObject schema = GenerateType(type);

            //--------------------------------------------------
            // Description
            //--------------------------------------------------

            DescriptionAttribute description =
                property.GetCustomAttribute<DescriptionAttribute>();

            if (description != null)
            {
                schema["description"] = description.Description;
            }

            //--------------------------------------------------
            // Default Value
            //--------------------------------------------------

            DefaultValueAttribute defaultValue =
                property.GetCustomAttribute<DefaultValueAttribute>();

            if (defaultValue != null)
            {
                schema["default"] =
                    JToken.FromObject(defaultValue.Value);
            }

            return schema;
        }

        private static JObject GenerateType(Type type)
        {
            //--------------------------------------------------
            // String
            //--------------------------------------------------

            if (type == typeof(string))
            {
                return new JObject
                {
                    ["type"] = "string"
                };
            }

            //--------------------------------------------------
            // Boolean
            //--------------------------------------------------

            if (type == typeof(bool))
            {
                return new JObject
                {
                    ["type"] = "boolean"
                };
            }

            //--------------------------------------------------
            // Integer
            //--------------------------------------------------

            if (type == typeof(byte) ||
                type == typeof(short) ||
                type == typeof(int) ||
                type == typeof(long))
            {
                return new JObject
                {
                    ["type"] = "integer"
                };
            }

            //--------------------------------------------------
            // Number
            //--------------------------------------------------

            if (type == typeof(float) ||
                type == typeof(double) ||
                type == typeof(decimal))
            {
                return new JObject
                {
                    ["type"] = "number"
                };
            }

            //--------------------------------------------------
            // Enum
            //--------------------------------------------------

            if (type.IsEnum)
            {
                return new JObject
                {
                    ["type"] = "string",
                    ["enum"] = new JArray(
                        Enum.GetNames(type))
                };
            }

            //--------------------------------------------------
            // Arrays
            //--------------------------------------------------

            if (type.IsArray)
            {
                return new JObject
                {
                    ["type"] = "array",
                    ["items"] = GenerateType(
                        type.GetElementType())
                };
            }

            //--------------------------------------------------
            // Generic List<T>
            //--------------------------------------------------

            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>))
            {
                return new JObject
                {
                    ["type"] = "array",
                    ["items"] = GenerateType(
                        type.GetGenericArguments()[0])
                };
            }

            //--------------------------------------------------
            // Nested Object
            //--------------------------------------------------

            return Generate(type);
        }

        private static string ToCamelCase(string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;

            if (char.IsLower(value[0]))
                return value;

            return char.ToLowerInvariant(value[0]) + value.Substring(1);
        }
    }
}