package validators

import (
	"strings"
	"testing"

	"veza-backend-api/internal/models"

	"github.com/stretchr/testify/assert"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupTestDB crée une base de données de test
func setupTestDB(t *testing.T) *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{})
	if err != nil {
		t.Fatalf("Failed to migrate: %v", err)
	}

	return db
}

func TestEmailValidator_ValidateFormat(t *testing.T) {
	db := setupTestDB(t)
	validator := NewEmailValidator(db)

	tests := []struct {
		name  string
		email string
		want  bool
	}{
		{
			name:  "valid email",
			email: "test@example.com",
			want:  true,
		},
		{
			name:  "valid email with subdomain",
			email: "user@mail.example.com",
			want:  true,
		},
		{
			name:  "valid email with plus",
			email: "user+tag@example.com",
			want:  true,
		},
		{
			name:  "valid email with dots",
			email: "first.last@example.com",
			want:  true,
		},
		{
			name:  "valid email with numbers",
			email: "user123@example.com",
			want:  true,
		},
		{
			name:  "valid email with underscore",
			email: "user_name@example.com",
			want:  true,
		},
		{
			name:  "invalid email - no @",
			email: "invalidemail.com",
			want:  false,
		},
		{
			name:  "invalid email - no domain",
			email: "invalid@",
			want:  false,
		},
		{
			name:  "invalid email - no local part",
			email: "@example.com",
			want:  false,
		},
		{
			name:  "invalid email - no TLD",
			email: "invalid@example",
			want:  false,
		},
		{
			name:  "invalid email - too long",
			email: strings.Repeat("a", 250) + "@example.com",
			want:  false,
		},
		{
			name:  "invalid email - local part starts with dot",
			email: ".user@example.com",
			want:  false,
		},
		{
			name:  "invalid email - local part ends with dot",
			email: "user.@example.com",
			want:  false,
		},
		{
			name:  "invalid email - domain starts with dot",
			email: "user@.example.com",
			want:  false,
		},
		{
			name:  "invalid email - domain ends with dot",
			email: "user@example.com.",
			want:  false,
		},
		{
			name:  "invalid email - domain starts with hyphen",
			email: "user@-example.com",
			want:  false,
		},
		{
			name:  "invalid email - domain ends with hyphen",
			email: "user@example.com-",
			want:  false,
		},
		{
			name:  "invalid email - empty string",
			email: "",
			want:  false,
		},
		{
			name:  "invalid email - only spaces",
			email: "   ",
			want:  false,
		},
		{
			name:  "valid email - trimmed spaces",
			email: "  test@example.com  ",
			want:  true,
		},
		{
			name:  "valid email - case insensitive",
			email: "TEST@EXAMPLE.COM",
			want:  true,
		},
		{
			name:  "valid email - with percent",
			email: "user%tag@example.com",
			want:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := validator.ValidateFormat(tt.email)
			assert.Equal(t, tt.want, got, "ValidateFormat(%q) = %v, want %v", tt.email, got, tt.want)
		})
	}
}

func TestEmailValidator_IsUnique(t *testing.T) {
	db := setupTestDB(t)
	validator := NewEmailValidator(db)

	// Créer un utilisateur de test
	user := &models.User{
		Email:    "existing@example.com",
		Username: "existing",
		Role:     "user",
	}
	err := db.Create(user).Error
	assert.NoError(t, err)

	tests := []struct {
		name    string
		email   string
		want    bool
		wantErr bool
	}{
		{
			name:    "unique email",
			email:   "new@example.com",
			want:    true,
			wantErr: false,
		},
		{
			name:    "existing email",
			email:   "existing@example.com",
			want:    false,
			wantErr: false,
		},
		{
			name:    "existing email - case insensitive",
			email:   "EXISTING@EXAMPLE.COM",
			want:    false,
			wantErr: false,
		},
		{
			name:    "existing email - with spaces",
			email:   "  existing@example.com  ",
			want:    false,
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := validator.IsUnique(tt.email)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.want, got, "IsUnique(%q) = %v, want %v", tt.email, got, tt.want)
			}
		})
	}
}

func TestEmailValidator_Validate(t *testing.T) {
	db := setupTestDB(t)
	validator := NewEmailValidator(db)

	// Créer un utilisateur de test
	user := &models.User{
		Email:    "existing@example.com",
		Username: "existing",
		Role:     "user",
	}
	err := db.Create(user).Error
	assert.NoError(t, err)

	tests := []struct {
		name    string
		email   string
		wantErr bool
		errMsg  string
	}{
		{
			name:    "valid and unique email",
			email:   "new@example.com",
			wantErr: false,
		},
		{
			name:    "invalid format",
			email:   "invalid-email",
			wantErr: true,
			errMsg:  "invalid email format",
		},
		{
			name:    "existing email",
			email:   "existing@example.com",
			wantErr: true,
			errMsg:  "email already exists",
		},
		{
			name:    "empty email",
			email:   "",
			wantErr: true,
			errMsg:  "invalid email format",
		},
		{
			name:    "email with invalid format - no @",
			email:   "invalidemail.com",
			wantErr: true,
			errMsg:  "invalid email format",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validator.Validate(tt.email)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestEmailValidator_ValidateFormat_EdgeCases(t *testing.T) {
	db := setupTestDB(t)
	validator := NewEmailValidator(db)

	tests := []struct {
		name  string
		email string
		want  bool
	}{
		{
			name:  "email at max length (254 chars)",
			email: strings.Repeat("a", 64) + "@" + strings.Repeat("b", 186) + ".co",
			want:  true,
		},
		{
			name:  "email over max length (255 chars)",
			email: strings.Repeat("a", 64) + "@" + strings.Repeat("b", 187) + ".co",
			want:  false,
		},
		{
			name:  "local part at max length (64 chars)",
			email: strings.Repeat("a", 64) + "@example.com",
			want:  true,
		},
		{
			name:  "local part over max length (65 chars)",
			email: strings.Repeat("a", 65) + "@example.com",
			want:  false,
		},
		{
			name:  "domain at max length (253 chars)",
			email: "user@" + strings.Repeat("a", 253),
			want:  false, // Domain doit avoir au moins un point
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := validator.ValidateFormat(tt.email)
			assert.Equal(t, tt.want, got, "ValidateFormat(%q) = %v, want %v", tt.email, got, tt.want)
		})
	}
}
