package auth

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func strPtr(s string) *string { return &s }

// ============================================================================
// Mock Implementations
// ============================================================================

// mockUserRepository implements ports.UserRepository for unit testing.
type mockUserRepository struct {
	findUserByEmailWithPasswordFn    func(ctx context.Context, email string) (*ports.UserWithPassword, error)
	findUserByIDWithPasswordFn       func(ctx context.Context, userID string) (*ports.UserWithPassword, error)
	getUserByIDFn                    func(ctx context.Context, userID string) (*models.User, error)
	getUserRoleIDFn                  func(ctx context.Context, userID string) (*string, error)
	getProfileNameByIDFn             func(ctx context.Context, profileID string) (string, error)
	checkUserExistsByEmailFn         func(ctx context.Context, email string) (bool, error)
	checkUserExistsByIDFn            func(ctx context.Context, id string) (bool, error)
	checkEmailConflictFn             func(ctx context.Context, email, excludeID string) (bool, error)
	updatePasswordFn                 func(ctx context.Context, userID, passwordHash string) error
	createUserFn                     func(ctx context.Context, user *models.User) error
	updateUserFn                     func(ctx context.Context, userID string, updates map[string]interface{}) error
	deleteUserFn                     func(ctx context.Context, userID string) error
	findAllFn                        func(ctx context.Context) ([]*models.User, error)
	resetFailedLoginAttemptsFn       func(ctx context.Context, userID string) error
	incrementFailedAndLockIfNeededFn func(ctx context.Context, userID string, maxAttempts int, lockDuration time.Duration) (bool, error)
}

func (m *mockUserRepository) FindUserByEmailWithPassword(ctx context.Context, email string) (*ports.UserWithPassword, error) {
	if m.findUserByEmailWithPasswordFn != nil {
		return m.findUserByEmailWithPasswordFn(ctx, email)
	}
	return nil, nil
}

func (m *mockUserRepository) FindUserByIDWithPassword(ctx context.Context, userID string) (*ports.UserWithPassword, error) {
	if m.findUserByIDWithPasswordFn != nil {
		return m.findUserByIDWithPasswordFn(ctx, userID)
	}
	return nil, nil
}

func (m *mockUserRepository) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
	if m.getUserByIDFn != nil {
		return m.getUserByIDFn(ctx, userID)
	}
	return nil, nil
}

func (m *mockUserRepository) GetUserRoleID(ctx context.Context, userID string) (*string, error) {
	if m.getUserRoleIDFn != nil {
		return m.getUserRoleIDFn(ctx, userID)
	}
	return nil, nil
}

func (m *mockUserRepository) CheckUserExistsByEmail(ctx context.Context, email string) (bool, error) {
	if m.checkUserExistsByEmailFn != nil {
		return m.checkUserExistsByEmailFn(ctx, email)
	}
	return false, nil
}

func (m *mockUserRepository) CheckUserExistsByID(ctx context.Context, id string) (bool, error) {
	if m.checkUserExistsByIDFn != nil {
		return m.checkUserExistsByIDFn(ctx, id)
	}
	return false, nil
}

func (m *mockUserRepository) CheckEmailConflict(ctx context.Context, email, excludeID string) (bool, error) {
	if m.checkEmailConflictFn != nil {
		return m.checkEmailConflictFn(ctx, email, excludeID)
	}
	return false, nil
}

func (m *mockUserRepository) UpdatePassword(ctx context.Context, userID, passwordHash string) error {
	if m.updatePasswordFn != nil {
		return m.updatePasswordFn(ctx, userID, passwordHash)
	}
	return nil
}

func (m *mockUserRepository) CreateUser(ctx context.Context, user *models.User) error {
	if m.createUserFn != nil {
		return m.createUserFn(ctx, user)
	}
	return nil
}

func (m *mockUserRepository) UpdateUser(ctx context.Context, userID string, updates map[string]interface{}) error {
	if m.updateUserFn != nil {
		return m.updateUserFn(ctx, userID, updates)
	}
	return nil
}

func (m *mockUserRepository) DeleteUser(ctx context.Context, userID string) error {
	if m.deleteUserFn != nil {
		return m.deleteUserFn(ctx, userID)
	}
	return nil
}

func (m *mockUserRepository) FindAll(ctx context.Context) ([]*models.User, error) {
	if m.findAllFn != nil {
		return m.findAllFn(ctx)
	}
	return nil, nil
}

func (m *mockUserRepository) GetFirstUserIDByProfile(ctx context.Context, profileID string) (string, error) {
	return "", nil
}
func (m *mockUserRepository) GetRoleNameByID(ctx context.Context, roleID string) (string, error) {
	return "", nil
}
func (m *mockUserRepository) GetProfileNameByID(ctx context.Context, profileID string) (string, error) {
	if m.getProfileNameByIDFn != nil {
		return m.getProfileNameByIDFn(ctx, profileID)
	}
	return "", nil
}
func (m *mockUserRepository) GetUserIDsByRoleIDs(ctx context.Context, roleIDs []string) ([]string, error) {
	return []string{}, nil
}

func (m *mockUserRepository) ResetFailedLoginAttempts(ctx context.Context, userID string) error {
	if m.resetFailedLoginAttemptsFn != nil {
		return m.resetFailedLoginAttemptsFn(ctx, userID)
	}
	return nil
}

