package middleware

import (
	"sync"
	"time"
)

// LoginAttemptTracker tracks failed login attempts per IP and username
type LoginAttemptTracker struct {
	mu              sync.RWMutex
	attempts        map[string]*AttemptInfo
	maxAttempts     int
	lockoutDuration time.Duration
	cleanupInterval time.Duration
}

// AttemptInfo stores information about login attempts
type AttemptInfo struct {
	AttemptCount int
	LockedUntil  time.Time
	LastAttempt  time.Time
}

// NewLoginAttemptTracker creates a new login attempt tracker
func NewLoginAttemptTracker(maxAttempts int, lockoutDuration, cleanupInterval time.Duration) *LoginAttemptTracker {
	tracker := &LoginAttemptTracker{
		attempts:        make(map[string]*AttemptInfo),
		maxAttempts:     maxAttempts,
		lockoutDuration: lockoutDuration,
		cleanupInterval: cleanupInterval,
	}

	// Start cleanup goroutine
	go tracker.cleanup()

	return tracker
}

// RecordFailedAttempt records a failed login attempt
func (t *LoginAttemptTracker) RecordFailedAttempt(identifier string) (locked bool, remainingTime time.Duration) {
	t.mu.Lock()
	defer t.mu.Unlock()

	now := time.Now()
	info, exists := t.attempts[identifier]

	if !exists {
		info = &AttemptInfo{
			AttemptCount: 1,
			LastAttempt:  now,
		}
		t.attempts[identifier] = info
		return false, 0
	}

	// Check if currently locked
	if now.Before(info.LockedUntil) {
		return true, time.Until(info.LockedUntil)
	}

	// Reset attempts if last attempt was more than lockoutDuration ago
	if now.Sub(info.LastAttempt) > t.lockoutDuration {
		info.AttemptCount = 1
	} else {
		info.AttemptCount++
	}

	info.LastAttempt = now

	// Check if max attempts reached
	if info.AttemptCount >= t.maxAttempts {
		info.LockedUntil = now.Add(t.lockoutDuration)
		return true, t.lockoutDuration
	}

	return false, 0
}

// IsLocked checks if an identifier is currently locked out
func (t *LoginAttemptTracker) IsLocked(identifier string) (locked bool, remainingTime time.Duration) {
	t.mu.Lock()
	defer t.mu.Unlock()

	info, exists := t.attempts[identifier]
	if !exists {
		return false, 0
	}

	now := time.Now()
	if now.Before(info.LockedUntil) {
		return true, time.Until(info.LockedUntil)
	}

	return false, 0
}

// ClearAttempts clears failed login attempts for an identifier
func (t *LoginAttemptTracker) ClearAttempts(identifier string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	delete(t.attempts, identifier)
}

// cleanup removes old attempt records periodically
func (t *LoginAttemptTracker) cleanup() {
	ticker := time.NewTicker(t.cleanupInterval)
	defer ticker.Stop()

	for range ticker.C {
		t.mu.Lock()
		now := time.Now()
		for identifier, info := range t.attempts {
			// Remove records that are not locked and haven't had recent attempts
			if now.After(info.LockedUntil) && now.Sub(info.LastAttempt) > t.lockoutDuration*2 {
				delete(t.attempts, identifier)
			}
		}
		t.mu.Unlock()
	}
}
