package utils

import (
	"baron-sso-backend/internal/domain"
	"testing"

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

func TestValidatePasswordWithPolicy(t *testing.T) {
	policy := &domain.PasswordPolicy{
		MinLength:         8,
		Lowercase:         true,
		Uppercase:         true,
		Number:            true,
		NonAlphanumeric:   true,
		MinCharacterTypes: 3,
	}

	t.Run("Valid Password", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "Pass1234!")
		assert.NoError(t, err)
	})

	t.Run("Nil Policy", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(nil, "")
		assert.NoError(t, err)
	})

	t.Run("Too Short", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "P123!")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "최소 8자")
	})

	t.Run("Missing Lowercase", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "PASS1234!")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "소문자")
	})

	t.Run("Missing Uppercase", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "pass1234!")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "대문자")
	})

	t.Run("Missing Number", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "Password!")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "숫자")
	})

	t.Run("Missing Symbol", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(policy, "Pass1234")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "특수문자")
	})

	t.Run("Missing Minimum Character Types", func(t *testing.T) {
		err := ValidatePasswordWithPolicy(&domain.PasswordPolicy{MinLength: 4, MinCharacterTypes: 4}, "abcd")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "4가지")
	})
}

func TestGeneratePasswordWithPolicy(t *testing.T) {
	policy := &domain.PasswordPolicy{
		MinLength:       16,
		Lowercase:       true,
		Uppercase:       true,
		Number:          true,
		NonAlphanumeric: true,
	}

	t.Run("Generate and Validate", func(t *testing.T) {
		password, err := GeneratePasswordWithPolicy(policy)
		assert.NoError(t, err)
		assert.Len(t, password, 16)

		err = ValidatePasswordWithPolicy(policy, password)
		assert.NoError(t, err, "Generated password '%s' does not satisfy policy", password)
	})

	t.Run("Nil Policy Uses Default Length", func(t *testing.T) {
		password, err := GeneratePasswordWithPolicy(nil)
		assert.NoError(t, err)
		assert.Len(t, password, 12)
	})

	t.Run("Minimum Character Types Adds Optional Categories", func(t *testing.T) {
		policy := &domain.PasswordPolicy{
			MinLength:         4,
			Lowercase:         true,
			MinCharacterTypes: 4,
		}

		password, err := GeneratePasswordWithPolicy(policy)
		assert.NoError(t, err)
		assert.Len(t, password, 4)
		assert.NoError(t, ValidatePasswordWithPolicy(policy, password))
	})

	t.Run("Required Categories Raise Short Minimum Length", func(t *testing.T) {
		policy := &domain.PasswordPolicy{
			MinLength:       1,
			Lowercase:       true,
			Uppercase:       true,
			Number:          true,
			NonAlphanumeric: true,
		}

		password, err := GeneratePasswordWithPolicy(policy)
		assert.NoError(t, err)
		assert.Len(t, password, 4)
		assert.NoError(t, ValidatePasswordWithPolicy(policy, password))
	})
}

func TestPasswordPolicyRandomHelpersRejectInvalidInput(t *testing.T) {
	_, err := randomIndex(0)
	assert.Error(t, err)

	_, err = randomChar("")
	assert.Error(t, err)

	assert.NoError(t, shuffleRunes([]rune("a")))
}
