package service

import (
	"strings"
	"time"

	"system-altrak/internal/domain"

	"gorm.io/gorm"
)

type dbLoginAttemptLimiter struct {
	db *gorm.DB
}

func NewDBLoginAttemptLimiter(db *gorm.DB) *dbLoginAttemptLimiter {
	return &dbLoginAttemptLimiter{db: db}
}

func (l *dbLoginAttemptLimiter) Allow(key string, max int, window time.Duration) (bool, time.Duration, error) {
	key = normalizeLoginAttemptKey(key)
	if key == "" {
		return true, 0, nil
	}
	if max <= 0 {
		max = 1
	}
	if window <= 0 {
		window = time.Minute
	}

	var blocked bool
	var retryAfter time.Duration
	err := l.db.Transaction(func(tx *gorm.DB) error {
		now := time.Now()
		var attempt domain.LoginAttempt
		err := tx.Where("`key` = ?", key).Limit(1).Find(&attempt).Error
		if err != nil {
			return err
		}
		if attempt.ID == 0 {
			return nil
		}

		if !attempt.ExpiresAt.After(now) {
			return tx.Unscoped().Where("`key` = ?", key).Delete(&domain.LoginAttempt{}).Error
		}

		if attempt.FailureCount >= max {
			blocked = true
			retryAfter = time.Until(attempt.ExpiresAt)
			if retryAfter < 0 {
				retryAfter = 0
			}
		}

		return nil
	})

	return !blocked, retryAfter, err
}

func (l *dbLoginAttemptLimiter) RecordFailure(key string, window time.Duration) error {
	key = normalizeLoginAttemptKey(key)
	if key == "" {
		return nil
	}
	if window <= 0 {
		window = time.Minute
	}

	return l.db.Transaction(func(tx *gorm.DB) error {
		now := time.Now()
		var attempt domain.LoginAttempt
		err := tx.Where("`key` = ?", key).Limit(1).Find(&attempt).Error
		if err != nil {
			return err
		}

		if attempt.ID == 0 {
			attempt = domain.LoginAttempt{
				Key:          key,
				FailureCount: 1,
				ExpiresAt:    now.Add(window),
			}
			return tx.Create(&attempt).Error
		}

		if !attempt.ExpiresAt.After(now) {
			attempt.FailureCount = 0
		}

		attempt.FailureCount++
		attempt.ExpiresAt = now.Add(window)
		return tx.Save(&attempt).Error
	})
}

func (l *dbLoginAttemptLimiter) Reset(key string) error {
	key = normalizeLoginAttemptKey(key)
	if key == "" {
		return nil
	}

	return l.db.Unscoped().Where("`key` = ?", key).Delete(&domain.LoginAttempt{}).Error
}

func (l *dbLoginAttemptLimiter) Cleanup() error {
	return l.db.Unscoped().Where("expires_at <= ?", time.Now()).Limit(500).Delete(&domain.LoginAttempt{}).Error
}

func (l *dbLoginAttemptLimiter) Size() int {
	var count int64
	err := l.db.Model(&domain.LoginAttempt{}).
		Where("expires_at > ?", time.Now()).
		Count(&count).Error
	if err != nil {
		return 0
	}
	return int(count)
}

func normalizeLoginAttemptKey(key string) string {
	return strings.ToLower(strings.TrimSpace(key))
}
