using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;

namespace Http.Core
{
    public class HttpClientFactory : IDisposable
    {
        private HttpClientFactory() { }
        private static readonly object _instanceLock = new object();
        private static HttpClientFactory _instance;
        public static HttpClientFactory Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_instanceLock)
                    {
                        if (_instance == null)
                            _instance = new HttpClientFactory();
                    }
                }
                return _instance;
            }
        }

        public void Dispose()
        {
            foreach (var config in _configs)
            {
                config.Value.Dispose();
            }
            _configs.Clear();
        }

        private readonly ConcurrentDictionary<string, HttpClientConfig> _configs = new ConcurrentDictionary<string, HttpClientConfig>();

        public delegate void ConfigureBuilder(HttpClientBuilder builder);
        public static HttpClientBuilder AddHttpClient(string name)
        {
            var builder = new HttpClientBuilder();
            _ = Instance._configs.TryAdd(name, builder.Config);
            return builder;
        }

        public static HttpClient CreateClient(string name)
        {
            if (!Instance._configs.ContainsKey(name))
                _ = Instance._configs.TryAdd(name, new HttpClientConfig());
            var config = Instance._configs[name];

            var client = new HttpClient(config.Handler, false);
            config.ConfigureClient?.Invoke(client);
            return client;
        }

        public delegate void ConfigureClient(HttpClient client);
        public delegate DelegatingHandler HandlerFactory();
        public class HttpClientBuilder
        {
            internal HttpClientConfig Config { get; } = new HttpClientConfig();

            public HttpClientBuilder AddConfigureClient(ConfigureClient configureClient)
            {
                Config.ConfigureClient += configureClient;
                return this;
            }

            public HttpClientBuilder AddHttpMessageHandler(HandlerFactory configureHandler)
            {
                Config.HandlerFactories.Add(configureHandler);
                return this;
            }
        }

        internal class HttpClientConfig : IDisposable
        {
            internal ConfigureClient ConfigureClient { get; set; }
            internal ICollection<HandlerFactory> HandlerFactories { get; private set; }

            internal HttpClientConfig()
            {
                HandlerFactories = new List<HandlerFactory>();
            }

            private readonly object _handlerLock = new object();
            private HttpMessageHandler _handler;
            internal HttpMessageHandler Handler
            {
                get
                {
                    if (_handler == null)
                    {
                        lock (_handlerLock)
                        {
                            if (_handler == null)
                                _handler = BuildHandler();
                        }
                    }
                    return _handler;
                }
            }

            private HttpMessageHandler BuildHandler()
            {
                if (HandlerFactories == null || !HandlerFactories.Any())
                    return new HttpClientHandler();

                DelegatingHandler handler, intermediate;
                handler = intermediate = HandlerFactories.First().Invoke();
                foreach (var handlerFactory in HandlerFactories.Skip(1))
                {
                    intermediate.InnerHandler = handlerFactory();
                    intermediate = intermediate.InnerHandler as DelegatingHandler;
                }
                intermediate.InnerHandler = new HttpClientHandler();
                return handler;
            }

            public void Dispose()
            {
                ConfigureClient = null;
                HandlerFactories?.Clear();
                HandlerFactories = null;
                _handler?.Dispose();
            }
        }
    }
}