package auth

import (
	"sync"
	"time"

	"github.com/wormhole/wormhole/internal/config"
)

type LoginBlockedError struct {
	RetryAfter time.Duration
}

func (e *LoginBlockedError) Error() string {
	return "too many login attempts"
}

type loginRecord struct {
	failures    int
	windowStart time.Time
	lockedUntil time.Time
}

type LoginLimiter struct {
	mu          sync.Mutex
	maxAttempts int
	window      time.Duration
	lockout     time.Duration
	records     map[string]*loginRecord
}

func NewLoginLimiter(cfg *config.AuthConfig) *LoginLimiter {
	if cfg.LoginMaxAttempts <= 0 {
		return nil
	}
	max := cfg.LoginMaxAttempts
	window := cfg.LoginWindow
	lockout := cfg.LoginLockout
	if window <= 0 {
		window = 5 * time.Minute
	}
	if lockout <= 0 {
		lockout = 15 * time.Minute
	}
	return &LoginLimiter{
		maxAttempts: max,
		window:      window,
		lockout:     lockout,
		records:     make(map[string]*loginRecord),
	}
}

func (l *LoginLimiter) keys(ip, username string) []string {
	keys := []string{"ip:" + ip}
	if username != "" {
		keys = append(keys, "user:"+username)
	}
	return keys
}

func (l *LoginLimiter) IsBlocked(ip, username string) (time.Duration, bool) {
	if l == nil {
		return 0, false
	}
	now := time.Now()
	l.mu.Lock()
	defer l.mu.Unlock()

	var maxRetry time.Duration
	for _, key := range l.keys(ip, username) {
		rec := l.records[key]
		if rec == nil {
			continue
		}
		if now.Before(rec.lockedUntil) {
			retry := time.Until(rec.lockedUntil)
			if retry > maxRetry {
				maxRetry = retry
			}
		}
	}
	return maxRetry, maxRetry > 0
}

func (l *LoginLimiter) RecordFailure(ip, username string) {
	if l == nil {
		return
	}
	now := time.Now()
	l.mu.Lock()
	defer l.mu.Unlock()

	for _, key := range l.keys(ip, username) {
		rec := l.records[key]
		if rec == nil {
			rec = &loginRecord{windowStart: now}
			l.records[key] = rec
		}
		if now.Before(rec.lockedUntil) {
			continue
		}
		if now.Sub(rec.windowStart) > l.window {
			rec.failures = 0
			rec.windowStart = now
		}
		rec.failures++
		if rec.failures >= l.maxAttempts {
			rec.lockedUntil = now.Add(l.lockout)
			rec.failures = 0
		}
	}
}

func (l *LoginLimiter) Reset(ip, username string) {
	if l == nil {
		return
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	for _, key := range l.keys(ip, username) {
		delete(l.records, key)
	}
}