func (m *mockUserRepository) IncrementFailedAndLockIfNeeded(ctx context.Context, userID string, maxAttempts int, lockDuration time.Duration) (bool, error) {
	if m.incrementFailedAndLockIfNeededFn != nil {
		return m.incrementFailedAndLockIfNeededFn(ctx, userID, maxAttempts, lockDuration)
	}
	return false, nil
}

// mockSessionRepository implements ports.SessionRepository for unit testing.
type mockSessionRepository struct {
	insertSessionFn            func(ctx context.Context, session *models.SystemSession) error
	getSessionFn               func(ctx context.Context, sessionID string) (*models.SystemSession, error)
	revokeSessionFn            func(ctx context.Context, sessionID string) error
	updateLastActivityFn       func(ctx context.Context, sessionID string) error
	revokeAllSessionsForUserFn func(ctx context.Context, userID string) error
}

func (m *mockSessionRepository) InsertSession(ctx context.Context, session *models.SystemSession) error {
	if m.insertSessionFn != nil {
		return m.insertSessionFn(ctx, session)
	}
	return nil
}

func (m *mockSessionRepository) GetSession(ctx context.Context, sessionID string) (*models.SystemSession, error) {
	if m.getSessionFn != nil {
		return m.getSessionFn(ctx, sessionID)
	}
	return nil, nil
}

func (m *mockSessionRepository) RevokeSession(ctx context.Context, sessionID string) error {
	if m.revokeSessionFn != nil {
		return m.revokeSessionFn(ctx, sessionID)
	}
	return nil
}

func (m *mockSessionRepository) RevokeAllSessionsForUser(ctx context.Context, userID string) error {
	if m.revokeAllSessionsForUserFn != nil {
		return m.revokeAllSessionsForUserFn(ctx, userID)
	}
	return nil
}

func (m *mockSessionRepository) RevokeAllSessionsForConnectedApp(_ context.Context, _ string) (int64, error) {
	return 0, nil
}

func (m *mockSessionRepository) CleanupExpiredSessions(_ context.Context, _ time.Duration) (int64, error) {
	return 0, nil
}

func (m *mockSessionRepository) IsConnectedAppActive(_ context.Context, _ string) (bool, error) {
	return true, nil
}

func (m *mockSessionRepository) UpdateLastActivity(ctx context.Context, sessionID string) error {
	if m.updateLastActivityFn != nil {
		return m.updateLastActivityFn(ctx, sessionID)
	}
	return nil
}

// mockPermissionRepository is a minimal stub — only GetAllProfiles is used in auth tests.
type mockPermissionRepository struct {
	getAllProfilesFn func(ctx context.Context) ([]*models.Profile, error)
}

