#if !UNITY_WEBGL || UNITY_EDITOR
using Best.HTTP.Hosts.Connections;
using Best.HTTP.Request.Timings;
using Best.HTTP.Shared;

using System;

namespace Best.HTTP.Proxies.Autodetect
{
    /// <summary>
    /// Interface for custom proxy-detection logic.
    /// </summary>
    public interface IProxyDetector
    {
        /// <summary>
        /// Receives the <see cref="HTTPRequest"/> instance this detector has to try to find a proxy.
        /// </summary>
        /// <param name="request"><see cref="HTTPRequest"/>instance to find a proxy for</param>
        /// <returns>A concrete <see cref="Proxy"/> implementation, or <c>null</c> if no proxy could be found.</returns>
        Proxy GetProxy(HTTPRequest request);
    }

    /// <summary>
    /// Possible detection modes the <see cref="ProxyDetector"/> can be in.
    /// </summary>
    public enum ProxyDetectionMode
    {
        /// <summary>
        /// In Continuous mode the ProxyDetector will check for a proxy for every request.
        /// </summary>
        Continuous,

        /// <summary>
        /// This mode will cache the first Proxy found and use it for consecutive requests.
        /// </summary>
        CacheFirstFound
    }

    /// <summary>
    /// Helper class to contain, manage and execute logic to detect available proxy on the network. It's a wrapper class to execute the various <see cref="IProxyDetector"/>s.
    /// </summary>
    public sealed class ProxyDetector
    {
        public static IProxyDetector[] GetDefaultDetectors() => new IProxyDetector[] {
                // HTTPManager.Proxy has the highest priority
                new ProgrammaticallyAddedProxyDetector(),

                // then comes the environment set
                new EnvironmentProxyDetector(),

                // .net framework's detector
                new FrameworkProxyDetector(),

#if UNITY_ANDROID && !UNITY_EDITOR
                new AndroidProxyDetector(),
#endif
            };

        private IProxyDetector[] _proxyDetectors;
        private ProxyDetectionMode _detectionMode;
        private bool _attached;

        public ProxyDetector()
            : this(ProxyDetectionMode.CacheFirstFound, GetDefaultDetectors())
        { }

        public ProxyDetector(ProxyDetectionMode detectionMode)
            : this(detectionMode, GetDefaultDetectors())
        { }

        public ProxyDetector(ProxyDetectionMode detectionMode, IProxyDetector[] proxyDetectors)
        {
            this._detectionMode = detectionMode;
            this._proxyDetectors = proxyDetectors;

            if (this._proxyDetectors != null)
                Reattach();
        }

        public void Reattach()
        {
            HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Reattach)}({this._attached})");

            if (!this._attached)
            {
                RequestEventHelper.OnEvent += OnRequestEvent;
                this._attached = true;
            }
        }

        /// <summary>
        /// Call Detach() to disable ProxyDetector's logic to find and set a proxy.
        /// </summary>
        public void Detach()
        {
            HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Detach)}({this._attached})");

            if (this._attached)
            {
                RequestEventHelper.OnEvent -= OnRequestEvent;
                this._attached = false;
            }
        }

        private void OnRequestEvent(RequestEventInfo @event)
        {
            // The Resend event is raised for every request when it's queued up (sent or redirected).
            if (@event.Event == RequestEvents.QueuedResend &&
                @event.SourceRequest.ProxySettings != null &&
                @event.SourceRequest.ProxySettings.Proxy == null)
            {
                Uri uri = @event.SourceRequest.CurrentUri;

                if (uri.Scheme.Equals("file"))
                    return;

                @event.SourceRequest.Timing.StartNext(TimingEventNames.ProxyDetection);

                try
                {
                    for (int i = 0; i < this._proxyDetectors.Length; i++)
                    {
                        var detector = this._proxyDetectors[i];

                        if (detector == null)
                            continue;

                        if (HTTPManager.Logger.IsDiagnostic)
                            HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Calling {detector.GetType().Name}'s GetProxy", @event.SourceRequest.Context);

                        Proxy proxy = null;

#if ENABLE_PROFILER
                        using (var _ = new Unity.Profiling.ProfilerMarker($"{detector.GetType().Name}.GetProxy").Auto())
#endif
                            proxy = detector.GetProxy(@event.SourceRequest);

#if ENABLE_PROFILER
                        using (var _ = new Unity.Profiling.ProfilerMarker($"{detector.GetType().Name}.UseProxyForAddress").Auto())
#endif
                            if (proxy != null && proxy.UseProxyForAddress(uri))
                            {
                                if (HTTPManager.Logger.IsDiagnostic)
                                    HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"[{detector.GetType().Name}] Proxy found: {proxy.Address} ", @event.SourceRequest.Context);

                                switch (this._detectionMode)
                                {
                                    case ProxyDetectionMode.Continuous:
                                        @event.SourceRequest.ProxySettings.Proxy = proxy;
                                        break;

                                    case ProxyDetectionMode.CacheFirstFound:
                                        HTTPManager.Proxy = @event.SourceRequest.ProxySettings.Proxy = proxy;

                                        HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Proxy cached in HTTPManager.Proxy!", @event.SourceRequest.Context);

                                        Detach();
                                        break;
                                }

                                return;
                            }
                    }

                    if (this._detectionMode == ProxyDetectionMode.CacheFirstFound)
                        Detach();
                    
                    HTTPManager.Logger.Information(nameof(ProxyDetector), $"No Proxy for '{uri}'.", @event.SourceRequest.Context);
                }
                catch (Exception ex)
                {
                    if (HTTPManager.Logger.IsDiagnostic)
                        HTTPManager.Logger.Exception(nameof(ProxyDetector), $"GetProxyFor({@event.SourceRequest.CurrentUri})", ex, @event.SourceRequest.Context);
                }
                finally
                {
                    @event.SourceRequest.Timing.StartNext(TimingEventNames.Queued);
                }
            }
        }
    }
}

#endif
