package loginlimit

import (
	"sync"
	"time"
)

// Limiter tracks failed admin login attempts per client IP.
type Limiter struct {
	mu          sync.Mutex
	attempts    map[string]*record
	maxAttempts int
	lockout     time.Duration
}

type record struct {
	failures    int
	lockedUntil time.Time
}

func New(maxAttempts int, lockout time.Duration) *Limiter {
	if maxAttempts <= 0 {
		maxAttempts = 5
	}
	if lockout <= 0 {
		lockout = 15 * time.Minute
	}
	return &Limiter{
		attempts:    make(map[string]*record),
		maxAttempts: maxAttempts,
		lockout:     lockout,
	}
}

// Check reports whether the IP is locked and how long to wait.
func (l *Limiter) Check(ip string) (locked bool, retryAfter time.Duration) {
	l.mu.Lock()
	defer l.mu.Unlock()
	r := l.attempts[ip]
	if r == nil {
		return false, 0
	}
	now := time.Now()
	if now.Before(r.lockedUntil) {
		return true, r.lockedUntil.Sub(now)
	}
	if r.failures >= l.maxAttempts {
		delete(l.attempts, ip)
	}
	return false, 0
}

// RecordFailure increments the failure count and locks the IP when the limit is reached.
func (l *Limiter) RecordFailure(ip string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	now := time.Now()
	r := l.attempts[ip]
	if r == nil {
		r = &record{}
		l.attempts[ip] = r
	}
	if now.Before(r.lockedUntil) {
		return
	}
	if r.failures >= l.maxAttempts && !r.lockedUntil.IsZero() && now.After(r.lockedUntil) {
		r.failures = 0
		r.lockedUntil = time.Time{}
	}
	r.failures++
	if r.failures >= l.maxAttempts {
		r.lockedUntil = now.Add(l.lockout)
	}
}

// Configure updates rate limit parameters at runtime.
func (l *Limiter) Configure(maxAttempts int, lockout time.Duration) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if maxAttempts > 0 {
		l.maxAttempts = maxAttempts
	}
	if lockout > 0 {
		l.lockout = lockout
	}
}

// Reset clears failure state after a successful login.
func (l *Limiter) Reset(ip string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	delete(l.attempts, ip)
}
