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 Moq;
using NUnit.Framework;
using UserManager.UnitTests.Helpers;

namespace UserManager.UnitTests.UserManager;

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

    [Test]
    public Task CompleteEmailAction_TokenNotFound_ThrowsTokenInvalidException()
    {
        //Arrange
        var token = new EmailActionToken
        {
            Email = "evil.hacker@sun-strategy.com",
            Token = new Guid("{84CC5C02-0A14-465B-AB14-6D5125D50E7B}")
        };

        //Act & Assert
        Assert.ThrowsAsync<TokenInvalidException>(async () =>
        {
            await UserManager.CompleteEmailAction(token);
        });
        return Task.CompletedTask;
    }

    [Test]
    public async Task CompleteEmailAction_TokenOutOfDate_ThrowsTokenInvalidException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 1,
            Email = "testuser@sun-strategy.com"
        };
        await UserManagerRepository.AddUser(AuditUserDetails, actualUser, default);

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{F5035CA7-E2B1-46B3-8E1A-B121107E66AD}")
        };

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.ConfirmEmailAddress,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 06, 11, 43, 0, TimeSpan.Zero);

        //Act & Assert
        Assert.ThrowsAsync<TokenInvalidException>(async () =>
        {
            await UserManager.CompleteEmailAction(token);
        });
    }

    [Test]
    public async Task CompleteEmailAction_UserEmailDoesNotMatchTheToken_ThrowsInvalidEmailException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 2,
            Email = "testuser@sun-strategy.com"
        };

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

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.ConfirmEmailAddress,
            Token = new Guid("{632B559B-4641-4973-A1C0-38D198816201}"),
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        var token = new EmailActionToken
        {
            Email = "evil.hacker@sun-strategy.com",
            Token = emailUserAction.Token,
            Password = "P@ssword12345"
        };

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

        //Act & Assert
        Assert.ThrowsAsync<InvalidEmailException>(async () =>
        {
            await UserManager.CompleteEmailAction(token);
        });
    }

    [Test]
    public async Task CompleteEmailAction_ConfirmEmail_CompletedSuccessfully()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 3,
            Email = "testuser2@sun-strategy.com",
            EmailConfirmed = false,
            Password = "aA1!12345789"
        };

        var oldPassword = actualUser.Password;

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

        var token = new EmailActionToken
        {
            Email = "testuser2@sun-strategy.com",
            Token = new Guid("{9275C68F-A88A-4AD5-963B-64678E3ABFF9}"),
            Password = "Pa55W0rD-3d1ce44c-86ce-4136!b6ad-8b24303e1cfc"
        };


        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.ConfirmEmailAddress,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

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

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

        //Act
        var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
        {
            await UserManager.CompleteEmailAction(token);
        });

        //Assert
        Assert.That(exception!.Message, Is.EqualTo($"EmailActionType {emailUserAction.EmailActionType} not supported, use the UI instead."));
    }

    [Test]
    public async Task CompleteEmailAction_ResetPasswordNewPasswordMissing_ThrowsException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo($"Password must be at least 12 characters"));
    }

    [Test]
    public async Task CompleteEmailAction_ResetPasswordNewPasswordIsTooShort_ThrowsException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = "test"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo("Password must be at least 12 characters"));
    }

    [Test]
    public async Task CompleteEmailAction_ResetPasswordDoesNotContainAValidSymbol_ThrowsException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = "testtesttest"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo($"Password must contain at least one symbol: ~`! @#$%^&*()_+={{[}}|\\:;\"'<,->.?/"));
    }

    [TestCase('~')]
    [TestCase('`')]
    [TestCase('!')]
    [TestCase(' ')]
    [TestCase('@')]
    [TestCase('#')]
    [TestCase('$')]
    [TestCase('%')]
    [TestCase('^')]
    [TestCase('&')]
    [TestCase('(')]
    [TestCase(')')]
    [TestCase('_')]
    [TestCase('+')]
    [TestCase('=')]
    [TestCase('{')]
    [TestCase('[')]
    [TestCase('}')]
    [TestCase('|')]
    [TestCase('\\')]
    [TestCase(':')]
    [TestCase(';')]
    [TestCase('\"')]
    [TestCase('\'')]
    [TestCase('<')]
    [TestCase(',')]
    [TestCase('-')]
    [TestCase('>')]
    [TestCase('.')]
    [TestCase('?')]
    [TestCase('/')]
    [TestCase('"')]
    public async Task CompleteEmailAction_ResetPasswordDoesNotContainAValidNumber_ThrowsException(char symbol)
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = $"t{symbol}sttesttest"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo($"Password must contain at least one number"));
    }

    [TestCase('0')]
    [TestCase('1')]
    [TestCase('2')]
    [TestCase('3')]
    [TestCase('4')]
    [TestCase('5')]
    [TestCase('6')]
    [TestCase('7')]
    [TestCase('8')]
    [TestCase('9')]
    public async Task CompleteEmailAction_ResetPasswordDoesNotContainAUpperCaseCharacter_ThrowsException(char number)
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = $"t*{number}ttesttest"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo($"Password must contain at least one upper case character"));
    }

    [Test]
    public async Task CompleteEmailAction_ResetPasswordDoesNotContainALowerCaseCharacter_ThrowsException()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 4,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I don't know aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = $"T*1TTESTTEST"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

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

        //Act
        var exception = Assert.ThrowsAsync<WeakPasswordException>(async () => await UserManager.CompleteEmailAction(token));

        //Assert
        Assert.That(exception?.Message, Is.EqualTo($"Password must contain at least one lower case character"));
    }

    [Test]
    public async Task CompleteEmailAction_ResetPassword_CompletedSuccessfully()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 5,
            Email = "testuser@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I've forgotten aA1!12345789"
        };

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

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = new Guid("{CAA1C1E2-7876-4ACB-A050-8CF94466CC4E}"),
            Password = "Pa55W0rD-3d1ce44c-86ce-4136-b6ad-8b24303e1cfc"
        };

        var oldPassword = actualUser.Password;

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.PasswordReset,
            Token = token.Token,
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);


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

        //Act
        await UserManager.CompleteEmailAction(token);

        //Assert
        var databaseRow = UserManagerRepository.EmailUserActions.SingleOrDefault(x => x.Token == token.Token);
        Assert.That(databaseRow, Is.Null);

        var user = UserManagerRepository.Users.Single(x => x.Email == token.Email);
        Assert.That(user.Password, Is.Not.EqualTo(oldPassword));
        Assert.That(user.Password, Is.EqualTo(hashedPassword));

        MailServiceMock.Verify(x => x.RequestEMailAsync(It.IsAny<MailRequest>(), It.IsAny<CancellationToken>()), Times.Once);
    }

    [Test]
    public async Task CompleteEmailAction_DisableTwoFactorAuthentivation_CompletedSuccessfully()
    {
        //Arrange
        var actualUser = new User
        {
            Id = 6,
            Email = "testuser3@sun-strategy.com",
            EmailConfirmed = false,
            Password = "I've forgotten aA1!12345789",
            UsingTwoFactorAuthentication = true
        };

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

        var emailUserAction = new EmailUserAction
        {
            EmailActionType = EmailUserActionType.DisableAuthenticator,
            Token = new Guid("{9B44A60C-AB66-4161-BF17-EEAC2162EAC2}"),
            User = actualUser,
            UserId = actualUser.Id,
            Created = new DateTimeOffset(2022, 08, 03, 11, 43, 0, TimeSpan.Zero),
            Expires = new DateTimeOffset(2022, 08, 05, 11, 43, 0, TimeSpan.Zero),
        };

        await UserManagerRepository.AddEmailUserAction(AuditUserDetails, emailUserAction, default);

        var token = new EmailActionToken
        {
            Email = actualUser.Email,
            Token = emailUserAction.Token
        };


        _fakeClock.DateTime = new DateTimeOffset(2022, 08, 04, 11, 43, 0, TimeSpan.Zero);

        //Act
        await UserManager.CompleteEmailAction(token);

        //Assert
        var databaseRow = UserManagerRepository.EmailUserActions.SingleOrDefault(x => x.Token == token.Token);
        Assert.That(databaseRow, Is.Null);

        var user = UserManagerRepository.Users.Single(x => x.Email == token.Email);
        Assert.That(user.UsingTwoFactorAuthentication, Is.False);
    }
}