func (m *mockPermissionRepository) LoadObjectPermission(ctx context.Context, profileID, objectAPIName string) (*models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) LoadFieldPermission(ctx context.Context, profileID, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) LoadEffectiveObjectPermission(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) LoadEffectiveFieldPermission(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListObjectPermissions(ctx context.Context, profileID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListFieldPermissions(ctx context.Context, profileID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) UpsertObjectPermission(ctx context.Context, perm models.ObjectPermissions) error {
	return nil
}
func (m *mockPermissionRepository) BatchUpsertObjectPermissions(ctx context.Context, perms []models.ObjectPermissions) error {
	return nil
}
func (m *mockPermissionRepository) BatchUpsertFieldPermissions(ctx context.Context, perms []models.FieldPermissions) error {
	return nil
}
func (m *mockPermissionRepository) DeleteFieldPermissionsForFields(ctx context.Context, objectAPIName string, fieldAPINames []string) error {
	return nil
}
func (m *mockPermissionRepository) CountObjectPermissionsByProfile(ctx context.Context, profileID string) (int, error) {
	return 0, nil
}
func (m *mockPermissionRepository) UpsertFieldPermission(ctx context.Context, perm models.FieldPermissions) error {
	return nil
}
func (m *mockPermissionRepository) ListPermissionSetObjectPermissions(ctx context.Context, permissionSetID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListPermissionSetFieldPermissions(ctx context.Context, permissionSetID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListEffectiveObjectPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListEffectiveFieldPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermissionRepository) CreatePermissionSet(ctx context.Context, name, label, description string) (string, error) {
	return "", nil
}
func (m *mockPermissionRepository) UpdatePermissionSet(ctx context.Context, id, name, label, description string) error {
	return nil
}
func (m *mockPermissionRepository) DeletePermissionSet(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermissionRepository) GetUserProfileID(ctx context.Context, userID string) (string, error) {
	return "", nil
}
func (m *mockPermissionRepository) GetAllRoles(ctx context.Context) ([]*models.UserRole, error) {
	return nil, nil
}
func (m *mockPermissionRepository) CreateRole(ctx context.Context, name, description string, parentRoleID *string) (string, error) {
	return "", nil
}
func (m *mockPermissionRepository) GetRole(ctx context.Context, id string) (*models.UserRole, error) {
	return nil, nil
}
func (m *mockPermissionRepository) UpdateRole(ctx context.Context, id, name, description string, parentRoleID *string) error {
	return nil
}
func (m *mockPermissionRepository) DeleteRole(ctx context.Context, id string) error { return nil }
func (m *mockPermissionRepository) IsUserInGroup(ctx context.Context, groupID, userID string) (bool, error) {
	return false, nil
}
func (m *mockPermissionRepository) GetManualShareAccessLevels(ctx context.Context, objectAPIName, recordID, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetTeamMemberAccessLevel(ctx context.Context, objectAPIName, recordID, userID string) (*string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetRecordOwnerID(ctx context.Context, tableName, recordID string) (string, error) {
	return "", nil
}
func (m *mockPermissionRepository) GetRecordByID(ctx context.Context, tableName, recordID string) (models.SObject, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetPendingApprovalLockInfo(ctx context.Context, targetObjectID string) (string, string, error) {
	return "", "", nil
}
func (m *mockPermissionRepository) HasPendingApprovalWorkitemForUser(ctx context.Context, processInstanceID, userID string) (bool, error) {
	return false, nil
}
func (m *mockPermissionRepository) GetUserGroupIDs(ctx context.Context, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetManualShareRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetTeamMemberRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetAllProfiles(ctx context.Context) ([]*models.Profile, error) {
	if m.getAllProfilesFn != nil {
		return m.getAllProfilesFn(ctx)
	}
	return nil, nil
}
func (m *mockPermissionRepository) ListSystemPermissions(ctx context.Context) ([]models.SystemPermission, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetEffectiveSystemPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetEffectiveCustomPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	return nil, nil
}
func (m *mockPermissionRepository) ListCustomPermissions(ctx context.Context) ([]models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetCustomPermission(ctx context.Context, id string) (*models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *mockPermissionRepository) CreateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *mockPermissionRepository) UpdateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *mockPermissionRepository) DeleteCustomPermission(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermissionRepository) ListPermissionSetGroups(ctx context.Context) ([]models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetPermissionSetGroupByID(ctx context.Context, id string) (*models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetPermissionSetGroupComponents(ctx context.Context, groupID string) ([]ports.PermissionSetRef, error) {
	return nil, nil
}
func (m *mockPermissionRepository) CreatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *mockPermissionRepository) UpdatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *mockPermissionRepository) DeletePermissionSetGroup(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermissionRepository) CleanupDirectProfilePermissions(ctx context.Context, profileIDs []string) error {
	return nil
}
func (m *mockPermissionRepository) BulkEnsureProfilePermissionSets(_ context.Context, _ []struct{ ID, Name string }) (map[string]string, error) {
	return nil, nil
}
func (m *mockPermissionRepository) BulkCountObjectPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermissionRepository) BulkCountFieldPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermissionRepository) BulkCountObjectPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermissionRepository) BulkCountFieldPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermissionRepository) EnsureProfilePermissionSet(ctx context.Context, profileID, profileName string) (string, error) {
	return "ps_" + profileID, nil
}
func (m *mockPermissionRepository) CleanupProfilePermissionSet(ctx context.Context, profileID string) error {
	return nil
}
func (m *mockPermissionRepository) LockPermissionSet(_ context.Context, _ string) (bool, error) {
	return true, nil
}
func (m *mockPermissionRepository) GetPermissionSetGrants(ctx context.Context, permSetID string) (map[string]bool, error) {
	return nil, nil
}
func (m *mockPermissionRepository) GetPermissionSetGrantsMany(ctx context.Context, permSetIDs []string) (map[string]map[string]bool, error) {
	return nil, nil
}
func (m *mockPermissionRepository) SetSystemPermission(ctx context.Context, permSetID, apiName string, enabled bool) error {
	return nil
}

// ============================================================================
// Test Helpers
// ============================================================================

// newTestService creates an AuthService with mock repositories, returning
// the service and references to each mock for test-specific configuration.
func newTestService() (*AuthService, *mockUserRepository, *mockSessionRepository, *mockPermissionRepository) {
	userRepo := &mockUserRepository{}
	sessionRepo := &mockSessionRepository{}
	permRepo := &mockPermissionRepository{}
	svc := NewAuthService(userRepo, sessionRepo, permRepo)
	return svc, userRepo, sessionRepo, permRepo
}

// hashForTest creates a bcrypt hash suitable for test assertions.
func hashForTest(t *testing.T, password string) string {
	t.Helper()
	h, err := auth.HashPassword(password)
	require.NoError(t, err)
	return h
}

// validUserWithPassword returns a standard test user with a hashed password.
func validUserWithPassword(t *testing.T, email, password string) *ports.UserWithPassword {
	t.Helper()
	return &ports.UserWithPassword{
		User: &models.User{
			ID:        "user-001",
			Username:  email,
			Email:     email,
			FirstName: "Test",
			LastName:  "User",
			ProfileID: "standard_user",
			IsActive:  true,
		},
		PasswordHash: hashForTest(t, password),
	}
}

// ============================================================================
// Tests — Login
// ============================================================================

func TestLogin(t *testing.T) {
	const (
		testEmail    = "alice@example.com"
		testPassword = "SecurePass1!"
		testIP       = "10.0.0.1"
		testUA       = "TestAgent/1.0"
	)

	tests := []struct {
		name        string
		email       string
		password    string
		setupMocks  func(*mockUserRepository, *mockSessionRepository)
		wantErr     bool
		errContains string
	}{
		{
			name:     "successful login returns token and user",
			email:    testEmail,
			password: testPassword,
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
					return validUserWithPassword(t, email, testPassword), nil
				}
			},
			wantErr: false,
		},
		{
			name:     "user not found returns unauthorized",
			email:    "nobody@example.com",
			password: testPassword,
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, _ string) (*ports.UserWithPassword, error) {
					return nil, nil // user not found
				}
			},
			wantErr:     true,
			errContains: "Invalid email or password",
		},
		{
			name:     "wrong password returns unauthorized",
			email:    testEmail,
			password: "WrongPassword1!",
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
					return validUserWithPassword(t, email, testPassword), nil
				}
			},
			wantErr:     true,
			errContains: "Invalid email or password",
		},
		{
			name:     "empty password hash returns unauthorized",
			email:    testEmail,
			password: testPassword,
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
					return &ports.UserWithPassword{
						User: &models.User{
							ID:        "user-002",
							Username:  email,
							Email:     email,
							ProfileID: "standard_user",
						},
						PasswordHash: "", // no password configured
					}, nil
				}
			},
			wantErr:     true,
			errContains: "Password authentication not configured",
		},
		{
			name:     "database error propagates",
			email:    testEmail,
			password: testPassword,
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, _ string) (*ports.UserWithPassword, error) {
					return nil, fmt.Errorf("connection refused")
				}
			},
			wantErr:     true,
			errContains: "database error",
		},
		{
			name:     "session persistence failure propagates",
			email:    testEmail,
			password: testPassword,
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
					return validUserWithPassword(t, email, testPassword), nil
				}
				sr.insertSessionFn = func(_ context.Context, _ *models.SystemSession) error {
					return fmt.Errorf("disk full")
				}
			},
			wantErr:     true,
			errContains: "failed to persist session",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, userRepo, sessionRepo, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(userRepo, sessionRepo)
			}

			result, err := svc.Login(context.Background(), tc.email, tc.password, testIP, testUA, nil)

			if tc.wantErr {
				require.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
				assert.Nil(t, result)
			} else {
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.NotEmpty(t, result.Token)
				assert.Equal(t, tc.email, result.User.Email)
				assert.False(t, result.ExpiresAt.IsZero())
				assert.True(t, result.ExpiresAt.After(time.Now()))
			}
		})
	}
}

