using e_suite.API.Common.exceptions;
using e_suite.API.Common.extensions;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.UserManager;
using e_suite.Modules.UserManager.Facade;
using e_suite.Modules.UserManager.Services;
using e_suite.Nuget.PasswordHasher;
using e_suite.Utilities.Pagination;
using eSuite.Core.Clock;
using eSuite.Core.MailService;
using eSuite.Core.Miscellaneous;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using System.Text;
using System.Text.Json;
using e_suite.Modules.UserManager.Extensions;
using IUserManager = e_suite.API.Common.IUserManager;
using System.Data;
using e_suite.API.Common;

namespace e_suite.Modules.UserManager;

public class UserManager : IUserManager
{
    private readonly IConfiguration _configuration;
    private readonly IPasswordHasher<IPassword> _passwordHasher;
    private readonly ITwoFactorAuthenticator _twoFactorAuthenticator;
    private readonly IJwtService _jwtService;
    private readonly IMailService _mailService;
    private readonly IRandomNumberGenerator _randomNumberGenerator;
    private readonly IClock _clock;
    private readonly IPatchFactory _patchFactory;

    private readonly IUserManagerRepository _userManagerRepository;
    private readonly IDomainRepository _domainRepository;
    

    public UserManager(IConfiguration configuration, IPasswordHasher<IPassword> passwordHasher, ITwoFactorAuthenticator twoFactorAuthenticator, IJwtService jwtService, IMailService mailService, IRandomNumberGenerator randomNumberGenerator, IClock clock, IUserManagerRepository userManagerRepository, IDomainRepository domainRepository, IPatchFactory patchFactory)
    {
        _configuration = configuration;
        _passwordHasher = passwordHasher;
        _twoFactorAuthenticator = twoFactorAuthenticator;
        _jwtService = jwtService;
        _mailService = mailService;
        _randomNumberGenerator = randomNumberGenerator;
        _clock = clock;
        _userManagerRepository = userManagerRepository;
        _domainRepository = domainRepository;
        _patchFactory = patchFactory;
    }

    private void HashPassword(User user, string password)
    {
        user.Password = _passwordHasher.HashPassword(user, password);
    }
    
