package service

import (
	"testing"
	"time"

	"system-altrak/internal/domain"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

func newLoginAttemptTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		t.Fatalf("failed to open sqlite database: %v", err)
	}
	if err := db.AutoMigrate(&domain.LoginAttempt{}); err != nil {
		t.Fatalf("failed to migrate login attempt schema: %v", err)
	}
	return db
}

func TestDBLoginAttemptLimiterLifecycle(t *testing.T) {
	db := newLoginAttemptTestDB(t)
	limiter := NewDBLoginAttemptLimiter(db)

	allowed, retryAfter, err := limiter.Allow("user:alice", 2, time.Minute)
	if err != nil {
		t.Fatalf("allow on empty store returned error: %v", err)
	}
	if !allowed {
		t.Fatal("expected empty key to be allowed")
	}
	if retryAfter != 0 {
		t.Fatalf("expected zero retry after on empty key, got %s", retryAfter)
	}

	if err := limiter.RecordFailure("user:alice", time.Minute); err != nil {
		t.Fatalf("failed to record first failure: %v", err)
	}
	if got := limiter.Size(); got != 1 {
		t.Fatalf("expected 1 active attempt row, got %d", got)
	}

	allowed, retryAfter, err = limiter.Allow("user:alice", 2, time.Minute)
	if err != nil {
		t.Fatalf("second allow returned error: %v", err)
	}
	if !allowed {
		t.Fatal("expected second attempt to remain allowed before the threshold")
	}
	if retryAfter != 0 {
		t.Fatalf("expected zero retry after before threshold, got %s", retryAfter)
	}

	if err := limiter.RecordFailure("user:alice", time.Minute); err != nil {
		t.Fatalf("failed to record second failure: %v", err)
	}

	allowed, retryAfter, err = limiter.Allow("user:alice", 2, time.Minute)
	if err != nil {
		t.Fatalf("blocked allow returned error: %v", err)
	}
	if allowed {
		t.Fatal("expected key to be blocked after reaching the limit")
	}
	if retryAfter <= 0 {
		t.Fatal("expected positive retry after when blocked")
	}

	if err := limiter.Reset("user:alice"); err != nil {
		t.Fatalf("failed to reset key: %v", err)
	}
	if got := limiter.Size(); got != 0 {
		t.Fatalf("expected limiter to be empty after reset, got %d", got)
	}
}

func TestDBLoginAttemptLimiterCleanup(t *testing.T) {
	db := newLoginAttemptTestDB(t)
	limiter := NewDBLoginAttemptLimiter(db)

	if err := db.Create(&domain.LoginAttempt{
		Key:          "user:bob",
		FailureCount: 1,
		ExpiresAt:    time.Now().Add(-1 * time.Hour),
	}).Error; err != nil {
		t.Fatalf("failed to seed expired row: %v", err)
	}

	if got := limiter.Size(); got != 0 {
		t.Fatalf("expected expired row to be excluded from active size, got %d", got)
	}

	if err := limiter.Cleanup(); err != nil {
		t.Fatalf("cleanup returned error: %v", err)
	}

	var totalRows int64
	if err := db.Unscoped().Model(&domain.LoginAttempt{}).Count(&totalRows).Error; err != nil {
		t.Fatalf("failed counting login attempt rows: %v", err)
	}
	if totalRows != 0 {
		t.Fatalf("expected cleanup to remove expired rows, got %d", totalRows)
	}
}
