using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Configuration;
using StellaOps.Excititor.Core;

namespace StellaOps.Excititor.Connectors.Abstractions;

/// <summary>
/// Provides strongly typed binding and validation for connector options.
/// </summary>
public static class VexConnectorOptionsBinder
{
    public static TOptions Bind<TOptions>(
        VexConnectorDescriptor descriptor,
        VexConnectorSettings settings,
        VexConnectorOptionsBinderOptions? options = null,
        IEnumerable<IVexConnectorOptionsValidator<TOptions>>? validators = null)
        where TOptions : class, new()
    {
        ArgumentNullException.ThrowIfNull(descriptor);
        ArgumentNullException.ThrowIfNull(settings);

        var binderSettings = options ?? new VexConnectorOptionsBinderOptions();
        var transformed = TransformValues(settings, binderSettings);

        var configuration = BuildConfiguration(transformed);

        var result = new TOptions();
        var errors = new List<string>();

        try
        {
            configuration.Bind(
                result,
                binderOptions => binderOptions.ErrorOnUnknownConfiguration = !binderSettings.AllowUnknownKeys);
        }
        catch (InvalidOperationException ex) when (!binderSettings.AllowUnknownKeys)
        {
            errors.Add(ex.Message);
        }

        binderSettings.PostConfigure?.Invoke(result);

        if (binderSettings.ValidateDataAnnotations)
        {
            ValidateDataAnnotations(result, errors);
        }

        if (validators is not null)
        {
            foreach (var validator in validators)
            {
                validator?.Validate(descriptor, result, errors);
            }
        }

        if (errors.Count > 0)
        {
            throw new VexConnectorOptionsValidationException(descriptor.Id, errors);
        }

        return result;
    }

    private static ImmutableDictionary<string, string?> TransformValues(
        VexConnectorSettings settings,
        VexConnectorOptionsBinderOptions binderOptions)
    {
        var builder = ImmutableDictionary.CreateBuilder<string, string?>(StringComparer.OrdinalIgnoreCase);
        foreach (var kvp in settings.Values)
        {
            var value = kvp.Value;
            if (binderOptions.TrimWhitespace && value is not null)
            {
                value = value.Trim();
            }

            if (binderOptions.TreatEmptyAsNull && string.IsNullOrEmpty(value))
            {
                value = null;
            }

            if (value is not null && binderOptions.ExpandEnvironmentVariables)
            {
                value = Environment.ExpandEnvironmentVariables(value);
            }

            if (binderOptions.ValueTransformer is not null)
            {
                value = binderOptions.ValueTransformer.Invoke(kvp.Key, value);
            }

            builder[kvp.Key] = value;
        }

        return builder.ToImmutable();
    }

    private static IConfiguration BuildConfiguration(ImmutableDictionary<string, string?> values)
    {
        var sources = new List<KeyValuePair<string, string?>>();
        foreach (var kvp in values)
        {
            if (kvp.Value is not null)
            {
                sources.Add(new KeyValuePair<string, string?>(kvp.Key, kvp.Value));
            }
        }

        var configurationBuilder = new ConfigurationBuilder();
        configurationBuilder.Add(new DictionaryConfigurationSource(sources));
        return configurationBuilder.Build();
    }

    private static void ValidateDataAnnotations<TOptions>(TOptions options, IList<string> errors)
    {
        var validationResults = new List<ValidationResult>();
        var validationContext = new ValidationContext(options!);
        if (!Validator.TryValidateObject(options!, validationContext, validationResults, validateAllProperties: true))
        {
            foreach (var validationResult in validationResults)
            {
                if (!string.IsNullOrWhiteSpace(validationResult.ErrorMessage))
                {
                    errors.Add(validationResult.ErrorMessage);
                }
            }
        }
    }

    private sealed class DictionaryConfigurationSource : IConfigurationSource
    {
        private readonly IReadOnlyList<KeyValuePair<string, string?>> _data;

        public DictionaryConfigurationSource(IEnumerable<KeyValuePair<string, string?>> data)
        {
            _data = data?.ToList() ?? new List<KeyValuePair<string, string?>>();
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder) => new DictionaryConfigurationProvider(_data);
    }

    private sealed class DictionaryConfigurationProvider : ConfigurationProvider
    {
        public DictionaryConfigurationProvider(IEnumerable<KeyValuePair<string, string?>> data)
        {
            foreach (var pair in data)
            {
                if (pair.Value is not null)
                {
                    Data[pair.Key] = pair.Value;
                }
            }
        }
    }
}
