package user

import (
	"testing"
	"time"

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

func newThrottle(now *time.Time) *LoginThrottle {
	return NewLoginThrottle(
		LoginThrottleConfig{Enabled: true, MaxFailures: 3, Window: 10 * time.Minute},
		func() time.Time { return *now },
	)
}

func TestLoginThrottle_LockoutAfterN(t *testing.T) {
	now := time.Now()
	th := newThrottle(&now)

	ip, email := "1.2.3.4", "user@example.com"
	for i := 0; i < 3; i++ {
		allowed, _ := th.Allow(ip, email)
		require.True(t, allowed, "attempt %d should be allowed", i+1)
		th.RecordFailure(ip, email)
	}
	// 4th attempt blocked.
	allowed, retry := th.Allow(ip, email)
	require.False(t, allowed)
	require.Greater(t, retry, time.Duration(0))
}

func TestLoginThrottle_WindowExpiryResets(t *testing.T) {
	now := time.Now()
	th := newThrottle(&now)
	ip, email := "1.2.3.4", "user@example.com"

	for i := 0; i < 3; i++ {
		th.RecordFailure(ip, email)
	}
	blocked, _ := th.Allow(ip, email)
	require.False(t, blocked)

	// Advance past the window: counter is stale -> allowed again.
	now = now.Add(11 * time.Minute)
	allowed, _ := th.Allow(ip, email)
	require.True(t, allowed)
}

func TestLoginThrottle_SuccessReset(t *testing.T) {
	now := time.Now()
	th := newThrottle(&now)
	ip, email := "1.2.3.4", "user@example.com"

	th.RecordFailure(ip, email)
	th.RecordFailure(ip, email)
	th.Reset(ip, email) // simulate successful login

	// Counter cleared: can fail 3 more times before lockout.
	for i := 0; i < 3; i++ {
		allowed, _ := th.Allow(ip, email)
		require.True(t, allowed)
		th.RecordFailure(ip, email)
	}
	blocked, _ := th.Allow(ip, email)
	require.False(t, blocked)
}

func TestLoginThrottle_PerIPEmailIsolation(t *testing.T) {
	now := time.Now()
	th := newThrottle(&now)
	victim := "victim@example.com"

	// Attacker hammers victim's email from one IP and trips the lock for that IP.
	for i := 0; i < 3; i++ {
		th.RecordFailure("9.9.9.9", victim)
	}
	blocked, _ := th.Allow("9.9.9.9", victim)
	require.False(t, blocked)

	// The real victim, on a DIFFERENT IP, is NOT locked out.
	allowed, _ := th.Allow("5.5.5.5", victim)
	require.True(t, allowed, "victim must not be locked out by an attacker on another IP")
}

func TestLoginThrottle_CaseInsensitiveEmail(t *testing.T) {
	now := time.Now()
	th := newThrottle(&now)
	ip := "1.2.3.4"
	for i := 0; i < 3; i++ {
		th.RecordFailure(ip, "User@Example.com")
	}
	// Same email different case shares the counter.
	blocked, _ := th.Allow(ip, "user@example.com")
	require.False(t, blocked)
}

func TestLoginThrottle_DisabledAlwaysAllows(t *testing.T) {
	th := NewLoginThrottle(LoginThrottleConfig{Enabled: false, MaxFailures: 1}, nil)
	for i := 0; i < 100; i++ {
		th.RecordFailure("1.1.1.1", "a@b.c")
	}
	allowed, _ := th.Allow("1.1.1.1", "a@b.c")
	require.True(t, allowed)
}
