using Http.Core.Constants;
using Http.Core.Contexts;
using Http.Core.Exceptions;
using Http.Core.Extensions;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Http.Core.Handlers
{
    public class AuthenticationHandler : DelegatingHandler
    {
        public static readonly TimeSpan _requiredRefreshTime = TimeSpan.FromMinutes(5);

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var shouldContinue = await AppendTokenAsync(request);
            if (!shouldContinue)
                return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
            return await base.SendAsync(request, cancellationToken);
        }

        private static readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
        private async Task<bool> AppendTokenAsync(HttpRequestMessage request)
        {
            if (request.Headers.Authorization == null)
            {
                if (UserContext.Instance.AccessToken == null)
                    return false;

                var expiredTime = UserContext.Instance.ExpiredTime;
                if (expiredTime != null && expiredTime - DateTime.UtcNow < _requiredRefreshTime)
                {
                    await _refreshSemaphore.WaitAsync();
                    bool refreshSuccess;
                    {
                        expiredTime = UserContext.Instance.ExpiredTime;
                        if (expiredTime != null && expiredTime - DateTime.UtcNow < _requiredRefreshTime)
                            refreshSuccess = await RefreshTokenAsync();
                        else
                            refreshSuccess = true; // Some other thread already refreshed token
                    }
                    _refreshSemaphore.Release();
                    if (!refreshSuccess)
                        return false;
                }
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", UserContext.Instance.AccessToken);
                return true;
            }
            return true;
        }

        private async Task<bool> RefreshTokenAsync()
        {
            try
            {
                using (var client = HttpClientFactory.CreateClient(HttpConstants.ClientNames.AuthClient))
                {
                    return await client.RefreshTokenAsync();
                }
            }
            catch (Exception ex) when (!(ex is AppOutdatedException)) { }
            return false;
        }
    }
}