func TestLogin_SuccessfulLogin_PopulatesUserSession(t *testing.T) {
	svc, userRepo, _, _ := newTestService()
	roleID := "role-admin"

	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:         "user-xyz",
				Username:   email,
				Email:      email,
				FirstName:  "Alice",
				LastName:   "Smith",
				ProfileID:  "admin_profile",
				IsActive:   true,
				UserRoleID: &roleID,
			},
			PasswordHash: hashForTest(t, "ValidPass1!"),
		}, nil
	}
	userRepo.getProfileNameByIDFn = func(_ context.Context, profileID string) (string, error) {
		assert.Equal(t, "admin_profile", profileID)
		return "Admin Profile", nil
	}

	result, err := svc.Login(context.Background(), "alice@example.com", "ValidPass1!", "1.2.3.4", "UA", nil)
	require.NoError(t, err)

	assert.Equal(t, "user-xyz", result.User.ID)
	assert.Equal(t, "Alice Smith", result.User.Name)
	assert.Equal(t, "alice@example.com", result.User.Email)
	assert.Equal(t, "admin_profile", result.User.ProfileId)
	assert.Equal(t, "Admin Profile", result.User.ProfileName)
	assert.Equal(t, &roleID, result.User.RoleId)
}

func TestLogin_InactiveUserRejectedWithoutSession(t *testing.T) {
	svc, userRepo, sessionRepo, _ := newTestService()

	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:        "inactive-user",
				Username:  email,
				Email:     email,
				FirstName: "Inactive",
				LastName:  "User",
				ProfileID: "standard_user",
				IsActive:  false,
			},
			PasswordHash: hashForTest(t, "ValidPass1!"),
		}, nil
	}

	sessionInserted := false
	sessionRepo.insertSessionFn = func(_ context.Context, _ *models.SystemSession) error {
		sessionInserted = true
		return nil
	}

	result, err := svc.Login(context.Background(), "inactive@example.com", "ValidPass1!", "1.2.3.4", "UA", nil)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "User account is inactive")
	assert.Nil(t, result)
	assert.False(t, sessionInserted, "inactive users must not create sessions")
}

// ============================================================================
// Tests — ValidateSession
// ============================================================================

