using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.UserManager;
using eSuite.Core.MailService;
using eSuite.Core.Miscellaneous;
using Moq;
using NUnit.Framework;
using UserManager.UnitTests.Helpers;

namespace UserManager.UnitTests.UserManager;

[TestFixture]
public class CreateUserUnitTests : UserManagerTestBase<object>
{
    [SetUp]
    public override async Task Setup()
    {
        await base.Setup();
    }

    /// <summary>
    /// Cannot create the same user twice.
    /// </summary>
    [Test]
    public async Task CreateUser_UserExists_ThrowException()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 6,
            Email = "testuser@sun-strategy.com",
            FirstName = "Test",
            LastName = "User"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var userRegistration = new UserRegistration
        {
            Email = "testuser@sun-strategy.com",
            FirstName = "Test",
            LastName = "User"
        };

        //Act & Assert
        Assert.ThrowsAsync<ExistsException>(() => UserManager.CreateUser(AuditUserDetails, userRegistration));
    }

    /// <summary>
    /// Can create a user that does not already exist
    /// </summary>
    /// <returns></returns>
    [Test]
    public async Task CreateUser_DoesNotExist_CreatesUser()
    {
        //Arrange
        var userRegistration = new UserRegistration
        {
            Email = "testuser10@sun-strategy.com",
            FirstName = "Test1",
            LastName = "User"
        };

        var hashedPassword = "owekjhrtlkerjthbwerlkjrthbw3";
        CustomPasswordHasherMock.Setup(x => x.HashPassword(It.IsAny<User>(), It.IsAny<string>()))
            .Returns(hashedPassword);

        MailRequest actualMailRequest = null!;

        MailServiceMock.Setup(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()))
            .Callback<MailRequest, CancellationToken>((mailRequest, cancellationToken) => { actualMailRequest = mailRequest; });

        //Act
        await UserManager.CreateUser(AuditUserDetails, userRegistration);

        //Assert
        RandomNumberGeneratorMock.Verify(x => x.GetRandomString(It.IsAny<int>()), Times.Exactly(2)); //one for the tfa, and one for the default password.

        //Assert that the user was added to the database
        var databaseRowToCheck = UserManagerRepository.Users.SingleOrDefault(x => x.Email == userRegistration.Email);

        Assert.That(databaseRowToCheck, Is.Not.Null); //Check row added to database
        Assert.That(databaseRowToCheck?.Password, Is.EqualTo(hashedPassword)); //User has hashed password
        Assert.That(databaseRowToCheck?.UsingTwoFactorAuthentication, Is.False); //UsingTwoFactorAuthentication disabled

        //Assert that the e-mail request was logged properly
        var emailUserAction = UserManagerRepository.EmailUserActions.Single(x => x.User.Email == userRegistration.Email);

        Assert.That(emailUserAction, Is.Not.Null);
        Assert.That(emailUserAction?.EmailActionType, Is.EqualTo(EmailUserActionType.ConfirmEmailAddress));
        Assert.That(emailUserAction?.User.Email, Is.EqualTo(userRegistration.Email));
        Assert.That(emailUserAction?.Token, Is.Not.Empty);

        //Assert that the e-mail request was sent.
        MailServiceMock.Verify(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()), Times.Once);
        Assert.That(actualMailRequest, Is.Not.Null);
        Assert.That(actualMailRequest.EmailType, Is.EqualTo(MailType.ConfirmEmailAddress));
        Assert.That(actualMailRequest.To.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.To[0].DisplayName, Is.EqualTo(userRegistration.FirstName + " " + userRegistration.LastName));
        Assert.That(actualMailRequest.To[0].Email, Is.EqualTo(userRegistration.Email.Trim()));
        Assert.That(actualMailRequest.Parameters.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.Parameters["url"], Is.Not.Empty);
    }


    /// <summary>
    /// Can create a user that does not already exist
    /// </summary>
    /// <returns></returns>
    [Test]
    public void CreateUser_NonExistingDomainIdSupplied_ThrowsNotFoundException()
    {
        //Arrange
        var userRegistration = new UserRegistration
        {
            Email = "testuser10@sun-strategy.com",
            FirstName = "Test1",
            LastName = "User",
            DomainId = new GeneralIdRef { Guid = new Guid("3b3be044-f55e-4608-ab20-2b5fd4be450f") }
        };

        var hashedPassword = "owekjhrtlkerjthbwerlkjrthbw3";
        CustomPasswordHasherMock.Setup(x => x.HashPassword(It.IsAny<User>(), It.IsAny<string>()))
            .Returns(hashedPassword);

        MailRequest actualMailRequest = null!;

        MailServiceMock.Setup(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()))
            .Callback<MailRequest, CancellationToken>((mailRequest, cancellationToken) => { actualMailRequest = mailRequest; });

        //Assert
        Assert.ThrowsAsync<NotFoundException>(async () =>
        {
            //Act
            await UserManager.CreateUser(AuditUserDetails, userRegistration);
        });
    }

    /// <summary>
    /// Can create a user that does not already exist
    /// </summary>
    /// <returns></returns>
    [Test]
    public async Task CreateUser_GivingADomainId_CreatesUser()
    {
        //Arrange
        var userRegistration = new UserRegistration
        {
            Email = "testuser10@sun-strategy.com",
            FirstName = "Test1",
            LastName = "User",
            DomainId = new GeneralIdRef { Guid = new Guid("d3d36362-dd2a-490a-b8ef-71ee59934b8d") }
        };

        var hashedPassword = "owekjhrtlkerjthbwerlkjrthbw3";
        CustomPasswordHasherMock.Setup(x => x.HashPassword(It.IsAny<User>(), It.IsAny<string>()))
            .Returns(hashedPassword);

        MailRequest actualMailRequest = null!;

        MailServiceMock.Setup(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()))
            .Callback<MailRequest, CancellationToken>((mailRequest, cancellationToken) => { actualMailRequest = mailRequest; });

        //Act
        await UserManager.CreateUser(AuditUserDetails, userRegistration);

        //Assert
        RandomNumberGeneratorMock.Verify(x => x.GetRandomString(It.IsAny<int>()), Times.Exactly(2)); //one for the tfa, and one for the default password.

        //Assert that the user was added to the database
        var databaseRowToCheck = UserManagerRepository.Users.SingleOrDefault(x => x.Email == userRegistration.Email);

        Assert.That(databaseRowToCheck, Is.Not.Null); //Check row added to database
        Assert.That(databaseRowToCheck?.Password, Is.EqualTo(hashedPassword)); //User has hashed password
        Assert.That(databaseRowToCheck?.UsingTwoFactorAuthentication, Is.False); //UsingTwoFactorAuthentication disabled

        //Assert that the e-mail request was logged properly
        var emailUserAction = UserManagerRepository.EmailUserActions.Single(x => x.User.Email == userRegistration.Email);

        Assert.That(emailUserAction, Is.Not.Null);
        Assert.That(emailUserAction?.EmailActionType, Is.EqualTo(EmailUserActionType.ConfirmEmailAddress));
        Assert.That(emailUserAction?.User.Email, Is.EqualTo(userRegistration.Email));
        Assert.That(emailUserAction?.Token, Is.Not.Empty);

        //Assert that the e-mail request was sent.
        MailServiceMock.Verify(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()), Times.Once);
        Assert.That(actualMailRequest, Is.Not.Null);
        Assert.That(actualMailRequest.EmailType, Is.EqualTo(MailType.ConfirmEmailAddress));
        Assert.That(actualMailRequest.To.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.To[0].DisplayName, Is.EqualTo(userRegistration.FirstName + " " + userRegistration.LastName));
        Assert.That(actualMailRequest.To[0].Email, Is.EqualTo(userRegistration.Email.Trim()));
        Assert.That(actualMailRequest.Parameters.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.Parameters["url"], Is.Not.Empty);
    }

    /// <summary>
    /// They need to have a valid e-mail to be created.
    /// </summary>
    [TestCase("testuser1@sun-s@trategy.com")]
    [TestCase("test@user1@sun-strategy.com ")]
    [TestCase(" sun-strategy.com")]
    public Task CreateUser_InvalidEmail_Throws(string email)
    {
        //Arrange
        var userRegistration = new UserRegistration
        {
            Email = email
        };

        //Act & Assert
        Assert.ThrowsAsync<ArgumentException>(async () =>
        {
            await UserManager.CreateUser(AuditUserDetails, userRegistration);
        });
        return Task.CompletedTask;
    }



    /// <summary>
    /// Can reactivate a deactivated user
    /// </summary>
    /// <returns></returns>
    [Test]
    public async Task CreateUser_ExistsButDeactivated_UserIsReactivated()
    {
        //Arrange

        var existingUser = new User
        {
            Id = 6,
            Email = "testuser@sun-strategy.com",
            FirstName = "Test",
            LastName = "User",
            Active = false
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var userRegistration = new UserRegistration
        {
            Email = "testuser0@sun-strategy.com",
            FirstName = "Test1",
            LastName = "User1"
        };

        var hashedPassword = "owekjhrtlkerjthbwerlkjrthbw3";
        CustomPasswordHasherMock.Setup(x => x.HashPassword(It.IsAny<User>(), It.IsAny<string>()))
            .Returns(hashedPassword);

        MailRequest actualMailRequest = null!;

        MailServiceMock.Setup(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()))
            .Callback<MailRequest, CancellationToken>((mailRequest, cancellationToken) => { actualMailRequest = mailRequest; });

        //Act
        await UserManager.CreateUser(AuditUserDetails, userRegistration);

        //Assert
        RandomNumberGeneratorMock.Verify(x => x.GetRandomString(It.IsAny<int>()), Times.Exactly(2)); //one for the tfa, and one for the default password.

        //Assert that the user was added to the database
        var databaseRowToCheck = UserManagerRepository.Users.SingleOrDefault(x => x.Email == userRegistration.Email);

        Assert.That(databaseRowToCheck, Is.Not.Null); //Check row added to database
        Assert.That(databaseRowToCheck?.Active, Is.True); //UsingTwoFactorAuthentication disabled

        Assert.That(databaseRowToCheck?.FirstName, Is.EqualTo("Test1")); //UsingTwoFactorAuthentication disabled
        Assert.That(databaseRowToCheck?.LastName, Is.EqualTo("User1")); //UsingTwoFactorAuthentication disabled
    }
}