// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Identity.Test;

public class PasswordValidatorTest
{
    [Flags]
    public enum Errors
    {
        None = 0,
        Length = 2,
        Alpha = 4,
        Upper = 8,
        Lower = 16,
        Digit = 32,
    }

    [Fact]
    public async Task ValidateThrowsWithNullTest()
    {
        // Setup
        var validator = new PasswordValidator<PocoUser>();

        // Act
        // Assert
        await Assert.ThrowsAsync<ArgumentNullException>("manager", () => validator.ValidateAsync(null, null, "foo"));
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("abc")]
    [InlineData("abcde")]
    public async Task FailsIfPasswordIsNullOrTooShortTests(string input)
    {
        const string error = "Passwords must be at least 6 characters.";
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = false;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), error);
    }

    [Theory]
    [InlineData("abcdef")]
    [InlineData("aaaaaaaaaaa")]
    public async Task SuccessIfLongEnoughTests(string input)
    {
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = false;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
    }

    [Theory]
    [InlineData("a")]
    [InlineData("aaaaaaaaaaa")]
    public async Task FailsWithoutRequiredNonAlphanumericTests(string input)
    {
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = true;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        manager.Options.Password.RequiredLength = 0;
        IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input),
            "Passwords must have at least one non alphanumeric character.");
    }

    [Theory]
    [InlineData("@")]
    [InlineData("abcd@e!ld!kajfd")]
    [InlineData("!!!!!!")]
    public async Task SucceedsWithRequiredNonAlphanumericTests(string input)
    {
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = true;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        manager.Options.Password.RequiredLength = 0;
        IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
    }

    [Theory]
    [InlineData("a", 2)]
    [InlineData("aaaaaaaaaaa", 2)]
    [InlineData("abcdabcdabcdabcdabcdabcdabcd", 5)]
    public async Task FailsWithoutRequiredUniqueCharsTests(string input, int uniqueChars)
    {
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = false;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        manager.Options.Password.RequiredLength = 0;
        manager.Options.Password.RequiredUniqueChars = uniqueChars;
        IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input),
            $"Passwords must use at least {uniqueChars} different characters.");
    }

    [Theory]
    [InlineData("12345", 5)]
    [InlineData("aAbBc", 5)]
    [InlineData("aAbBcaAbBcaAbBc", 5)]
    [InlineData("!@#$%", 5)]
    [InlineData("a", 1)]
    [InlineData("this is a long password with many chars", 10)]
    public async Task SucceedsWithRequiredUniqueCharsTests(string input, int uniqueChars)
    {
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        manager.Options.Password.RequireUppercase = false;
        manager.Options.Password.RequireNonAlphanumeric = false;
        manager.Options.Password.RequireLowercase = false;
        manager.Options.Password.RequireDigit = false;
        manager.Options.Password.RequiredLength = 0;
        manager.Options.Password.RequiredUniqueChars = uniqueChars;
        IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
    }

    [Theory]
    [InlineData("abcde", Errors.Length | Errors.Alpha | Errors.Upper | Errors.Digit)]
    [InlineData("a@B@cd", Errors.Digit)]
    [InlineData("___", Errors.Length | Errors.Digit | Errors.Lower | Errors.Upper)]
    [InlineData("a_b9de", Errors.Upper)]
    [InlineData("abcd@e!ld!kaj9Fd", Errors.None)]
    [InlineData("aB1@df", Errors.None)]
    public async Task UberMixedRequiredTests(string input, Errors errorMask)
    {
        const string alphaError = "Passwords must have at least one non alphanumeric character.";
        const string upperError = "Passwords must have at least one uppercase ('A'-'Z').";
        const string lowerError = "Passwords must have at least one lowercase ('a'-'z').";
        const string digitError = "Passwords must have at least one digit ('0'-'9').";
        const string lengthError = "Passwords must be at least 6 characters.";
        var manager = MockHelpers.TestUserManager<PocoUser>();
        var valid = new PasswordValidator<PocoUser>();
        var errors = new List<string>();
        if ((errorMask & Errors.Length) != Errors.None)
        {
            errors.Add(lengthError);
        }
        if ((errorMask & Errors.Alpha) != Errors.None)
        {
            errors.Add(alphaError);
        }
        if ((errorMask & Errors.Digit) != Errors.None)
        {
            errors.Add(digitError);
        }
        if ((errorMask & Errors.Lower) != Errors.None)
        {
            errors.Add(lowerError);
        }
        if ((errorMask & Errors.Upper) != Errors.None)
        {
            errors.Add(upperError);
        }
        var result = await valid.ValidateAsync(manager, null, input);
        if (errors.Count == 0)
        {
            IdentityResultAssert.IsSuccess(result);
        }
        else
        {
            IdentityResultAssert.IsFailure(result);
            foreach (var error in errors)
            {
                Assert.Contains(result.Errors, e => e.Description == error);
            }
        }
    }
}
