using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

namespace BlazorClient
{
  public class JwtAuthStateProvider : AuthenticationStateProvider
  {
    private readonly HttpClient _http;
    private readonly LocalStorage _storage;

    public JwtAuthStateProvider(HttpClient http, LocalStorage storage)
    {
      _http = http;
      _storage = storage;
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
      return AuthenticationStateFromCurrentToken();
    }

    public async Task MarkUserAsAuthenticated(string token)
    {
      await _storage.Save("token", token);
      NotifyAuthenticationStateChanged(AuthenticationStateFromCurrentToken());
    }

    public async Task LogoutCurrentUser()
    {
      await _storage.Save("token", string.Empty);
      NotifyAuthenticationStateChanged(AuthenticationStateFromCurrentToken());
    }

    private async Task<AuthenticationState> AuthenticationStateFromCurrentToken()
    {
      var token = await _storage.Get("token");

      if (string.IsNullOrWhiteSpace(token))
      {
        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
      }

      _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

      var claims = ParseClaimsFromJwt(token);
      var principal = new ClaimsPrincipal(
        new ClaimsIdentity(claims, "jwt", "unique_name", null));
      return new AuthenticationState(principal);
    }

    private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
    {
      var claims = new List<Claim>();
      var payload = jwt.Split('.')[1];
      var jsonBytes = ParseBase64WithoutPadding(payload);
      var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

      keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

      if (roles != null)
      {
        if (roles.ToString().Trim().StartsWith("["))
        {
          var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());

          foreach (var parsedRole in parsedRoles)
          {
            claims.Add(new Claim(ClaimTypes.Role, parsedRole));
          }
        }
        else
        {
          claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
        }

        keyValuePairs.Remove(ClaimTypes.Role);
      }

      claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
      return claims;
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
      switch (base64.Length % 4)
      {
        case 2: base64 += "=="; break;
        case 3: base64 += "="; break;
      }
      return Convert.FromBase64String(base64);
    }
  }
}