using e_suite.API.Common.models;
using e_suite.Database.Core.Models;
using e_suite.Database.Core.Tables.UserManager;
using e_suite.Nuget.PasswordHasher;
using eSuite.Core.MailService;
using Microsoft.AspNetCore.Identity;
using Moq;
using NUnit.Framework;
using UserManager.UnitTests.Helpers;

namespace UserManager.UnitTests.UserManager;

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

    [Test]
    public async Task Login_UserNotFound_ReturnsFailed()
    {
        //Arrange
        var login = new Login
        {
            Email = "fred@bloggs.com" //User does not exist in fake database.
        };

        //Act
        var result = await UserManager.Login(login);

        //Assert
        Assert.That(result.Result, Is.EqualTo(LoginResult.Failed));
    }

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

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Failed);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        CustomPasswordHasherMock.Verify(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password), Times.Once);
        Assert.That(result.Result, Is.EqualTo(LoginResult.Failed));
    }

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

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        CustomPasswordHasherMock.Verify(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password), Times.Once);
        CustomPasswordHasherMock.Verify(x => x.HashPassword(It.IsAny<IPassword>(), login.Password), Times.Never);
        Assert.That(result.Result, Is.EqualTo(LoginResult.Success));
        Assert.That(result.Token, Is.Not.Empty);
    }

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

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing"
        };

        var newHashedPassword = "123456";

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.SuccessRehashNeeded);
        CustomPasswordHasherMock.Setup(x => x.HashPassword(It.IsAny<IPassword>(), login.Password)).Returns(newHashedPassword);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        CustomPasswordHasherMock.Verify(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password), Times.Once);
        CustomPasswordHasherMock.Verify(x => x.HashPassword(It.IsAny<IPassword>(), login.Password), Times.Once);
        Assert.That(result.Result, Is.EqualTo(LoginResult.Success));
        Assert.That(result.Token, Is.Not.Empty);
    }

    [Test]
    public async Task Login_EmailNotConfirmed_ReturnsError()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 16,
            Email = "testuser2@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = false
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        Assert.That(result.Result, Is.EqualTo(LoginResult.EmailNotConfirmed));
    }

    [Test]
    public async Task Login_RequestTfaDisable_ReturnsTwoFactorAuthenticationRemovalRequested()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 16,
            Email = "testuser3@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = true,
            UsingTwoFactorAuthentication = true,
            FirstName = "Test3",
            LastName = "User"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing",
            RequestTfaRemoval = true
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        MailRequest actualMailRequest = null!;

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

        //Act
        var result = await UserManager.Login(login);

        //Assert that the new row was added to the database correctly.
        var emailUserAction = UserManagerRepository.EmailUserActions.SingleOrDefault(x => x.User.Email == login.Email);

        Assert.That(emailUserAction, Is.Not.Null);
        Assert.That(emailUserAction?.EmailActionType, Is.EqualTo(EmailUserActionType.DisableAuthenticator));
        Assert.That(emailUserAction?.User.Email, Is.EqualTo(login.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.DisableAuthenticator));
        Assert.That(actualMailRequest.To.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.To[0].DisplayName, Is.EqualTo(existingUser.DisplayName));
        Assert.That(actualMailRequest.To[0].Email, Is.EqualTo(login.Email));
        Assert.That(actualMailRequest.Parameters.Count, Is.EqualTo(1));
        Assert.That(actualMailRequest.Parameters["url"], Is.Not.Empty);

        Assert.That(result.Result, Is.EqualTo(LoginResult.TwoFactorAuthenticationRemovalRequested));
    }

    [Test]
    public async Task Login_TfaEnabledButCodeNotProvided_ReturnsTwoFactorAuthenticationCodeRequired()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 16,
            Email = "testuser3@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = true,
            UsingTwoFactorAuthentication = true,
            FirstName = "Test3",
            LastName = "User"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing",
            RequestTfaRemoval = false
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        Assert.That(result.Result, Is.EqualTo(LoginResult.TwoFactorAuthenticationCodeRequired));
    }

    [Test]
    public async Task Login_TfaEnabledButCodeIncorrect_ReturnsTwoFactorAuthenticationCodeIncorrect()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 16,
            Email = "testuser3@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = true,
            UsingTwoFactorAuthentication = true,
            FirstName = "Test3",
            LastName = "User"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing",
            RequestTfaRemoval = false,
            SecurityCode = "123456"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        TwoFactorAuthenticatorMock.Verify(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), login.SecurityCode, false), Times.Once);

        Assert.That(result.Result, Is.EqualTo(LoginResult.TwoFactorAuthenticationCodeIncorrect));
    }

    [Test]
    public async Task Login_TfaEnabledAndCodeCorrect_ReturnsSuccess()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 16,
            Email = "testuser3@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = true,
            UsingTwoFactorAuthentication = true,
            FirstName = "Test3",
            LastName = "User"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = "testuser3@sun-strategy.com", //User exists in fake database. But email is not confirmed.
            Password = "testing",
            RequestTfaRemoval = false,
            SecurityCode = "123456"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);
        TwoFactorAuthenticatorMock.Setup(x => x.ValidateTwoFactorPIN(It.IsAny<string>(), login.SecurityCode, It.IsAny<bool>())).Returns(true);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        Assert.That(result.Result, Is.EqualTo(LoginResult.Success));
        Assert.That(result.Token, Is.Not.Empty);
    }

    [Test]
    public async Task Login_AccountIsNotActive_ReturnsFailed()
    {
        //Arrange
        var existingUser = new User
        {
            Id = 15,
            Email = "testuser@sun-strategy.com",
            Password = "testing",
            EmailConfirmed = true,
            Active = false
        };
        await UserManagerRepository.AddUser(AuditUserDetails, existingUser, default);

        var login = new Login
        {
            Email = existingUser.Email,
            Password = "testing"
        };

        CustomPasswordHasherMock.Setup(x => x.VerifyHashedPassword(It.IsAny<IPassword>(), It.IsAny<string>(), login.Password)).Returns(PasswordVerificationResult.Success);

        //Act
        var result = await UserManager.Login(login);

        //Assert
        Assert.That(result.Result, Is.EqualTo(LoginResult.Failed));
        Assert.That(result.Token, Is.Empty);
    }
}