using System;
using System.Threading.Tasks;
using System.Data.SqlClient;

using CommonHelpers;

using BookRegistry.Server.DB;

namespace BookRegistry.Server {

//**************************************************************************************************
/// <summary>
/// User authorization DB-queries for “UserAuthorizationController”-class
/// </summary>
class UserAuthorization {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public ServiceUser_Info? UserInfo { get; private set; }
public string? ErrorMessage  { get; private set; }
public Guid Status { get; private set; }
public string? TechMessage  { get; private set; }

/// <exception cref="ArgumentException" />
public static Task<UserAuthorization> AuthorizeUser_A( string loginName, string password )
{
   loginName = loginName.Trim();
   return loginName.StartsWith('$') ?
      AuthorizeAdministrator_A( loginName, password ) :
      AuthorizeGeneralUser_A( loginName, password );
}

//==================================================================================================

/// <exception cref="ArgumentException" />
static async Task<UserAuthorization> AuthorizeGeneralUser_A( string loginName, string password )
{
   if ( loginName.StartsWith('$') )
      throw new ArgumentException(
         "Login-name is incorrect for general user autorization.",
         nameof(loginName) );

   var ( spr, userInfo ) = await GetServiceUser_SP.EXEC_A( loginName );
   if ( !spr.Success )
      return Error( spr );

   if ( userInfo == null )
      return ErrorFmt(HLocal.TEXT(
         "User not found: \"{0}\" (login-name).",
         "Пользователь не обнаружен: \"{0}\" (имя входа)." ),
         loginName );

   if ( !HString.Equals( loginName, userInfo.LoginName, true ) )
      return AuthorizationFailure_Error( loginName, "Unexpected user name mismatch." );

   byte[]? passwordData = userInfo.PasswordData;
   if ( passwordData==null || passwordData.Length==0 )
      return AuthorizationFailure_Error( loginName, "Password data is unavailable." );
   byte[]? maskedPassword = PasswordMasking.MaskUserPassword( password );
   if ( maskedPassword==null || maskedPassword.Length != passwordData.Length )
      goto INCORRECT_PASSWORD;
   for (int i=0; i<maskedPassword.Length; i++)
      if ( maskedPassword[i] != passwordData[i] )
         goto INCORRECT_PASSWORD;
   if ( password != PasswordMasking.UnmaskUserPassword( passwordData ) )
      goto INCORRECT_PASSWORD;

   return new UserAuthorization(){ UserInfo = userInfo };
INCORRECT_PASSWORD:
   return AuthorizationFailure_Error( loginName, "Incorrect password." );
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="ArgumentException" />
static async Task<UserAuthorization> AuthorizeAdministrator_A( string loginName, string password )
{
   if ( !loginName.StartsWith('$') )
      throw new ArgumentException(
         "Login-name is incorrect for admin-user authorization.",
         nameof(loginName) );

   var ( spr, userInfo ) = await GetServiceUser_SP.EXEC_A( loginName );
   if ( !spr.Success )
      return Error( spr );

   if ( userInfo == null )
      return AuthorizationFailure_Error( loginName,
         $"User not found: \"{loginName}\" (administration login-name)." );
         // ^ technical message 

   var connection = new SqlConnection(
      $"Server={StartupData.ServerName};User ID={loginName};Password={password};"+
      $"Trusted_Connection=False;Application Name={Program.AppModuleBaseName} (Web-service);"+
      $"Current Language={StartupData.DatabaseLanguage};"+
      $"MultipleActiveResultSets=False;Pooling=False" );
   try {
      using ( connection )
      {
         await connection.OpenAsync();
         connection.Close();
      }
   } catch (Exception e) {
      return AuthorizationFailure_Error( loginName, HException.GetErrorMessage(e) );
   }

   return new UserAuthorization(){ UserInfo = userInfo };
}

//==================================================================================================

static UserAuthorization Error( string message ) =>
   new UserAuthorization() { ErrorMessage = message };
static UserAuthorization Error( HSPCommandResult spr ) =>
   new UserAuthorization() {
      ErrorMessage = spr.ErrorMessage,
      Status = spr.Status };
static UserAuthorization ErrorFmt( string messageFmt, params object?[] parameters ) =>
   Error( HString.FormatAsText( messageFmt, parameters ) );

static UserAuthorization ErrorWithLogName( string loginName, string message ) =>
   new UserAuthorization() { ErrorMessage =
      HString.FormatAsText(HLocal.TEXT(
         "{0}\n(Login name: \"{1}\".)",
         "{0}\n(Имя входа: \"{1}\".)" ),
         message, loginName ) };
static UserAuthorization ErrorWithLogNameFmt( string loginName,
                                              string messageFmt, params object?[] parameters ) =>
   ErrorWithLogName( loginName, HString.FormatAsText( messageFmt, parameters ) );

//--------------------------------------------------------------------------------------------------

static UserAuthorization AuthorizationFailure_Error( string loginName,
                                                     string? techMessage = null )
{
   var error = ErrorWithLogNameFmt( loginName, HLocal.TEXT(
      "Authorization has failed.{0}",
      "Авторизация потерпела неудачу.{0}" ),
      Program.DevelopmentBehaviour && techMessage!=null ? "\n"+techMessage : "" );
   error.TechMessage = techMessage;
   return error;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // UserAuthorization
//**************************************************************************************************

} // BookRegistry.Server