func TestValidateSession(t *testing.T) {
	// Generate a real token for testing validation
	testSession := auth.UserSession{
		ID:        "user-100",
		Name:      "Test User",
		Email:     "test@example.com",
		ProfileId: "standard_user",
	}
	validToken, err := auth.GenerateToken(testSession)
	require.NoError(t, err)

	// Decode the token to get the session ID (JTI) for mock setup
	claims, err := auth.DecodeToken(validToken)
	require.NoError(t, err)
	sessionID := claims.RegisteredClaims.ID

	activeUser := &models.User{ID: "user-100", IsActive: true}

	tests := []struct {
		name           string
		token          string
		setupMocks     func(*mockSessionRepository)
		setupUserMocks func(*mockUserRepository)
		wantErr        bool
		errContains    string
	}{
		{
			name:  "valid token with active session succeeds",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.getSessionFn = func(_ context.Context, id string) (*models.SystemSession, error) {
					assert.Equal(t, sessionID, id)
					return &models.SystemSession{
						ID:        id,
						UserID:    "user-100",
						Token:     validToken,
						IsRevoked: false,
						ExpiresAt: time.Now().Add(24 * time.Hour),
					}, nil
				}
			},
			setupUserMocks: func(ur *mockUserRepository) {
				ur.getUserByIDFn = func(_ context.Context, _ string) (*models.User, error) {
					return activeUser, nil
				}
			},
			wantErr: false,
		},
		{
			name:  "revoked session returns unauthorized",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.getSessionFn = func(_ context.Context, id string) (*models.SystemSession, error) {
					return &models.SystemSession{
						ID:        id,
						UserID:    "user-100",
						IsRevoked: true,
					}, nil
				}
			},
			wantErr:     true,
			errContains: "revoked",
		},
		{
			name:  "inactive user is rejected even with valid non-revoked session",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.getSessionFn = func(_ context.Context, id string) (*models.SystemSession, error) {
					return &models.SystemSession{
						ID:        id,
						UserID:    "user-100",
						IsRevoked: false,
						ExpiresAt: time.Now().Add(24 * time.Hour),
					}, nil
				}
			},
			setupUserMocks: func(ur *mockUserRepository) {
				ur.getUserByIDFn = func(_ context.Context, _ string) (*models.User, error) {
					return &models.User{ID: "user-100", IsActive: false}, nil
				}
			},
			wantErr:     true,
			errContains: "inactive",
		},
		{
			name:  "session not found in DB returns unauthorized",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.getSessionFn = func(_ context.Context, _ string) (*models.SystemSession, error) {
					return nil, nil // not found
				}
			},
			wantErr:     true,
			errContains: "Session not found",
		},
		{
			name:        "malformed token fails JWT validation",
			token:       "not-a-valid-jwt",
			setupMocks:  func(sr *mockSessionRepository) {},
			wantErr:     true,
			errContains: "", // JWT parse error
		},
		{
			name:  "database error during session lookup propagates",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.getSessionFn = func(_ context.Context, _ string) (*models.SystemSession, error) {
					return nil, fmt.Errorf("timeout")
				}
			},
			wantErr:     true,
			errContains: "database error",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, userRepo, sessionRepo, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(sessionRepo)
			}
			if tc.setupUserMocks != nil {
				tc.setupUserMocks(userRepo)
			}

			resultClaims, err := svc.ValidateSession(context.Background(), tc.token)

			if tc.wantErr {
				assert.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
				assert.Nil(t, resultClaims)
			} else {
				require.NoError(t, err)
				require.NotNil(t, resultClaims)
				assert.Equal(t, "user-100", resultClaims.Subject)
			}
		})
	}
}

// ============================================================================
// Tests — Logout
// ============================================================================

