using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using AIRuntime.Runtime.Capabilities.Attributes;

namespace AIRuntime.Runtime.Capabilities
{
    /// <summary>
    /// Validates capability arguments using AIRuntime attributes.
    /// </summary>
    public static class CapabilityValidator
    {
        public static async Task ValidateAsync(
            CapabilityContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if (context.Arguments == null)
                throw new ValidationException(
                    "Arguments cannot be null.");

            if (context.FileSystem == null)
                throw new InvalidOperationException(
                    "CapabilityContext.FileSystem has not been assigned.");

            ValidationResult result =
                new ValidationResult();

            await ValidateObjectAsync(
                context,
                context.Arguments,
                result);

            if (!result.IsValid)
            {
                throw new ValidationException(
                    result.ToString());
            }
        }

        private static async Task ValidateObjectAsync(
            CapabilityContext context,
            object instance,
            ValidationResult result)
        {
            Type type = instance.GetType();

            foreach (PropertyInfo property in type.GetProperties(
                         BindingFlags.Instance |
                         BindingFlags.Public))
            {
                object value = property.GetValue(instance);

                //--------------------------------------------------
                // Required
                //--------------------------------------------------

                if (Attribute.IsDefined(
                        property,
                        typeof(RequiredAttribute)))
                {
                    if (value == null)
                    {
                        result.Add($"{property.Name} is required.");
                        continue;
                    }

                    if (value is string required &&
                        string.IsNullOrWhiteSpace(required))
                    {
                        result.Add($"{property.Name} cannot be empty.");
                        continue;
                    }
                }

                //--------------------------------------------------
                // Min Length
                //--------------------------------------------------

                MinLengthAttribute min =
                    property.GetCustomAttribute<MinLengthAttribute>();

                if (min != null &&
                    value is string minString &&
                    minString.Length < min.Length)
                {
                    result.Add(
                        $"{property.Name} must contain at least {min.Length} characters.");
                }

                //--------------------------------------------------
                // Max Length
                //--------------------------------------------------

                MaxLengthAttribute max =
                    property.GetCustomAttribute<MaxLengthAttribute>();

                if (max != null &&
                    value is string maxString &&
                    maxString.Length > max.Length)
                {
                    result.Add(
                        $"{property.Name} cannot exceed {max.Length} characters.");
                }

                //--------------------------------------------------
                // Numeric Range
                //--------------------------------------------------

                RangeAttribute range =
                    property.GetCustomAttribute<RangeAttribute>();

                if (range != null && value != null)
                {
                    try
                    {
                        double number = Convert.ToDouble(value);

                        if (number < range.Minimum ||
                            number > range.Maximum)
                        {
                            result.Add(
                                $"{property.Name} must be between {range.Minimum} and {range.Maximum}.");
                        }
                    }
                    catch
                    {
                        result.Add(
                            $"{property.Name} must be numeric.");
                    }
                }

                //--------------------------------------------------
                // Workspace Path
                //--------------------------------------------------

                if (Attribute.IsDefined(
                        property,
                        typeof(WorkspacePathAttribute)))
                {
                    if (value is string workspacePath)
                    {
                        try
                        {
                            await context.FileSystem.ValidatePathAsync(
                                workspacePath);
                        }
                        catch (Exception ex)
                        {
                            result.Add(ex.Message);
                        }
                    }
                }

                //--------------------------------------------------
                // Existing File
                //--------------------------------------------------

                if (Attribute.IsDefined(
                        property,
                        typeof(ExistingFileAttribute)))
                {
                    if (value is string filePath &&
                        !string.IsNullOrWhiteSpace(filePath))
                    {
                        try
                        {
                            await context.FileSystem.ValidatePathAsync(
                                filePath);

                            bool exists =
                                await context.FileSystem.FileExistsAsync(
                                    filePath);

                            if (!exists)
                            {
                                result.Add(
                                    $"{property.Name} does not exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            result.Add(ex.Message);
                        }
                    }
                }

                //--------------------------------------------------
                // Directory Path
                //--------------------------------------------------

                if (Attribute.IsDefined(
                        property,
                        typeof(DirectoryPathAttribute)))
                {
                    if (value is string directoryPath &&
                        !string.IsNullOrWhiteSpace(directoryPath))
                    {
                        try
                        {
                            await context.FileSystem.ValidatePathAsync(
                                directoryPath);

                            bool exists =
                                await context.FileSystem.DirectoryExistsAsync(
                                    directoryPath);

                            if (!exists)
                            {
                                result.Add(
                                    $"{property.Name} does not exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            result.Add(ex.Message);
                        }
                    }
                }

                //--------------------------------------------------
                // File Extensions
                //--------------------------------------------------

                FileExtensionsAttribute extensions =
                    property.GetCustomAttribute<FileExtensionsAttribute>();

                if (extensions != null &&
                    value is string filename)
                {
                    string extension =
                        Path.GetExtension(filename);

                    bool valid = false;

                    foreach (string allowed in extensions.Extensions)
                    {
                        if (string.Equals(
                                extension,
                                allowed,
                                StringComparison.OrdinalIgnoreCase))
                        {
                            valid = true;
                            break;
                        }
                    }

                    if (!valid)
                    {
                        result.Add(
                            $"{property.Name} must have one of the following extensions: {string.Join(", ", extensions.Extensions)}");
                    }
                }

                //--------------------------------------------------
                // Recursive Validation
                //--------------------------------------------------

                if (value == null)
                    continue;

                Type propertyType = property.PropertyType;

                if (propertyType == typeof(string))
                    continue;

                if (propertyType.IsPrimitive)
                    continue;

                if (propertyType.IsEnum)
                    continue;

                if (typeof(IEnumerable).IsAssignableFrom(propertyType))
                    continue;

                await ValidateObjectAsync(
                    context,
                    value,
                    result);
            }
        }
    }
}
