#pragma warning disable 1998
using Blazored.LocalStorage;
using ITVisions.Blazor;
using Microsoft.AspNetCore.Components.Authorization;
using MiracleListAPI;
using System;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Web
{
 public class MLAuthenticationStateProvider : AuthenticationStateProvider
 {
  #region DI / [Inject] not possible here
  private MiracleListProxy proxy { get; set; } = null; // Backend Proxy
  private BlazorUtil blazorUtil { get; set; } = null; // Utility for Browser Console
  private ILocalStorageService localStorage { get; set; } // Local Storage in Browser
  #endregion

  // Data from Login() operation
  public LoginInfo CurrentLoginInfo { get; set; } = null;

  // Name of local Storage Keys
  const string LoginInfoStorageKey = "LoginInfo";
  const string BackendStorageKey = "Backend";

  public MLAuthenticationStateProvider(MiracleListProxy proxy, BlazorUtil blazorUtil, ILocalStorageService localStorage)
  {
   // DI
   this.blazorUtil = blazorUtil;
   this.proxy = proxy;
   this.localStorage = localStorage;
  }

  /// <summary>
  /// Login to be called by Razor Component Login.razor
  /// </summary>
  public async Task<LoginInfo> LogIn(string username, string password, string backend)
  {
   var l = new LoginInfo() { Username = username, Password = password, ClientID = AppSettings.ClientID };
   proxy.BaseUrl = backend;
   blazorUtil.Log($"{nameof(LogIn)}: Login at {backend} for {l.Username} ...");
   try
   {
    CurrentLoginInfo = await proxy.LoginAsync(l);

    if (String.IsNullOrEmpty(CurrentLoginInfo.Token))
    {
     blazorUtil.Log($"{nameof(LogIn)}: Login Error: {CurrentLoginInfo.Message}!");
    }
    else
    {
     blazorUtil.Log($"{nameof(LogIn)}: Login OK :-) Token={CurrentLoginInfo.Token}!");
    }
   }
   catch (Exception ex)
   {

    blazorUtil.Log($"{nameof(LogIn)}: Login Error: {ex}!");
    this.CurrentLoginInfo = new LoginInfo
    {
     Message = ex.ToString()
    };
   }

   // Notify new state!
   Notify();
   // Store user token and backend URL in local Storage
   blazorUtil.Log("Write to Local storage", proxy.BaseUrl + "/" + CurrentLoginInfo.Username);
   await localStorage.SetItemAsync(LoginInfoStorageKey, CurrentLoginInfo);
   await localStorage.SetItemAsync(BackendStorageKey, backend);
   return CurrentLoginInfo;
  }

  /// <summary>
  /// Logout to be called by Razor Component Login.razor
  /// </summary>
  public async Task Logout()
  {
   blazorUtil.Log("Logout", this.CurrentLoginInfo);
   if (this.CurrentLoginInfo == null) return;
   var e = await proxy.LogoffAsync(this.CurrentLoginInfo.Token);
   blazorUtil.Log("Logout-Result", e);
   if (e)
   {
    // Store the LoginInfo without token to present username again in Login.razor
    CurrentLoginInfo.Token = null;
    await localStorage.SetItemAsync(LoginInfoStorageKey, CurrentLoginInfo);
    // Remove LoginInfo in RAM for clearing authenticaton state
    CurrentLoginInfo = null;
    Notify();
   }
  }

  /// <summary>
  /// Called by Blazor infrastructure if AuthenticationState is unknown
  /// </summary>
  /// <returns>AuthenticationState</returns>
  public override async Task<AuthenticationState> GetAuthenticationStateAsync()
  {

   if (CurrentLoginInfo == null)
   {
    try
    {
     blazorUtil.Log("Reading local storage..");
     proxy.BaseUrl = await localStorage.GetItemAsync<string>(BackendStorageKey);
     this.CurrentLoginInfo = await localStorage.GetItemAsync<LoginInfo>(LoginInfoStorageKey);

     blazorUtil.Log("Data from Local storage", "URL=" + proxy.BaseUrl + " USER=" + CurrentLoginInfo?.Username);
    }
    catch (Exception ex)
    {
     blazorUtil.Log(nameof(GetAuthenticationStateAsync) + ": cannot read data from local storage: " + ex.ToString());
     CurrentLoginInfo = null;
    }

    if (this.CurrentLoginInfo != null && !String.IsNullOrEmpty(this.CurrentLoginInfo.Token) && !String.IsNullOrEmpty(proxy.BaseUrl))
    {
     // Re-Validate token (is the token still valid?)
     blazorUtil.Log($"Re-Validating token {this.CurrentLoginInfo.Token}...");
     try
     {
      this.CurrentLoginInfo = await proxy.LoginAsync(CurrentLoginInfo);
      blazorUtil.Log("Data from Re-Validation", this.CurrentLoginInfo);
      if (String.IsNullOrEmpty(CurrentLoginInfo.Token))
      {
       blazorUtil.Log($"{nameof(LogIn)}: Token not valid: {CurrentLoginInfo.Message}!");
       CurrentLoginInfo = null;
      }
     }
     catch (Exception ex)
     {
      blazorUtil.Log($"{nameof(GetAuthenticationStateAsync)}: cannot re-validate token {this.CurrentLoginInfo.Token}: {ex}");
      CurrentLoginInfo = null;
     }
    }

    if (CurrentLoginInfo == null)
    {
     blazorUtil.Log(nameof(GetAuthenticationStateAsync) + ": not authenticated!");
     return CreateAuthenticationState(null); // null = kein User!
    }
   }
   else
   {
    blazorUtil.Log(nameof(GetAuthenticationStateAsync) + ":" + CurrentLoginInfo.Username);
   }

   return CreateAuthenticationState(CurrentLoginInfo);
  }

  /// <summary>
  /// Convert LoginInfo to AuthenticationState
  /// </summary>
  private AuthenticationState CreateAuthenticationState(LoginInfo l)
  {
   // If you create your identity including authenticationType parameter the user is authenticated. If you create your identity without authenticationType parameter, the user is not authenticated.
   if (l == null || String.IsNullOrEmpty(l.Token))
   { // not authenticated :-(
    return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
   }
   else   // authenticated :-)
   {
    const string authType = "MiracleList WebAPI Authentication";
    var identity = new ClaimsIdentity(new[]
    {
    new Claim("Backend", proxy.BaseUrl),
    new Claim(ClaimTypes.Sid, l.Token), // use SID claim for token
    new Claim(ClaimTypes.Name, l.Username),
    }, authType);

    var user = new ClaimsPrincipal(identity);
    return new AuthenticationState(user);
   }
  }

  /// <summary>
  /// Notify Blazor infrastructure about new Authentication State
  /// </summary>
  private void Notify()
  {
   var aus = CreateAuthenticationState(CurrentLoginInfo);
   var e = Task.FromResult(aus);
   this.NotifyAuthenticationStateChanged(e);
  }
 }
}