using System.Net;
using System.Text.Json;
using System.Text;

using MesaFabApproval.Shared.Models;

using Microsoft.Extensions.Caching.Memory;
using System.Net.Http.Headers;
using MesaFabApproval.Client.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using System.Net.Http;

namespace MesaFabApproval.Client.Utilities;

public class ApiHttpClientHandler : DelegatingHandler {
    private readonly IMemoryCache _cache;
    private readonly IAuthenticationService _authService;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly ISnackbar _snackbar;
    private readonly MesaFabApprovalAuthStateProvider _authStateProvider;
    private readonly NavigationManager _navigationManager;

    private readonly string _apiBaseUrl;

    public ApiHttpClientHandler(IMemoryCache cache,
                                IConfiguration config,
                                IAuthenticationService authService,
                                IHttpClientFactory httpClientFactory,
                                ISnackbar snackbar,
                                MesaFabApprovalAuthStateProvider authStateProvider,
                                NavigationManager navigationManager) {
        _cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
        _apiBaseUrl = config["FabApprovalApiBaseUrl"] ??
            throw new NullReferenceException("FabApprovalApiBaseUrl not found in config");
        _httpClientFactory = httpClientFactory ??
            throw new ArgumentNullException("IHttpClientFactory not injected");
        _snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected");
        _authService = authService ??
            throw new ArgumentNullException("IAuthenticationService not injected");
        _authStateProvider = authStateProvider ??
            throw new ArgumentNullException("MesaFabApprovalAuthStateProvider not injected");
        _navigationManager = navigationManager ??
            throw new ArgumentNullException("NavigationManager not injected");
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage,
                                                                 CancellationToken cancellationToken) {
        AuthTokens? authTokens = await _authService.GetAuthTokens();

        HttpRequestMessage initialRequestMessage = new() { 
            Content = requestMessage.Content,
            Method = requestMessage.Method,
            RequestUri = requestMessage.RequestUri
        };

        if (authTokens is not null) {
            initialRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
        }

        HttpClient initialClient = _httpClientFactory.CreateClient("API_Handler");

        HttpResponseMessage responseMessage = await initialClient.SendAsync(initialRequestMessage, cancellationToken);

        HttpClient refreshClient = _httpClientFactory.CreateClient("API_Handler");

        if (responseMessage.StatusCode.Equals(HttpStatusCode.Unauthorized)) {
            string? loginId = await _authService.GetLoginId();

            if (!string.IsNullOrWhiteSpace(loginId)) {
                AuthAttempt authAttempt = new() {
                    LoginID = loginId,
                    AuthTokens = authTokens
                };

                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/refresh");

                request.Content = new StringContent(JsonSerializer.Serialize(authAttempt),
                                                    Encoding.UTF8,
                                                    "application/json");

                if (authTokens is not null) {
                    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
                }

                HttpResponseMessage httpResponseMessage = await refreshClient.SendAsync(request, cancellationToken);

                string responseContent = await httpResponseMessage.Content.ReadAsStringAsync();
                
                if (httpResponseMessage.IsSuccessStatusCode) {
                    JsonSerializerOptions jsonSerializerOptions = new() {
                        PropertyNameCaseInsensitive = true
                    };

                    LoginResult loginResult = JsonSerializer.Deserialize<LoginResult>(responseContent, jsonSerializerOptions) ??
                        throw new Exception("unable to parse login result from API response");

                    if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {loginId} not authorized");

                    if (loginResult.AuthTokens is not null) {
                        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.AuthTokens.JwtToken);
                        await _authService.SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken);
                    }

                    if (loginResult.User is not null)
                        await _authService.SetCurrentUser(loginResult.User);
                } else {
                    await _authStateProvider.Logout();

                    string? redirectUrl = _cache.Get<string>("redirectUrl");

                    if (!string.IsNullOrWhiteSpace(redirectUrl)) {
                        _navigationManager.NavigateTo($"login/{redirectUrl}");
                    } else {
                        _navigationManager.NavigateTo("login");
                    }
                }
            }

            return await base.SendAsync(requestMessage, cancellationToken);
        }

        initialClient.Dispose();
        refreshClient.Dispose();

        return responseMessage;
    }
}