package middlewares

import (
	"regexp"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/authelia/authelia/v4/internal/configuration/schema"
)

func TestNewPasswordPolicyProvider(t *testing.T) {
	testCases := []struct {
		desc     string
		have     schema.PasswordPolicyConfiguration
		expected PasswordPolicyProvider
	}{
		{
			desc:     "ShouldReturnUnconfiguredProvider",
			have:     schema.PasswordPolicyConfiguration{},
			expected: &StandardPasswordPolicyProvider{},
		},
		{
			desc:     "ShouldReturnProviderWhenZxcvbn",
			have:     schema.PasswordPolicyConfiguration{ZXCVBN: schema.PasswordPolicyZXCVBNParams{Enabled: true, MinScore: 10}},
			expected: &ZXCVBNPasswordPolicyProvider{minScore: 10},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWithMin",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8}},
			expected: &StandardPasswordPolicyProvider{min: 8},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWitHMinMax",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, MaxLength: 100}},
			expected: &StandardPasswordPolicyProvider{min: 8, max: 100},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWithMinLowercase",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true}},
			expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`)}},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWithMinLowercaseUppercase",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true}},
			expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`)}},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWithMinLowercaseUppercaseNumber",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true, RequireNumber: true}},
			expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[0-9]+`)}},
		},
		{
			desc:     "ShouldReturnConfiguredProviderWithMinLowercaseUppercaseSpecial",
			have:     schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true, RequireSpecial: true}},
			expected: &StandardPasswordPolicyProvider{min: 8, patterns: []regexp.Regexp{*regexp.MustCompile(`[a-z]+`), *regexp.MustCompile(`[A-Z]+`), *regexp.MustCompile(`[^a-zA-Z0-9]+`)}},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.desc, func(t *testing.T) {
			actual := NewPasswordPolicyProvider(tc.have)
			assert.Equal(t, tc.expected, actual)
		})
	}
}

func TestPasswordPolicyProvider_Validate(t *testing.T) {
	testCases := []struct {
		desc     string
		config   schema.PasswordPolicyConfiguration
		have     []string
		expected []error
	}{
		{
			desc:     "ShouldValidateAllPasswords",
			config:   schema.PasswordPolicyConfiguration{},
			have:     []string{"a", "1", "a really str0ng pass12nm3kjl12word@@#4"},
			expected: []error{nil, nil, nil},
		},
		{
			desc:     "ShouldValidatePasswordMinLength",
			config:   schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8}},
			have:     []string{"a", "b123", "1111111", "aaaaaaaa", "1o23nm1kio2n3k12jn"},
			expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil, nil},
		},
		{
			desc:   "ShouldValidatePasswordMaxLength",
			config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MaxLength: 30}},
			have: []string{
				"a1234567894654wkjnkjasnskjandkjansdkjnas",
				"012345678901234567890123456789a",
				"0123456789012345678901234567890123456789",
				"012345678901234567890123456789",
				"1o23nm1kio2n3k12jn",
			},
			expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil, nil},
		},
		{
			desc:     "ShouldValidatePasswordAdvancedLowerUpperMin8",
			config:   schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, RequireLowercase: true, RequireUppercase: true}},
			have:     []string{"a", "b123", "1111111", "aaaaaaaa", "1o23nm1kio2n3k12jn", "ANJKJQ@#NEK!@#NJK!@#", "qjik2nkjAkjlmn123"},
			expected: []error{errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, errPasswordPolicyNoMet, nil},
		},
		{
			desc:   "ShouldValidatePasswordAdvancedAllMax100Min8",
			config: schema.PasswordPolicyConfiguration{Standard: schema.PasswordPolicyStandardParams{Enabled: true, MinLength: 8, MaxLength: 100, RequireLowercase: true, RequireUppercase: true, RequireNumber: true, RequireSpecial: true}},
			have: []string{
				"a",
				"b123",
				"1111111",
				"aaaaaaaa",
				"1o23nm1kio2n3k12jn",
				"ANJKJQ@#NEK!@#NJK!@#",
				"qjik2nkjAkjlmn123",
				"qjik2n@jAkjlmn123",
				"qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123qjik2n@jAkjlmn123",
			},
			expected: []error{
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				errPasswordPolicyNoMet,
				nil,
				errPasswordPolicyNoMet,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.desc, func(t *testing.T) {
			require.Equal(t, len(tc.have), len(tc.expected))
			for i := 0; i < len(tc.have); i++ {
				provider := NewPasswordPolicyProvider(tc.config)
				t.Run(tc.have[i], func(t *testing.T) {
					assert.Equal(t, tc.expected[i], provider.Check(tc.have[i]))
				})
			}
		})
	}
}