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

namespace UserManager.UnitTests.UserManager;

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

    [Test]
    public async Task UpdateProfile_UpdatesMade_SavesChanges()
    {
        //Arrange
        const string existingEmail = "testuser@sun-strategy.com";
        var existingUser = new User
        {
            Id = 12,
            Email = existingEmail
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            FirstName = "James",
            MiddleNames = "Tiberius",
            LastName = "Kirk",
            UsingTwoFactorAuthentication = true,
            Email = "james.t.kirk@enterprise.uss.fed"
        };

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.Email, Is.Not.EqualTo(existingEmail));
        Assert.That(alteredProfile?.Email, Is.EqualTo(updatedProfile.Email));
    }

    [Test]
    public async Task UpdateProfile_NewPasswordSupplied_HashesNewPassword()
    {
        //Arrange
        const string existingEmail = "testuser@sun-strategy.com";
        var existingUser = new User
        {
            Id = 12,
            Email = existingEmail,
            EmailConfirmed = true
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = existingEmail,
            Password = "This is my new password",
        };

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

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.Password, Is.EqualTo(hashedPassword));
        MailServiceMock.Verify( x => x.RequestEMailAsync( It.IsAny<MailRequest>(), It.IsAny<CancellationToken>() ), Times.Once);
    }


    [Test]
    public async Task UpdateProfile_NewEmailExists_ThrowsError()
    {
        //Arrange
        const string existingEmail = "testuser@sun-strategy.com";
        var existingUser = new User
        {
            Id = 12,
            Email = existingEmail,
            Domain = new Domain
            {
                Name = "TestDomain"
            }
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        const string existing2Email = "testuser2@sun-strategy.com";
        var existingUser2 = new User
        {
            Id = 13,
            Email = existing2Email
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser2, default);

        var testProfile = await UserManager.GetProfile(existingUser.Email);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = existing2Email
        };

        //Act & Assert
        Assert.ThrowsAsync<ExistsException>(async () => { await UserManager.UpdateProfile(AuditUserDetails, existingEmail, updatedProfile); });
    }

    [Test]
    public async Task UpdateProfile_DisablingTFA_DisablesTFAAndChangesKey()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 12,
            Email = "testuser@sun-strategy.com",
            UsingTwoFactorAuthentication = true,
            TwoFactorAuthenticationKey = "FirstKey"
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var newTwoFactorAuthenticationKey = "SecondKey";
        RandomNumberGeneratorMock.Setup(x => x.GetRandomString(It.IsAny<int>()))
            .Returns(newTwoFactorAuthenticationKey);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = existingUser.Email,
            UsingTwoFactorAuthentication = false
        };

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.UsingTwoFactorAuthentication, Is.False);
        Assert.That(alteredProfile?.TwoFactorAuthenticationKey, Is.EqualTo(newTwoFactorAuthenticationKey));
    }

    [Test]
    public async Task UpdateProfile_EnablingTFANoSecurityCode_DoesNotEnableTFA()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 12,
            Email = "testuser@sun-strategy.com",
            UsingTwoFactorAuthentication = false,
            TwoFactorAuthenticationKey = "FirstKey"
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);


        var updatedProfile = new UpdatedUserProfile
        {
            Email = existingUser.Email,
            UsingTwoFactorAuthentication = true
        };

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.UsingTwoFactorAuthentication, Is.False);

        TwoFactorAuthenticatorMock.Verify(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never);
    }

    [Test]
    public async Task UpdateProfile_EnablingTFAIncorrectSecurityCode_DoesNotEnableTFA()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 12,
            Email = "testuser@sun-strategy.com",
            UsingTwoFactorAuthentication = false,
            TwoFactorAuthenticationKey = "FirstKey"
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = existingUser.Email,
            UsingTwoFactorAuthentication = true,
            SecurityCode = "12345"
        };

        TwoFactorAuthenticatorMock.Setup(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), updatedProfile.SecurityCode, It.IsAny<bool>())).Returns(false);

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.UsingTwoFactorAuthentication, Is.False);

        TwoFactorAuthenticatorMock.Verify(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Once);
    }

    [Test]
    public async Task UpdateProfile_EnablingTFACorrectSecurityCode_EnablesTFA()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 12,
            Email = "testuser@sun-strategy.com",
            UsingTwoFactorAuthentication = false,
            TwoFactorAuthenticationKey = "FirstKey"
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = existingUser.Email,
            UsingTwoFactorAuthentication = true,
            SecurityCode = "12345"
        };

        TwoFactorAuthenticatorMock.Setup(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), updatedProfile.SecurityCode, It.IsAny<bool>())).Returns(true);

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.UsingTwoFactorAuthentication, Is.True);

        TwoFactorAuthenticatorMock.Verify(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Once);
    }

    [Test]
    public async Task UpdateProfile_WhenEmailIsEmpty_DoesNotAttemptToChangeEmailAddress()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 13,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = true
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = string.Empty
        };

        TwoFactorAuthenticatorMock.Setup(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), updatedProfile.SecurityCode, It.IsAny<bool>())).Returns(true);

        //Act
        await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile);

        //Assert
        var alteredProfile = UserManagerRepository.Users.SingleOrDefault(x => x.Id == existingUser.Id);
        Assert.That(alteredProfile?.EmailConfirmed, Is.True);
    }

    [Test]
    public async Task UpdateProfile_WhenEmailIsNotValid_ThrowsExpectedException()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 13,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = true
        };

        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var updatedProfile = new UpdatedUserProfile
        {
            Email = "invalid at invalid dot invalid"
        };

        TwoFactorAuthenticatorMock.Setup(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), updatedProfile.SecurityCode, It.IsAny<bool>())).Returns(true);

        //Act & Assert
        Assert.ThrowsAsync<ArgumentException>(async () =>
            await UserManager.UpdateProfile(AuditUserDetails, existingUser.Email, updatedProfile));
    }
}