    private string GetEmailUserActionUrl(User existingUser, EmailUserAction emailConfirmation)
    {
        var emailToConfirmationToken = new EmailActionToken
        {
            Email = existingUser.Email,
            EmailActionType = emailConfirmation.EmailActionType,
            Token = emailConfirmation.Token
        };

        var jsonString = JsonSerializer.Serialize(emailToConfirmationToken);
        var encodedBase64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString));

        var baseUrl = _configuration.GetConfigValue("BASE_URL", "baseUrl", "http://localhost:3000");
        var confirmEmail = $"{baseUrl?.Trim().TrimEnd('/')}/emailuseraction/{encodedBase64String}";
        return confirmEmail;
    }

    private async Task SendEmailUserAction(AuditUserDetails auditUserDetails, User user, EmailUserActionType type,
        CancellationToken cancellationToken)
    {
        if (user == null)
            throw new Exception("User not found");

        if (!user.Active)
            throw new NotFoundException("Active User not found");

        var emailUserAction = await CreateEmailUserAction(auditUserDetails, user, type, cancellationToken);
        var emailUserActionUrl = GetEmailUserActionUrl(user, emailUserAction);
        await SentEmailRequest(user, emailUserActionUrl, type, cancellationToken);
    }

    private async Task<EmailUserAction> CreateEmailUserAction(AuditUserDetails auditUserDetails, User existingUser,
        EmailUserActionType type, CancellationToken cancellationToken)
    {
        var emailTimeout = GetEmailTimeoutHours();

        var emailConfirmation = new EmailUserAction
        {
            Created = _clock.GetNow,
            Expires = _clock.GetNow.AddHours(emailTimeout),
            EmailActionType = type,
            Token = Guid.NewGuid(),
            UserId = existingUser.Id
        };

        await _userManagerRepository.AddEmailUserAction(auditUserDetails, emailConfirmation, cancellationToken);
        return emailConfirmation;
    }

    private int GetEmailTimeoutHours()
    {
        return _configuration.GetValue("Smtp:EmailTimeoutHours", 48);
    }

    private async Task SentEmailRequest(User user, string emailUserActionUrl, EmailUserActionType type, CancellationToken cancellationToken)
    {
        var emailRequest = new MailRequest();
        emailRequest.To.Add(user);
        emailRequest.EmailType = GetMailType(type);
        emailRequest.Parameters.Add("url", emailUserActionUrl);

        await _mailService.RequestEMailAsync(emailRequest, cancellationToken);
    }

    private async Task SendEmailRequest(User user, MailType mailType, CancellationToken cancellationToken)
    {
        var emailRequest = new MailRequest();
        emailRequest.To.Add(user);
        emailRequest.EmailType = mailType;

        await _mailService.RequestEMailAsync(emailRequest, cancellationToken);
    }

    private static MailType GetMailType(EmailUserActionType type)
    {
        return type switch
        {
            EmailUserActionType.ConfirmEmailAddress => MailType.ConfirmEmailAddress,
            EmailUserActionType.DisableAuthenticator => MailType.DisableAuthenticator,
            EmailUserActionType.PasswordReset => MailType.PasswordReset,
            _ => throw new ArgumentException(message: $"{type} cannot be translated to and EmailType.", paramName: nameof(type))
        };
    }

    public async Task<LoginResponse> Login(Login userLogin, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByEmail(userLogin.Email, cancellationToken);

        if (user == null)
        {
            return new LoginResponse
            {
                Result = LoginResult.Failed
            };
        }

        var result = _passwordHasher.VerifyHashedPassword(user, user.Password, userLogin.Password);

        if (result == PasswordVerificationResult.Failed)
        {
            return new LoginResponse
            {
                Result = LoginResult.Failed
            };
        }

        var auditUserDetails = new AuditUserDetails
        {
            UserId = user.Id,
            UserDisplayName = user.DisplayName
        };

        if (result == PasswordVerificationResult.SuccessRehashNeeded)
        {
            HashPassword(user, userLogin.Password);
            
            await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
        }

        if (userLogin.RequestTfaRemoval)
        {
            await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.DisableAuthenticator, cancellationToken);

            return new LoginResponse
            {
                Result = LoginResult.TwoFactorAuthenticationRemovalRequested
            };
        }

        if (user.UsingTwoFactorAuthentication)
        {
            if (string.IsNullOrWhiteSpace(userLogin.SecurityCode))
            {
                return new LoginResponse
                {
                    Result = LoginResult.TwoFactorAuthenticationCodeRequired
                };
            }

            var tfaResult =
                _twoFactorAuthenticator.ValidateTwoFactorPIN(user.TwoFactorAuthenticationKey, userLogin.SecurityCode);

            if (!tfaResult)
            {
                return new LoginResponse
                {
                    Result = LoginResult.TwoFactorAuthenticationCodeIncorrect
                };
            }
        }

        return CreateLoginResponse(user);
    }
    public async Task<LoginResponse> LoginSso(long ssoId, string ssoUserId, CancellationToken cancellationToken = default!)
    {
        var user = await _userManagerRepository.GetUserSsoId(ssoId, ssoUserId, cancellationToken) ?? await _userManagerRepository.GetUserByDomainSsoId(ssoId, ssoUserId, cancellationToken);

        return CreateLoginResponse(user);
    }

    private LoginResponse CreateLoginResponse(User? user)
    {
        if (user == null)
        {
            return new LoginResponse
            {
                Result = LoginResult.Failed
            };
        }

        if (!user.Active)
        {
            return new LoginResponse
            {
                Result = LoginResult.Failed
            };
        }

        if (!user.EmailConfirmed)
        {
            return new LoginResponse
            {
                Result = LoginResult.EmailNotConfirmed
            };
        }

        var token = _jwtService.GenerateSecurityToken(user);
        return new LoginResponse
        {
            Result = LoginResult.Success,
            Token = token
        };
    }

    public async Task CreateUser(AuditUserDetails auditUserDetails, UserRegistration userRegistration, CancellationToken cancellationToken)
    {
        await _userManagerRepository.TransactionAsync(async () =>
        {
            userRegistration.Email = userRegistration.Email.Trim();
            ValidateEmail(userRegistration.Email);
            await CheckForExistingUserByEmail(userRegistration.Email, cancellationToken);
            
            var currentUser = await _userManagerRepository.GetUserByIdAsync(new GeneralIdRef
            {
                Id = auditUserDetails.UserId
            }, cancellationToken);

            var domainId = currentUser!.DomainId; 
            if (userRegistration.DomainId != null )
            {
                var domain = await _userManagerRepository.GetDomainById(userRegistration.DomainId, cancellationToken)
                             ?? throw new NotFoundException("unable to find domain");
                domainId = domain.Id;
            }

            var user = new User
            {
                FirstName = userRegistration.FirstName.Trim(),
                MiddleNames = userRegistration.MiddleNames.Trim(),
                LastName = userRegistration.LastName.Trim(),
                Email = userRegistration.Email,
                EmailConfirmed = false,
                DomainId = domainId
            };

            DisableTwoFactorAuthenticationForUser(user);

            var password = _randomNumberGenerator.GetRandomString(10);

            HashPassword(user, password);

            await _userManagerRepository.AddUser(auditUserDetails, user, cancellationToken);

            await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken);
        });
    }

    private async Task CheckForExistingUserByEmail(string emailAddress, CancellationToken cancellationToken)
    {
        var existingUser = await _userManagerRepository.GetUserByEmail(emailAddress, cancellationToken);
        if (existingUser != null)
            throw new ExistsException("User Already Exists");
    }

    private static void ValidateEmail(string email)
    {
        if (!(new EmailAddressAttribute()).IsValid(email))
            throw new ArgumentException($"{email} is not a valid email address");
    }

    private void DisableTwoFactorAuthenticationForUser(User user)
    {
        user.UsingTwoFactorAuthentication = false;
        user.TwoFactorAuthenticationKey = GenerateTwoFactorAuthenticationKey();
    }

    private string GenerateTwoFactorAuthenticationKey()
    {
        return _randomNumberGenerator.GetRandomString(_configuration.GetValue<int>("twoFactorAuthentication:keySize"));
    }

    public async Task<LoginResponse> RefreshToken(string email, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByEmail(email, cancellationToken);
        return RefreshToken(user);
    }

    public async Task<LoginResponse> RefreshToken(IGeneralIdRef id, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByIdAsync(id, cancellationToken);
        return RefreshToken(user);
    }

    private LoginResponse RefreshToken(User? user)
    {
        return CreateLoginResponse(user);
    }
    
    public async Task ForgotPassword(string email, CancellationToken cancellationToken)
    {
        await _userManagerRepository.TransactionAsync( async () =>
        {
            var user = await GetUserByEmailAsync(email, cancellationToken);

            if (!user.EmailConfirmed) {
                throw new EmailNotConfirmedException("Email not confirmed");
            }
            
            var auditUserDetails = new AuditUserDetails
            {
                UserId = user.Id,
                UserDisplayName = user.DisplayName
            };

            await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.PasswordReset, cancellationToken);
        });
    }

    public async Task<User> CompleteEmailAction(EmailActionToken token, CancellationToken cancellationToken)
    {
        return await _userManagerRepository.TransactionAsync(async () =>
        {
            var emailUserAction = await _userManagerRepository.GetEmailUserAction(token.Token, cancellationToken)
                                  ?? throw new TokenInvalidException("Your email token is invalid");

            if ((emailUserAction.EmailActionType == EmailUserActionType.ConfirmEmailAddress ||
                 emailUserAction.EmailActionType == EmailUserActionType.PasswordReset))
                CheckPasswordStrength(token.Password);

            var user = emailUserAction.User;

            if (!user.Email.Equals(token.Email, StringComparison.CurrentCultureIgnoreCase))
                throw new InvalidEmailException("Your email is invalid");

            switch (emailUserAction.EmailActionType)
            {
                case EmailUserActionType.ConfirmEmailAddress:
                    throw new ArgumentException($"EmailActionType {emailUserAction.EmailActionType} not supported, use the UI instead.");
                case EmailUserActionType.DisableAuthenticator:
                    DisableTwoFactorAuthenticationForUser(user);
                    break;
                case EmailUserActionType.PasswordReset:
                    AddPasswordHash(user,token.Password);
                    break;
                default:
                    throw new ArgumentException($"EmailActionType {emailUserAction.EmailActionType} not implemented");
            }

            var auditUserDetails = new AuditUserDetails
            {
                UserId = user.Id,
                UserDisplayName = user.DisplayName
            };

            await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);

            await _userManagerRepository.DeleteEmailUserAction(auditUserDetails, emailUserAction, cancellationToken);

            if (emailUserAction.EmailActionType == EmailUserActionType.PasswordReset) {
                await SendEmailRequest(user, MailType.PasswordResetCompleted, cancellationToken);
            }
            return user;
        });
    }

    public async Task DeactivateUser(AuditUserDetails auditUserDetails, string email, CancellationToken cancellationToken)
    {
        await _userManagerRepository.TransactionAsync( async () =>
        {
            var user = await GetUserByEmailAsync(email, cancellationToken);

            await DeactivateUser(auditUserDetails, user, cancellationToken);
        });
    }

    public async Task DeactivateUser(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        await _userManagerRepository.TransactionAsync(async () =>
        {
            var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken)
                ?? throw new NotFoundException("Unable to find user");

            await DeactivateUser(auditUserDetails, user, cancellationToken);
        });
    }

    private async Task DeactivateUser(AuditUserDetails auditUserDetails, User user, CancellationToken cancellationToken)
    {
        if (!user.Active)
            throw new InvalidOperationException("User already deactivated");

        if (user.Id == auditUserDetails.UserId)
            throw new InvalidOperationException("You can't delete yourself");

        user.Active = false;

        await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
    }
    
    public async Task<UserProfile> GetProfile(string email, CancellationToken cancellationToken)
    {
        var applicationName = _configuration.GetValue<string>("applicationName");

        if (string.IsNullOrWhiteSpace(applicationName))
        {
            throw new SystemException("applicationName has not been set");
        }

        var user = await GetUserByEmailAsync(email, cancellationToken);

        var setupInfo = _twoFactorAuthenticator.GenerateSetupCode(applicationName, user.Email, user.TwoFactorAuthenticationKey, false);

        var twoFactorAuthenticationSettings = new TwoFactorAuthenticationSettings
        {
            QrCodeImageUrl = setupInfo.QrCodeSetupImageUrl,
            ManualEntrySetupCode = setupInfo.ManualEntryKey
        };

        var ssoProviders = GetSsoProvidersForUser();

        var userProfile = new UserProfile
        {
            FirstName = user.FirstName,
            MiddleNames = user.MiddleNames,
            LastName = user.LastName,
            Email = user.Email,
            UsingTwoFactorAuthentication = user.UsingTwoFactorAuthentication,
            TwoFactorAuthenticationSettings = twoFactorAuthenticationSettings,
            Created = user.Created,
            DomainSsoProviderId = user.Domain.SsoProviderId,
            SsoProviderId = user.SsoProviderId,
            SsoSubject = user.SsoSubject,
            SsoProviders = ssoProviders
                .ToDictionary(x => x.Id, x => x.Name),
            PreferredLocale = user.PreferredLocale
        };
        
        return userProfile;
    }

    private IEnumerable<SsoProvider> GetSsoProvidersForUser()
    {
        var ssoProviders = _userManagerRepository.GetSsoProviders().Where(x => x.Deleted == false && x.IsPublic == true);

        return ssoProviders;
    }

    public async Task UpdateProfile(AuditUserDetails auditUserDetails, string email, UpdatedUserProfile userProfile, CancellationToken cancellationToken)
    {
        await AlterProfile(auditUserDetails, email, async user =>
        {
            userProfile.Email = userProfile.Email.Trim();

            if (!string.IsNullOrWhiteSpace(userProfile.Email))
            {
                ValidateEmail(userProfile.Email);

                if (user.Email != userProfile.Email)
                {
                    await CheckForExistingUserByEmail(userProfile.Email, cancellationToken);
                    user.EmailConfirmed = false;
                    user.Email = userProfile.Email;
                }
            }

            user.FirstName = userProfile.FirstName;
            user.MiddleNames = userProfile.MiddleNames;
            user.LastName = userProfile.LastName;
            user.Email = userProfile.Email;
            user.PreferredLocale = userProfile.PreferredLocale;

            await SetInternalAuthenticationDetails(user, userProfile, true, cancellationToken);
        },
        cancellationToken );
    }

    public async Task<LoginResponse> PatchProfile( AuditUserDetails auditUserDetails, string email, PatchUserProfile patchUserProfile, CancellationToken cancellationToken)
    {
        var patch = _patchFactory.Create(patchUserProfile);
        
        var user = await AlterProfile(auditUserDetails, email, async user =>
            {
                await patch.ApplyTo(user);
            },
            cancellationToken);

        return CreateLoginResponse(user);
    }

    private async Task<User> AlterProfile(
        AuditUserDetails auditUserDetails,
        string email,
        Func<User, Task> applyChanges,
        CancellationToken cancellationToken
    )
    {
        User user = null!;
        await _userManagerRepository.TransactionAsync(async () =>
        {
            user = await GetUserByEmailAsync(email, cancellationToken);
            
            await applyChanges(user);
            
            await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);

            if (!user.EmailConfirmed)
            {
                await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress,
                    cancellationToken);
            }
            
        });

        return user;
    }
    private async Task SetInternalAuthenticationDetails(
        User user,
        UpdatedUserProfile userProfile,
        bool sendEmail,
        CancellationToken cancellationToken
    )
    {
        if (!string.IsNullOrWhiteSpace(userProfile.Password))
        {
            HashPassword(user, userProfile.Password);
            if (sendEmail)
                await SendEmailRequest(user, MailType.PasswordResetCompleted, cancellationToken);
        }

        if (userProfile.UsingTwoFactorAuthentication != user.UsingTwoFactorAuthentication)
        {
            if (userProfile.UsingTwoFactorAuthentication)
            {
                if (!string.IsNullOrWhiteSpace(userProfile.SecurityCode))
                {
                    if (_twoFactorAuthenticator.ValidateTwoFactorPIN(user.TwoFactorAuthenticationKey,
                            userProfile.SecurityCode))
                        user.UsingTwoFactorAuthentication = true;
                }
            }
            else
                DisableTwoFactorAuthenticationForUser(user);
        }
    }

    public async Task EditUser(
        AuditUserDetails auditUserDetails,
        EditUser user,
        CancellationToken cancellationToken)
    {
        await AlterUser(auditUserDetails, user.Id, async editUser =>
        {
            var userDomain = await _userManagerRepository.GetDomainById(user.Domain, cancellationToken)
                             ?? throw new NotFoundException("unable to find domain");

            editUser.FirstName = user.FirstName;
            editUser.MiddleNames = user.MiddleNames;
            editUser.LastName = user.LastName;
            editUser.Email = user.Email;
            editUser.DomainId = userDomain.Id;
            editUser.Domain = userDomain;

        }, cancellationToken);
    }
    
    public async Task PatchUser(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef userId,
        PatchUser patchUser,
        CancellationToken cancellationToken
    )
    {
        var patch = _patchFactory.Create(patchUser);

        await AlterUser(auditUserDetails, userId, async editUser =>
        {
            await patch.ApplyTo(editUser);
        }, cancellationToken);
    }

    private async Task AlterUser(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef userId,
        Func<User, Task> applyChanges,
        CancellationToken cancellationToken)
    {
        await _userManagerRepository.TransactionAsync(async () =>
        {
            var user = await _userManagerRepository.GetUserByIdAsync(userId, cancellationToken)
                       ?? throw new NotFoundException("unable to find user");

            if (!user.Active)
                throw new DeletedRowInaccessibleException("This user is inactive so cannot be modified. You will need to create the user again.");

            await applyChanges(user);

            await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
        });
    }


    public async Task<IPaginatedData<GetUser>> GetUsersAsync(Paging paging, CancellationToken cancellationToken)
    {
        var users = _userManagerRepository.GetUsers().Where(x => x.Active == true);

        var paginatedData = await PaginatedData.Paginate(users, paging, KeySelector, cancellationToken);

        var paginatedResult = new PaginatedData<GetUser>
        {
            Count = paginatedData.Count,
            Page = paginatedData.Page,
            PageSize = paginatedData.PageSize,
            Data = paginatedData.Data.Select(MapUser)
        };
        return paginatedResult;
    }

    private Expression<Func<User, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "firstname" => x => x.FirstName,
            "lastname" => x => x.LastName,
            "middlenames" => x => x.MiddleNames,
            "displayname" => x => ((x.FirstName.Trim() + " " + x.MiddleNames).Trim() + " " + x.LastName).Trim(),
            "domainname" => x => x.Domain.Name,
            "created" => x => x.Created,
            "lastupdated" => x => x.LastUpdated,
            "email" => x => x.Email,
            _ => x => x.Email
        };
    }

    public async Task<GetUser?> GetUserAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) 
                   ?? throw new NotFoundException("User not found");

        return MapUser(user);
    }

    public async Task<User> GetUserByEmailAsync(string email, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByEmail(email, cancellationToken)
                   ?? throw new NotFoundException("User not found");

        return user;
    }

    private GetUser MapUser(User user)
    {
        var getUser = new GetUser
        {
            Id = user.Id,
            Guid = user.Guid,
            FirstName = user.FirstName,
            LastName = user.LastName,
            MiddleNames = user.MiddleNames,
            DisplayName = user.DisplayName,
            Email = user.Email,
            Created = user.Created,
            LastUpdated = user.LastUpdated,
            Domain = new GeneralIdRef
            {
                Id = user.Domain.Id,
                Guid = user.Domain.Guid
            },
            DomainName = user.Domain.Name,
            EmailConfirmed = user.EmailConfirmed
        };

        return getUser;
    }
    
    private void AddPasswordHash(User user,string tokenPass)
    {
        if (string.IsNullOrWhiteSpace(tokenPass))
            throw new ArgumentException("Invalid password supplied");
        HashPassword(user, tokenPass);
    }
    
    private static void CheckPasswordStrength(string password)
    {
        const int minPasswordLength = 12;
        const string symbols = "~`! @#$%^&*()_+={[}|\\:;\"'<,->.?/";
        
        if (password.Length < minPasswordLength)
            throw new WeakPasswordException($"Password must be at least {minPasswordLength} characters");
        
        if (!password.Contains(symbols.ToCharArray()))
            throw new WeakPasswordException($"Password must contain at least one symbol: {symbols}");

        if (!password.Any(char.IsDigit))
            throw new WeakPasswordException($"Password must contain at least one number");

        if (!password.Any(char.IsUpper))
            throw new WeakPasswordException($"Password must contain at least one upper case character");

        if (!password.Any(char.IsLower))
            throw new WeakPasswordException($"Password must contain at least one lower case character");
    }

    public async Task ResendConfirmEmail(
        AuditUserDetails auditUserDetails,
        GeneralIdRef generalIdRef,
        CancellationToken cancellationToken
    )
    {
        var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken)
                   ?? throw new NotFoundException("User not found");

        await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken);
    }

    public async Task<string> GetCurrentEmailActionUrl( string emailAddress, EmailUserActionType emailUserActionType, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByEmail(emailAddress, cancellationToken)
                   ?? throw new NotFoundException("User not found");
        var emailUserAction = await _userManagerRepository.GetCurrentEmailUserAction(user.Id, emailUserActionType, cancellationToken)
                              ?? throw new NotFoundException("Email action not found");
        var emailUserActionUrl = GetEmailUserActionUrl(user, emailUserAction);
        return emailUserActionUrl;
    }

    public async Task<SsoProvider?> GetSsoProviderForEmail(string loginEmail, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByEmail(loginEmail, cancellationToken);

        if (user?.DomainId != null)
        {
            var domain =
                await _domainRepository.GetDomainById(new GeneralIdRef { Id = user.DomainId }, cancellationToken);

            if (domain?.SsoProvider != null)
                return domain.SsoProvider;
        }

        return user?.SsoProvider;
    }

    public async Task<SsoProvider?> GetSsoProviderById(long ssoProviderId, CancellationToken cancellationToken)
    {
        var ssoProvider = await _userManagerRepository.GetSsoProviderById(ssoProviderId, cancellationToken);
        return ssoProvider;
    }

    public async Task LinkSsoProfileToUser(AuditUserDetails auditUserDetails, User user, long ssoId, string ssoUserId, bool setEmailConfirmed, CancellationToken cancellationToken)
    {
        if (user == null)
            throw new NullReferenceException();


        if (user.Domain.SsoProviderId == null)
        {
            var ssoProvider = await _userManagerRepository.GetSsoProviderById(ssoId, cancellationToken) ?? throw new NotFoundException("SSO Provider not found");

            user.SsoProviderId = ssoProvider.Id;
        }
        user.SsoSubject = ssoUserId;
        
        if (setEmailConfirmed)
            user.EmailConfirmed = true;

        await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
    }

    public async Task TurnOfSsoForUser(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found");

        user.SsoProviderId = null;
        user.SsoSubject = string.Empty;
        user.SsoProvider = null;

        await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
    }

    public async Task<Guid> CreateSingleUseGuid(
        AuditUserDetails auditUserDetails,
        GeneralIdRef generalIdRef,
        CancellationToken cancellationToken
    )
    {
        var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found");

        var singleUseGuid = new SingleUseGuid
        {
            UserId = user.Id,
            User = user,
            Guid = Guid.NewGuid(),
            Expires = _clock.GetNow.AddHours(GetEmailTimeoutHours())
        };

        await _userManagerRepository.SaveSingleUseGuidForUser(singleUseGuid, cancellationToken);

        return singleUseGuid.Guid;
    }

    public async Task<User?> GetUserWithSingleUseGuid(Guid guid, CancellationToken cancellationToken)
    {
        var user = await _userManagerRepository.GetUserBySingleUseGuid(guid, cancellationToken);
        
        await _userManagerRepository.DeleteSingleUseGuid(guid, cancellationToken);
        return user;
    }

    public async Task SetAuthentication(
        AuditUserDetails auditUserDetails,
        UserAuthenticationDetails userAuthenticationDetails,
        bool setEmailConfirmed,
        CancellationToken cancellationToken
    )
    {
        await _userManagerRepository.TransactionAsync(async () =>
        {
            var user = await _userManagerRepository.GetUserByIdAsync(userAuthenticationDetails.Id, cancellationToken) 
                       ?? throw new NotFoundException("User not found"); 

            var userProfile = new UpdatedUserProfile
            {
                Password = userAuthenticationDetails.Password,
                SecurityCode = userAuthenticationDetails.SecurityCode,
                UsingTwoFactorAuthentication = userAuthenticationDetails.UsingTwoFactorAuthentication
            };

            await SetInternalAuthenticationDetails(user, userProfile, false, cancellationToken);

            if (setEmailConfirmed)
                user.EmailConfirmed = true;

            await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
        });
    }
}