func TestLogout(t *testing.T) {
	// Generate a real token for logout tests
	testSession := auth.UserSession{
		ID:        "user-200",
		Name:      "Logout Tester",
		Email:     "logout@example.com",
		ProfileId: "standard_user",
	}
	validToken, err := auth.GenerateToken(testSession)
	require.NoError(t, err)

	tests := []struct {
		name        string
		token       string
		setupMocks  func(*mockSessionRepository)
		wantErr     bool
		errContains string
	}{
		{
			name:  "successful logout revokes session",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.revokeSessionFn = func(_ context.Context, _ string) error {
					return nil
				}
			},
			wantErr: false,
		},
		{
			name:        "malformed token returns validation error",
			token:       "garbage-token",
			setupMocks:  func(sr *mockSessionRepository) {},
			wantErr:     true,
			errContains: "", // validation error from DecodeToken
		},
		{
			name:  "database error during revoke propagates",
			token: validToken,
			setupMocks: func(sr *mockSessionRepository) {
				sr.revokeSessionFn = func(_ context.Context, _ string) error {
					return fmt.Errorf("database unavailable")
				}
			},
			wantErr:     true,
			errContains: "database unavailable",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, _, sessionRepo, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(sessionRepo)
			}

			err := svc.Logout(context.Background(), tc.token)

			if tc.wantErr {
				assert.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// ============================================================================
// Tests — ChangePassword
// ============================================================================

func TestChangePassword(t *testing.T) {
	const (
		userID       = "user-300"
		currentPwd   = "OldSecure1!"
		newPwd       = "NewSecure2@"
		weakPwd      = "weak"
		noUpperPwd   = "nouppercase1!"
		noDigitPwd   = "NoDigitHere!"
		noSpecialPwd = "NoSpecial1a"
	)

	tests := []struct {
		name        string
		userID      string
		currentPwd  string
		newPwd      string
		setupMocks  func(*mockUserRepository)
		wantErr     bool
		errContains string
	}{
		{
			name:       "successful password change",
			userID:     userID,
			currentPwd: currentPwd,
			newPwd:     newPwd,
			setupMocks: func(ur *mockUserRepository) {
				ur.findUserByIDWithPasswordFn = func(_ context.Context, id string) (*ports.UserWithPassword, error) {
					return &ports.UserWithPassword{
						User:         &models.User{ID: id},
						PasswordHash: hashForTest(t, currentPwd),
					}, nil
				}
				ur.updatePasswordFn = func(_ context.Context, _ string, newHash string) error {
					// Verify the new hash is valid
					assert.True(t, auth.VerifyPassword(newPwd, newHash))
					return nil
				}
			},
			wantErr: false,
		},
		{
			name:        "weak new password rejected",
			userID:      userID,
			currentPwd:  currentPwd,
			newPwd:      weakPwd,
			setupMocks:  func(ur *mockUserRepository) {},
			wantErr:     true,
			errContains: "at least 8 characters",
		},
		{
			name:        "new password without uppercase rejected",
			userID:      userID,
			currentPwd:  currentPwd,
			newPwd:      noUpperPwd,
			setupMocks:  func(ur *mockUserRepository) {},
			wantErr:     true,
			errContains: "uppercase",
		},
		{
			name:        "new password without digit rejected",
			userID:      userID,
			currentPwd:  currentPwd,
			newPwd:      noDigitPwd,
			setupMocks:  func(ur *mockUserRepository) {},
			wantErr:     true,
			errContains: "number",
		},
		{
			name:        "new password without special char rejected",
			userID:      userID,
			currentPwd:  currentPwd,
			newPwd:      noSpecialPwd,
			setupMocks:  func(ur *mockUserRepository) {},
			wantErr:     true,
			errContains: "special character",
		},
		{
			name:       "wrong current password rejected",
			userID:     userID,
			currentPwd: "WrongCurrent1!",
			newPwd:     newPwd,
			setupMocks: func(ur *mockUserRepository) {
				ur.findUserByIDWithPasswordFn = func(_ context.Context, id string) (*ports.UserWithPassword, error) {
					return &ports.UserWithPassword{
						User:         &models.User{ID: id},
						PasswordHash: hashForTest(t, currentPwd),
					}, nil
				}
			},
			wantErr:     true,
			errContains: "Current password is incorrect",
		},
		{
			name:       "user not found returns not found error",
			userID:     "nonexistent",
			currentPwd: currentPwd,
			newPwd:     newPwd,
			setupMocks: func(ur *mockUserRepository) {
				ur.findUserByIDWithPasswordFn = func(_ context.Context, _ string) (*ports.UserWithPassword, error) {
					return nil, nil
				}
			},
			wantErr:     true,
			errContains: "not found",
		},
		{
			name:       "user with no password hash configured",
			userID:     userID,
			currentPwd: currentPwd,
			newPwd:     newPwd,
			setupMocks: func(ur *mockUserRepository) {
				ur.findUserByIDWithPasswordFn = func(_ context.Context, id string) (*ports.UserWithPassword, error) {
					return &ports.UserWithPassword{
						User:         &models.User{ID: id},
						PasswordHash: "",
					}, nil
				}
			},
			wantErr:     true,
			errContains: "not configured",
		},
		{
			name:       "database error on user lookup propagates",
			userID:     userID,
			currentPwd: currentPwd,
			newPwd:     newPwd,
			setupMocks: func(ur *mockUserRepository) {
				ur.findUserByIDWithPasswordFn = func(_ context.Context, _ string) (*ports.UserWithPassword, error) {
					return nil, fmt.Errorf("db down")
				}
			},
			wantErr:     true,
			errContains: "failed to retrieve user",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, userRepo, _, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(userRepo)
			}

			err := svc.ChangePassword(context.Background(), tc.userID, tc.currentPwd, tc.newPwd)

			if tc.wantErr {
				assert.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// ============================================================================
// Tests — CreateSessionForUser (OAuth flow)
// ============================================================================

func TestCreateSessionForUser(t *testing.T) {
	tests := []struct {
		name        string
		userID      string
		setupMocks  func(*mockUserRepository, *mockSessionRepository)
		wantErr     bool
		errContains string
	}{
		{
			name:   "successful session creation for OAuth user",
			userID: "user-400",
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				roleID := "role-x"
				ur.getUserByIDFn = func(_ context.Context, id string) (*models.User, error) {
					return &models.User{
						ID:        id,
						Username:  "oauth@example.com",
						Email:     "oauth@example.com",
						FirstName: "OAuth",
						LastName:  "User",
						ProfileID: "standard_user",
						IsActive:  true,
					}, nil
				}
				ur.getUserRoleIDFn = func(_ context.Context, _ string) (*string, error) {
					return &roleID, nil
				}
				ur.getProfileNameByIDFn = func(_ context.Context, profileID string) (string, error) {
					assert.Equal(t, "standard_user", profileID)
					return "Standard User", nil
				}
			},
			wantErr: false,
		},
		{
			name:   "user not found returns not found error",
			userID: "missing-user",
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.getUserByIDFn = func(_ context.Context, _ string) (*models.User, error) {
					return nil, nil
				}
			},
			wantErr:     true,
			errContains: "not found",
		},
		{
			name:   "user repo error propagates",
			userID: "user-err",
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.getUserByIDFn = func(_ context.Context, _ string) (*models.User, error) {
					return nil, fmt.Errorf("connection reset")
				}
			},
			wantErr:     true,
			errContains: "connection reset",
		},
		{
			name:   "session persistence failure propagates",
			userID: "user-500",
			setupMocks: func(ur *mockUserRepository, sr *mockSessionRepository) {
				ur.getUserByIDFn = func(_ context.Context, id string) (*models.User, error) {
					return &models.User{
						ID:        id,
						Username:  "persist-fail@example.com",
						Email:     "persist-fail@example.com",
						ProfileID: "standard_user",
						IsActive:  true,
					}, nil
				}
				sr.insertSessionFn = func(_ context.Context, _ *models.SystemSession) error {
					return fmt.Errorf("write error")
				}
			},
			wantErr:     true,
			errContains: "failed to persist session",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, userRepo, sessionRepo, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(userRepo, sessionRepo)
			}

			token, err := svc.CreateSessionForUser(context.Background(), tc.userID, "10.0.0.1", "OAuthClient", nil)

			if tc.wantErr {
				assert.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
				assert.Nil(t, token)
			} else {
				require.NoError(t, err)
				require.NotNil(t, token)
				assert.NotEmpty(t, token.AccessToken)
				assert.Equal(t, "Bearer", token.TokenType)
				assert.Greater(t, token.ExpiresIn, 0)
				claims, decodeErr := auth.DecodeToken(token.AccessToken)
				require.NoError(t, decodeErr)
				assert.Equal(t, "Standard User", claims.User.ProfileName)
			}
		})
	}
}

