package middleware

import (
	"sync"
	"time"

	"github.com/gofiber/fiber/v2"
)

type loginAttempt struct {
	Count    int
	LockedAt time.Time
}

// BruteForceGuard tracks login attempts per IP and enforces exponential backoff.
type BruteForceGuard struct {
	mu           sync.Mutex
	attempts     map[string]*loginAttempt
	maxAttempts  int
	lockoutSecs  int
}

func NewBruteForceGuard(maxAttempts, lockoutSecs int) *BruteForceGuard {
	guard := &BruteForceGuard{
		attempts:    make(map[string]*loginAttempt),
		maxAttempts: maxAttempts,
		lockoutSecs: lockoutSecs,
	}

	// Clean up stale entries every 10 minutes
	go func() {
		ticker := time.NewTicker(10 * time.Minute)
		defer ticker.Stop()
		for range ticker.C {
			guard.cleanup()
		}
	}()

	return guard
}

// Check returns a Fiber handler that blocks requests from locked-out IPs.
func (g *BruteForceGuard) Check() fiber.Handler {
	return func(c *fiber.Ctx) error {
		ip := c.IP()

		g.mu.Lock()
		attempt, exists := g.attempts[ip]
		g.mu.Unlock()

		if exists && attempt.Count >= g.maxAttempts {
			lockoutDuration := time.Duration(g.lockoutSecs) * time.Second
			if time.Since(attempt.LockedAt) < lockoutDuration {
				remaining := lockoutDuration - time.Since(attempt.LockedAt)
				return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
					"error":       "too many login attempts, account temporarily locked",
					"retry_after": int(remaining.Seconds()),
				})
			}
			// Lockout expired, reset
			g.mu.Lock()
			delete(g.attempts, ip)
			g.mu.Unlock()
		}

		return c.Next()
	}
}

// RecordFailure increments the failure count for an IP.
func (g *BruteForceGuard) RecordFailure(ip string) {
	g.mu.Lock()
	defer g.mu.Unlock()

	attempt, exists := g.attempts[ip]
	if !exists {
		g.attempts[ip] = &loginAttempt{Count: 1, LockedAt: time.Now()}
		return
	}

	attempt.Count++
	if attempt.Count >= g.maxAttempts {
		attempt.LockedAt = time.Now()
	}
}

// RecordSuccess resets the failure count for an IP after a successful login.
func (g *BruteForceGuard) RecordSuccess(ip string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	delete(g.attempts, ip)
}

func (g *BruteForceGuard) cleanup() {
	g.mu.Lock()
	defer g.mu.Unlock()

	cutoff := time.Duration(g.lockoutSecs*2) * time.Second
	for ip, attempt := range g.attempts {
		if time.Since(attempt.LockedAt) > cutoff {
			delete(g.attempts, ip)
		}
	}
}
