package services

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
)

// setupTestRefreshTokenService crée un RefreshTokenService de test avec une base de données en mémoire
func setupTestRefreshTokenService(t *testing.T) (*RefreshTokenService, *gorm.DB) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		t.Fatalf("Failed to open test database: %v", err)
	}

	// Auto-migrate
	err = db.AutoMigrate(&models.User{}, &models.RefreshToken{})
	if err != nil {
		t.Fatalf("Failed to migrate: %v", err)
	}

	// Create a test user
	user := &models.User{
		Email:    "test@example.com",
		Username: "testuser",
		Role:     "user",
		IsActive: true,
	}
	db.Create(user)

	service := NewRefreshTokenService(db)
	return service, db
}

func TestRefreshTokenService_Store(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	token := "test-refresh-token-123"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token, ttl)
	assert.NoError(t, err)

	// Verify token was stored (check by hash)
	var storedToken models.RefreshToken
	tokenHash := service.hashToken(token)
	err = db.Where("user_id = ? AND token_hash = ?", user.ID, tokenHash).First(&storedToken).Error
	assert.NoError(t, err)
	assert.Equal(t, user.ID, storedToken.UserID)
	assert.Equal(t, tokenHash, storedToken.TokenHash)
}

func TestRefreshTokenService_Validate_ValidToken(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	token := "valid-refresh-token"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token, ttl)
	require.NoError(t, err)

	// Validate the token
	err = service.Validate(user.ID, token)
	assert.NoError(t, err)
}

func TestRefreshTokenService_Validate_InvalidToken(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	// Try to validate a token that doesn't exist
	err := service.Validate(user.ID, "non-existent-token")
	assert.Error(t, err)
	assert.Equal(t, "refresh token not found", err.Error())
}

func TestRefreshTokenService_Validate_ExpiredToken(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	token := "expired-refresh-token"
	ttl := -1 * time.Hour // Expired 1 hour ago

	err := service.Store(user.ID, token, ttl)
	require.NoError(t, err)

	// Validate the expired token
	err = service.Validate(user.ID, token)
	assert.Error(t, err)
	assert.Equal(t, "refresh token expired", err.Error())
}

func TestRefreshTokenService_Validate_WrongUser(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	// Create another user
	otherUser := &models.User{
		Email:    "other@example.com",
		Username: "otheruser",
		Role:     "user",
		IsActive: true,
	}
	db.Create(otherUser)

	token := "user-specific-token"
	ttl := 30 * 24 * time.Hour

	// Store token for first user
	err := service.Store(user.ID, token, ttl)
	require.NoError(t, err)

	// Try to validate with wrong user ID
	err = service.Validate(otherUser.ID, token)
	assert.Error(t, err)
	assert.Equal(t, "refresh token not found", err.Error())
}

func TestRefreshTokenService_Revoke(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	token := "token-to-revoke"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token, ttl)
	require.NoError(t, err)

	// Verify token exists
	err = service.Validate(user.ID, token)
	require.NoError(t, err)

	// Revoke the token
	err = service.Revoke(user.ID, token)
	assert.NoError(t, err)

	// Verify token is no longer valid
	err = service.Validate(user.ID, token)
	assert.Error(t, err)
	assert.Equal(t, "refresh token not found", err.Error())
}

func TestRefreshTokenService_Revoke_NonExistentToken(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	// Try to revoke a token that doesn't exist
	err := service.Revoke(user.ID, "non-existent-token")
	assert.NoError(t, err)
	// assert.Contains(t, err.Error(), "not found") // Service returns nil (idempotent)
}

func TestRefreshTokenService_RevokeAll(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	// Store multiple tokens
	token1 := "token-1"
	token2 := "token-2"
	token3 := "token-3"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token1, ttl)
	require.NoError(t, err)
	err = service.Store(user.ID, token2, ttl)
	require.NoError(t, err)
	err = service.Store(user.ID, token3, ttl)
	require.NoError(t, err)

	// Verify all tokens are valid
	err = service.Validate(user.ID, token1)
	assert.NoError(t, err)
	err = service.Validate(user.ID, token2)
	assert.NoError(t, err)
	err = service.Validate(user.ID, token3)
	assert.NoError(t, err)

	// Revoke all tokens
	err = service.RevokeAll(user.ID)
	assert.NoError(t, err)

	// Verify all tokens are revoked
	err = service.Validate(user.ID, token1)
	assert.Error(t, err)
	err = service.Validate(user.ID, token2)
	assert.Error(t, err)
	err = service.Validate(user.ID, token3)
	assert.Error(t, err)
}

func TestRefreshTokenService_hashToken(t *testing.T) {
	service, _ := setupTestRefreshTokenService(t)

	token := "test-token"
	hash1 := service.hashToken(token)
	hash2 := service.hashToken(token)

	// Same token should produce same hash
	assert.Equal(t, hash1, hash2)
	assert.Len(t, hash1, 64) // SHA-256 produces 64 hex characters

	// Different tokens should produce different hashes
	hash3 := service.hashToken("different-token")
	assert.NotEqual(t, hash1, hash3)
}

func TestRefreshTokenService_StoreMultipleTokens(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	// Store multiple tokens for the same user
	token1 := "token-1"
	token2 := "token-2"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token1, ttl)
	assert.NoError(t, err)

	err = service.Store(user.ID, token2, ttl)
	assert.NoError(t, err)

	// Both tokens should be valid
	err = service.Validate(user.ID, token1)
	assert.NoError(t, err)

	err = service.Validate(user.ID, token2)
	assert.NoError(t, err)

	// Verify both tokens are stored in database
	var count int64
	db.Model(&models.RefreshToken{}).Where("user_id = ?", user.ID).Count(&count)
	assert.Equal(t, int64(2), count)
}

func TestRefreshTokenService_Validate_AfterRevokeOne(t *testing.T) {
	service, db := setupTestRefreshTokenService(t)

	var user models.User
	db.Where("email = ?", "test@example.com").First(&user)

	token1 := "token-1"
	token2 := "token-2"
	ttl := 30 * 24 * time.Hour

	err := service.Store(user.ID, token1, ttl)
	require.NoError(t, err)
	err = service.Store(user.ID, token2, ttl)
	require.NoError(t, err)

	// Revoke only token1
	err = service.Revoke(user.ID, token1)
	assert.NoError(t, err)

	// token1 should be invalid
	err = service.Validate(user.ID, token1)
	assert.Error(t, err)

	// token2 should still be valid
	err = service.Validate(user.ID, token2)
	assert.NoError(t, err)
}