func TestCreateSessionForUser_InactiveUserRejectedWithoutSession(t *testing.T) {
	svc, userRepo, sessionRepo, _ := newTestService()

	userRepo.getUserByIDFn = func(_ context.Context, userID string) (*models.User, error) {
		return &models.User{
			ID:        userID,
			Username:  "inactive-oauth@example.com",
			Email:     "inactive-oauth@example.com",
			FirstName: "Inactive",
			LastName:  "OAuth",
			ProfileID: "standard_user",
			IsActive:  false,
		}, nil
	}

	sessionInserted := false
	sessionRepo.insertSessionFn = func(_ context.Context, _ *models.SystemSession) error {
		sessionInserted = true
		return nil
	}

	token, err := svc.CreateSessionForUser(context.Background(), "inactive-oauth", "10.0.0.1", "OAuthClient", nil)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "User account is inactive")
	assert.Nil(t, token)
	assert.False(t, sessionInserted, "inactive users must not create sessions")
}

// ============================================================================
// Tests — GetUserByID
// ============================================================================

func TestGetUserByID(t *testing.T) {
	tests := []struct {
		name        string
		userID      string
		setupMocks  func(*mockUserRepository)
		wantErr     bool
		errContains string
		checkResult func(t *testing.T, user *models.UserSession)
	}{
		{
			name:   "successful user retrieval with role",
			userID: "user-600",
			setupMocks: func(ur *mockUserRepository) {
				roleID := "role-mgr"
				ur.getUserByIDFn = func(_ context.Context, id string) (*models.User, error) {
					return &models.User{
						ID:        id,
						Username:  "manager@example.com",
						Email:     "manager@example.com",
						FirstName: "Jane",
						LastName:  "Manager",
						ProfileID: "admin_profile",
					}, nil
				}
				ur.getUserRoleIDFn = func(_ context.Context, _ string) (*string, error) {
					return &roleID, nil
				}
				ur.getProfileNameByIDFn = func(_ context.Context, profileID string) (string, error) {
					assert.Equal(t, "admin_profile", profileID)
					return "Admin Profile", nil
				}
			},
			wantErr: false,
			checkResult: func(t *testing.T, user *models.UserSession) {
				assert.Equal(t, "user-600", user.ID)
				assert.Equal(t, "Jane Manager", user.Name)
				assert.Equal(t, "manager@example.com", *user.Email)
				assert.Equal(t, "admin_profile", user.ProfileID)
				assert.Equal(t, "Admin Profile", user.ProfileName)
				assert.NotNil(t, user.RoleID)
				assert.Equal(t, "role-mgr", *user.RoleID)
			},
		},
		{
			name:   "user not found",
			userID: "missing",
			setupMocks: func(ur *mockUserRepository) {
				ur.getUserByIDFn = func(_ context.Context, _ string) (*models.User, error) {
					return nil, nil
				}
			},
			wantErr:     true,
			errContains: "not found",
		},
		{
			name:   "user with username fallback for display name",
			userID: "user-700",
			setupMocks: func(ur *mockUserRepository) {
				ur.getUserByIDFn = func(_ context.Context, id string) (*models.User, error) {
					return &models.User{
						ID:        id,
						Username:  "svc-account",
						Email:     "svc@example.com",
						FirstName: "",
						LastName:  "",
						ProfileID: "api_only",
					}, nil
				}
			},
			wantErr: false,
			checkResult: func(t *testing.T, user *models.UserSession) {
				assert.Equal(t, "svc-account", user.Name, "should fall back to Username when first/last are empty")
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, userRepo, _, _ := newTestService()
			if tc.setupMocks != nil {
				tc.setupMocks(userRepo)
			}

			user, err := svc.GetUserByID(context.Background(), tc.userID)

			if tc.wantErr {
				assert.Error(t, err)
				if tc.errContains != "" {
					assert.Contains(t, err.Error(), tc.errContains)
				}
			} else {
				require.NoError(t, err)
				require.NotNil(t, user)
				if tc.checkResult != nil {
					tc.checkResult(t, user)
				}
			}
		})
	}
}

