package accountserviceimp

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tech/sendico/pkg/model"
	apiconfig "github.com/tech/sendico/server/internal/api/config"
	"go.uber.org/zap"
)

// TestValidatePassword tests the password validation logic directly
func TestValidatePassword(t *testing.T) {
	config := &apiconfig.PasswordConfig{
		Check: apiconfig.PasswordChecks{
			MinLength: 8,
			Digit:     true,
			Upper:     true,
			Lower:     true,
			Special:   true,
		},
		TokenLength: 32,
	}

	// Create a minimal service for testing password validation
	logger := zap.NewNop() // Use no-op logger for tests
	service := &service{
		config: config,
		logger: logger,
	}

	t.Run("ValidPassword", func(t *testing.T) {
		err := service.ValidatePassword("TestPassword123!", nil)
		assert.NoError(t, err)
	})

	t.Run("PasswordTooShort", func(t *testing.T) {
		err := service.ValidatePassword("Test1!", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least 8 characters")
	})

	t.Run("PasswordMissingDigit", func(t *testing.T) {
		err := service.ValidatePassword("TestPassword!", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least one digit")
	})

	t.Run("PasswordMissingUppercase", func(t *testing.T) {
		err := service.ValidatePassword("testpassword123!", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least one uppercase")
	})

	t.Run("PasswordMissingLowercase", func(t *testing.T) {
		err := service.ValidatePassword("TESTPASSWORD123!", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least one lowercase")
	})

	t.Run("PasswordMissingSpecialCharacter", func(t *testing.T) {
		err := service.ValidatePassword("TestPassword123", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least one special character")
	})

	t.Run("PasswordSameAsOld", func(t *testing.T) {
		oldPassword := "TestPassword123!"
		err := service.ValidatePassword("TestPassword123!", &oldPassword)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "cannot be the same as the old password")
	})
}

// TestValidateAccount tests the account validation logic directly
func TestValidateAccount(t *testing.T) {
	config := &apiconfig.PasswordConfig{
		Check: apiconfig.PasswordChecks{
			MinLength: 8,
			Digit:     true,
			Upper:     true,
			Lower:     true,
			Special:   true,
		},
		TokenLength: 32,
	}

	logger := zap.NewNop() // Use no-op logger for tests
	service := &service{
		config: config,
		logger: logger,
	}

	t.Run("ValidAccount", func(t *testing.T) {
		account := &model.Account{
			AccountPublic: model.AccountPublic{
				AccountBase: model.AccountBase{
					Describable: model.Describable{
						Name: "Test User",
					},
				},
				UserDataBase: model.UserDataBase{
					Login: "test@example.com",
				},
			},
			Password: "TestPassword123!",
		}

		originalPassword := account.Password
		err := service.ValidateAccount(account)
		require.NoError(t, err)

		// Password should be hashed after validation
		assert.NotEqual(t, originalPassword, account.Password)
	})

	t.Run("AccountMissingName", func(t *testing.T) {
		account := &model.Account{
			AccountPublic: model.AccountPublic{
				UserDataBase: model.UserDataBase{
					Login: "test@example.com",
				},
			},
			Password: "TestPassword123!",
		}

		err := service.ValidateAccount(account)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Name must not be empty")
	})

	t.Run("AccountMissingLogin", func(t *testing.T) {
		account := &model.Account{
			AccountPublic: model.AccountPublic{
				AccountBase: model.AccountBase{
					Describable: model.Describable{
						Name: "Test User",
					},
				},
			},
			Password: "TestPassword123!",
		}

		err := service.ValidateAccount(account)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Login must not be empty")
	})

	t.Run("AccountMissingPassword", func(t *testing.T) {
		account := &model.Account{
			AccountPublic: model.AccountPublic{
				AccountBase: model.AccountBase{
					Describable: model.Describable{
						Name: "Test User",
					},
				},
				UserDataBase: model.UserDataBase{
					Login: "test@example.com",
				},
			},
			Password: "",
		}

		err := service.ValidateAccount(account)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Password must not be empty")
	})

	t.Run("AccountInvalidPassword", func(t *testing.T) {
		account := &model.Account{
			AccountPublic: model.AccountPublic{
				AccountBase: model.AccountBase{
					Describable: model.Describable{
						Name: "Test User",
					},
				},
				UserDataBase: model.UserDataBase{
					Login: "test@example.com",
				},
			},
			Password: "weak", // Should fail validation
		}

		err := service.ValidateAccount(account)
		assert.Error(t, err)
		// Should fail on password validation
		assert.Contains(t, err.Error(), "at least 8 characters")
	})
}

// TestPasswordConfiguration verifies different password rule configurations
func TestPasswordConfiguration(t *testing.T) {
	t.Run("MinimalRequirements", func(t *testing.T) {
		config := &apiconfig.PasswordConfig{
			Check: apiconfig.PasswordChecks{
				MinLength: 4,
				Digit:     false,
				Upper:     false,
				Lower:     false,
				Special:   false,
			},
			TokenLength: 16,
		}

		logger := zap.NewNop() // Use no-op logger for tests
		service := &service{
			config: config,
			logger: logger,
		}

		// Should pass with minimal requirements
		err := service.ValidatePassword("test", nil)
		assert.NoError(t, err)
	})

	t.Run("StrictRequirements", func(t *testing.T) {
		config := &apiconfig.PasswordConfig{
			Check: apiconfig.PasswordChecks{
				MinLength: 12,
				Digit:     true,
				Upper:     true,
				Lower:     true,
				Special:   true,
			},
			TokenLength: 64,
		}

		logger := zap.NewNop() // Use no-op logger for tests
		service := &service{
			config: config,
			logger: logger,
		}

		// Should fail with shorter password
		err := service.ValidatePassword("Test123!", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "at least 12 characters")

		// Should pass with longer password
		err = service.ValidatePassword("TestPassword123!", nil)
		assert.NoError(t, err)
	})
}