// ============================================================================
// Tests — NewAuthService constructor
// ============================================================================

func TestNewAuthService(t *testing.T) {
	userRepo := &mockUserRepository{}
	sessionRepo := &mockSessionRepository{}
	permRepo := &mockPermissionRepository{}

	svc := NewAuthService(userRepo, sessionRepo, permRepo)

	require.NotNil(t, svc, "NewAuthService should return a non-nil service")
	assert.Equal(t, userRepo, svc.userRepo)
	assert.Equal(t, sessionRepo, svc.sessionRepo)
	assert.Equal(t, permRepo, svc.permissionRepo)
}

// ============================================================================
// Tests — Account Lockout
// ============================================================================

func TestLogin_LockedAccount_RejectsLogin(t *testing.T) {
	svc, userRepo, _, _ := newTestService()

	lockedUntil := time.Now().Add(10 * time.Minute)
	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:                  "user-locked",
				Username:            email,
				Email:               email,
				ProfileID:           "standard_user",
				IsActive:            true,
				FailedLoginAttempts: 5,
				LockedUntil:         &lockedUntil,
			},
			PasswordHash: hashForTest(t, "ValidPass1!"),
		}, nil
	}

	result, err := svc.Login(context.Background(), "locked@example.com", "ValidPass1!", "1.2.3.4", "UA", nil)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "temporarily locked")
	assert.Nil(t, result)
}

func TestLogin_ExpiredLock_AllowsLogin(t *testing.T) {
	svc, userRepo, _, _ := newTestService()

	// Lock expired in the past
	expiredLock := time.Now().Add(-1 * time.Minute)
	resetCalled := false
	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:                  "user-expired-lock",
				Username:            email,
				Email:               email,
				ProfileID:           "standard_user",
				IsActive:            true,
				FailedLoginAttempts: 5,
				LockedUntil:         &expiredLock,
			},
			PasswordHash: hashForTest(t, "ValidPass1!"),
		}, nil
	}
	userRepo.resetFailedLoginAttemptsFn = func(_ context.Context, _ string) error {
		resetCalled = true
		return nil
	}

	result, err := svc.Login(context.Background(), "expired@example.com", "ValidPass1!", "1.2.3.4", "UA", nil)

	require.NoError(t, err)
	require.NotNil(t, result)
	assert.True(t, resetCalled, "failed attempts should be reset on successful login")
}

func TestLogin_FailedAttempts_IncrementsCounter(t *testing.T) {
	svc, userRepo, _, _ := newTestService()

	incrementCalled := false
	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:                  "user-attempts",
				Username:            email,
				Email:               email,
				ProfileID:           "standard_user",
				IsActive:            true,
				FailedLoginAttempts: 2, // below threshold
			},
			PasswordHash: hashForTest(t, "CorrectPass1!"),
		}, nil
	}
	userRepo.incrementFailedAndLockIfNeededFn = func(_ context.Context, _ string, _ int, _ time.Duration) (bool, error) {
		incrementCalled = true
		return false, nil // below threshold, not locked
	}

	result, err := svc.Login(context.Background(), "user@example.com", "WrongPass1!", "1.2.3.4", "UA", nil)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "Invalid email or password")
	assert.Nil(t, result)
	assert.True(t, incrementCalled, "failed attempts counter should be incremented")
}

func TestLogin_FailedAttempts_LocksAccountAtThreshold(t *testing.T) {
	svc, userRepo, _, _ := newTestService()

	lockTriggered := false
	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:                  "user-threshold",
				Username:            email,
				Email:               email,
				ProfileID:           "standard_user",
				IsActive:            true,
				FailedLoginAttempts: 4, // one away from threshold (MaxFailedLoginAttempts = 5)
			},
			PasswordHash: hashForTest(t, "CorrectPass1!"),
		}, nil
	}
	userRepo.incrementFailedAndLockIfNeededFn = func(_ context.Context, _ string, _ int, _ time.Duration) (bool, error) {
		lockTriggered = true
		return true, nil // threshold reached, locked
	}

	result, err := svc.Login(context.Background(), "user@example.com", "WrongPass1!", "1.2.3.4", "UA", nil)

	require.Error(t, err)
	assert.Nil(t, result)
	assert.True(t, lockTriggered, "atomic increment-and-lock should be called")
}

func TestLogin_SuccessfulLogin_ResetsFailedAttempts(t *testing.T) {
	svc, userRepo, _, _ := newTestService()

	resetCalled := false
	userRepo.findUserByEmailWithPasswordFn = func(_ context.Context, email string) (*ports.UserWithPassword, error) {
		return &ports.UserWithPassword{
			User: &models.User{
				ID:                  "user-reset",
				Username:            email,
				Email:               email,
				ProfileID:           "standard_user",
				IsActive:            true,
				FailedLoginAttempts: 3,
			},
			PasswordHash: hashForTest(t, "ValidPass1!"),
		}, nil
	}
	userRepo.resetFailedLoginAttemptsFn = func(_ context.Context, _ string) error {
		resetCalled = true
		return nil
	}

	result, err := svc.Login(context.Background(), "user@example.com", "ValidPass1!", "1.2.3.4", "UA", nil)

	require.NoError(t, err)
	require.NotNil(t, result)
	assert.True(t, resetCalled, "failed attempts should be reset on successful login")
}
