package testutils

import (
	"fmt"
	"strings"
	"time"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

// CreateTestUser crée un utilisateur de test avec des valeurs par défaut
func CreateTestUser(db *gorm.DB) (*models.User, error) {
	// Make username and email unique to avoid constraint violations when tests share the same DB
	uniqueID := strings.ReplaceAll(uuid.New().String()[:8], "-", "")
	uniqueUsername := fmt.Sprintf("testuser_%s", uniqueID)
	uniqueEmail := fmt.Sprintf("test_%s@example.com", uniqueID)
	// Slug must also be unique - use the same uniqueID to ensure uniqueness
	uniqueSlug := fmt.Sprintf("testuser-%s", uniqueID)

	user := &models.User{
		Username:     uniqueUsername,
		Slug:         uniqueSlug,
		Email:        uniqueEmail,
		PasswordHash: "$2a$10$examplehash", // Hash bcrypt factice
		TokenVersion: 0,
		FirstName:    "Test",
		LastName:     "User",
		Role:         "user",
		IsActive:     true,
		IsVerified:   true,
		IsAdmin:      false,
	}

	if err := db.Create(user).Error; err != nil {
		return nil, err
	}

	return user, nil
}

// CreateTestUserWithCustomData crée un utilisateur de test avec des données personnalisées
func CreateTestUserWithCustomData(db *gorm.DB, username, email string) (*models.User, error) {
	// Make username and email unique to avoid constraint violations
	// Username must match ^[a-zA-Z0-9_]{3,30}$ (no dashes, only alphanum + underscore)
	uniqueID := strings.ReplaceAll(uuid.New().String()[:8], "-", "") // Remove dashes from UUID
	// Ensure username doesn't exceed 30 chars (constraint limit)
	maxUsernameLen := 30
	uniqueUsername := fmt.Sprintf("%s_%s", username, uniqueID)
	if len(uniqueUsername) > maxUsernameLen {
		// Truncate username part if needed
		maxUsernamePartLen := maxUsernameLen - len(uniqueID) - 1 // -1 for underscore
		if maxUsernamePartLen < 1 {
			maxUsernamePartLen = 1
		}
		uniqueUsername = fmt.Sprintf("%s_%s", username[:maxUsernamePartLen], uniqueID)
	}

	// Extract email parts and add unique ID
	emailParts := strings.Split(email, "@")
	if len(emailParts) != 2 {
		return nil, fmt.Errorf("invalid email format: %s", email)
	}
	uniqueEmail := fmt.Sprintf("%s_%s@%s", emailParts[0], uniqueID, emailParts[1])

	// Slug must also be unique - generate from username with uniqueID
	// Slugify converts underscores to dashes, so "customuser_abc123" becomes "customuser-abc123"
	uniqueSlug := fmt.Sprintf("%s-%s", strings.ToLower(username), uniqueID)

	user := &models.User{
		Username:     uniqueUsername,
		Slug:         uniqueSlug,
		Email:        uniqueEmail,
		PasswordHash: "$2a$10$examplehash",
		TokenVersion: 0,
		FirstName:    "Test",
		LastName:     "User",
		Role:         "user",
		IsActive:     true,
		IsVerified:   true,
		IsAdmin:      false,
	}

	if err := db.Create(user).Error; err != nil {
		return nil, err
	}

	return user, nil
}

// CreateTestAdmin crée un utilisateur administrateur de test
func CreateTestAdmin(db *gorm.DB) (*models.User, error) {
	// Make username and email unique to avoid constraint violations when tests share the same DB
	uniqueID := strings.ReplaceAll(uuid.New().String()[:8], "-", "")
	uniqueUsername := fmt.Sprintf("admin_%s", uniqueID)
	uniqueEmail := fmt.Sprintf("admin_%s@example.com", uniqueID)
	// Slug must also be unique - use the same uniqueID to ensure uniqueness
	uniqueSlug := fmt.Sprintf("admin-%s", uniqueID)

	user := &models.User{
		Username:     uniqueUsername,
		Slug:         uniqueSlug,
		Email:        uniqueEmail,
		PasswordHash: "$2a$10$examplehash",
		TokenVersion: 0,
		FirstName:    "Admin",
		LastName:     "User",
		Role:         "admin",
		IsActive:     true,
		IsVerified:   true,
		IsAdmin:      true,
	}

	if err := db.Create(user).Error; err != nil {
		return nil, err
	}

	return user, nil
}

// CreateTestTrack crée un track de test
func CreateTestTrack(db *gorm.DB, userID uuid.UUID) (*models.Track, error) {
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		Artist:   "Test Artist",
		Duration: 180, // 3 minutes
		FilePath: "uploads/test_track.mp3",
		FileSize: 1024 * 1024 * 5, // 5MB
		Format:   "mp3",
	}

	if err := db.Create(track).Error; err != nil {
		return nil, err
	}

	return track, nil
}

// CreateTestTrackWithCustomData crée un track de test avec des données personnalisées
func CreateTestTrackWithCustomData(db *gorm.DB, userID uuid.UUID, title, artist string) (*models.Track, error) {
	track := &models.Track{
		UserID:   userID,
		Title:    title,
		Artist:   artist,
		Duration: 180,
		FilePath: "uploads/test_track.mp3",
		FileSize: 1024 * 1024 * 5,
		Format:   "mp3",
	}

	if err := db.Create(track).Error; err != nil {
		return nil, err
	}

	return track, nil
}

// CreateTestPlaylist crée une playlist de test
func CreateTestPlaylist(db *gorm.DB, userID uuid.UUID) (*models.Playlist, error) {
	playlist := &models.Playlist{
		UserID:      userID,
		Title:       "Test Playlist",
		Description: "A test playlist",
	}

	if err := db.Create(playlist).Error; err != nil {
		return nil, err
	}

	return playlist, nil
}

// CreateTestRoom crée une room de test
func CreateTestRoom(db *gorm.DB, createdBy uuid.UUID) (*models.Room, error) {
	room := &models.Room{
		Name:        "Test Room",
		Description: "A test room",
		Type:        "public",
		IsPrivate:   false,
		CreatedBy:   createdBy,
	}

	if err := db.Create(room).Error; err != nil {
		return nil, err
	}

	return room, nil
}

// CreateTestMessage crée un message de test
func CreateTestMessage(db *gorm.DB, roomID uuid.UUID, userID uuid.UUID, content string) (*models.Message, error) {
	message := &models.Message{
		RoomID:    roomID,
		UserID:    userID,
		Content:   content,
		Type:      "text",
		IsEdited:  false,
		IsDeleted: false,
	}

	if err := db.Create(message).Error; err != nil {
		return nil, err
	}

	return message, nil
}

// CreateTestSession crée une session de test
func CreateTestSession(db *gorm.DB, userID uuid.UUID) (*models.Session, error) {
	session := &models.Session{
		UserID:    userID,
		Token:     "test_hash_" + uuid.New().String(),
		IPAddress: "127.0.0.1",
		UserAgent: "test-agent",
		ExpiresAt: time.Now().Add(24 * time.Hour),
	}

	if err := db.Create(session).Error; err != nil {
		return nil, err
	}

	return session, nil
}

// CreateMultipleTestUsers crée plusieurs utilisateurs de test
func CreateMultipleTestUsers(db *gorm.DB, count int) ([]*models.User, error) {
	users := make([]*models.User, 0, count)

	for i := 1; i <= count; i++ {
		// Make username and email unique to avoid constraint violations when tests share the same DB
		uniqueID := strings.ReplaceAll(uuid.New().String()[:8], "-", "")
		uniqueUsername := fmt.Sprintf("testuser%d_%s", i, uniqueID)
		uniqueEmail := fmt.Sprintf("test%d_%s@example.com", i, uniqueID)
		uniqueSlug := fmt.Sprintf("testuser%d-%s", i, uniqueID)

		user := &models.User{
			Username:     uniqueUsername,
			Slug:         uniqueSlug,
			Email:        uniqueEmail,
			PasswordHash: "$2a$10$examplehash",
			TokenVersion: 0,
			FirstName:    "Test",
			LastName:     "User",
			Role:         "user",
			IsActive:     true,
			IsVerified:   true,
			IsAdmin:      false,
		}

		if err := db.Create(user).Error; err != nil {
			return nil, err
		}

		users = append(users, user)
	}

	return users, nil
}

// CreateMultipleTestTracks crée plusieurs tracks de test pour un créateur
func CreateMultipleTestTracks(db *gorm.DB, userID uuid.UUID, count int) ([]*models.Track, error) {
	tracks := make([]*models.Track, 0, count)

	for i := 1; i <= count; i++ {
		track := &models.Track{
			UserID:   userID,
			Title:    fmt.Sprintf("Test Track %d", i),
			Artist:   "Test Artist",
			Duration: 180,
			FilePath: "uploads/test_track.mp3",
			FileSize: 1024 * 1024,
			Format:   "mp3",
		}

		if err := db.Create(track).Error; err != nil {
			return nil, err
		}

		tracks = append(tracks, track)
	}

	return tracks, nil
}

// UserFactory crée des utilisateurs de test avec pattern Builder
type UserFactory struct {
	user *models.User
}

// NewUserFactory crée un nouveau factory avec valeurs par défaut
func NewUserFactory() *UserFactory {
	return &UserFactory{
		user: &models.User{
			Username:     "testuser",
			Email:        "test@example.com",
			PasswordHash: "$2a$10$examplehash",
			TokenVersion: 0,
			FirstName:    "Test",
			LastName:     "User",
			Role:         "user",
			IsActive:     true,
			IsVerified:   true,
			IsAdmin:      false,
		},
	}
}

// WithUsername définit le username
func (f *UserFactory) WithUsername(username string) *UserFactory {
	f.user.Username = username
	return f
}

// WithEmail définit l'email
func (f *UserFactory) WithEmail(email string) *UserFactory {
	f.user.Email = email
	return f
}

// WithRole définit le rôle
func (f *UserFactory) WithRole(role string) *UserFactory {
	f.user.Role = role
	if role == "admin" {
		f.user.IsAdmin = true
	}
	return f
}

// WithPasswordHash définit le hash du mot de passe
func (f *UserFactory) WithPasswordHash(hash string) *UserFactory {
	f.user.PasswordHash = hash
	return f
}

// WithFirstName définit le prénom
func (f *UserFactory) WithFirstName(firstName string) *UserFactory {
	f.user.FirstName = firstName
	return f
}

// WithLastName définit le nom
func (f *UserFactory) WithLastName(lastName string) *UserFactory {
	f.user.LastName = lastName
	return f
}

// WithIsActive définit si l'utilisateur est actif
func (f *UserFactory) WithIsActive(isActive bool) *UserFactory {
	f.user.IsActive = isActive
	return f
}

// WithIsVerified définit si l'utilisateur est vérifié
func (f *UserFactory) WithIsVerified(isVerified bool) *UserFactory {
	f.user.IsVerified = isVerified
	return f
}

// Build construit l'utilisateur sans sauvegarder
func (f *UserFactory) Build() *models.User {
	return f.user
}

// MustBuild construit et sauvegarde en DB
func (f *UserFactory) MustBuild(db *gorm.DB) *models.User {
	user := f.Build()
	if err := db.Create(user).Error; err != nil {
		panic(err)
	}
	return user
}

// TrackFactory crée des tracks de test avec pattern Builder
type TrackFactory struct {
	track *models.Track
}

// NewTrackFactory crée un nouveau factory avec valeurs par défaut
func NewTrackFactory(userID uuid.UUID) *TrackFactory {
	return &TrackFactory{
		track: &models.Track{
			UserID:   userID,
			Title:    "Test Track",
			Artist:   "Test Artist",
			Duration: 180, // 3 minutes
			FilePath: "uploads/test_track.mp3",
			FileSize: 1024 * 1024,
			Format:   "mp3",
		},
	}
}

// WithTitle définit le titre
func (f *TrackFactory) WithTitle(title string) *TrackFactory {
	f.track.Title = title
	return f
}

// WithArtist définit l'artiste
func (f *TrackFactory) WithArtist(artist string) *TrackFactory {
	f.track.Artist = artist
	return f
}

// WithDuration définit la durée en secondes
func (f *TrackFactory) WithDuration(duration int) *TrackFactory {
	f.track.Duration = duration
	return f
}

// Build construit le track sans sauvegarder
func (f *TrackFactory) Build() *models.Track {
	return f.track
}

// MustBuild construit et sauvegarde en DB
func (f *TrackFactory) MustBuild(db *gorm.DB) *models.Track {
	track := f.Build()
	if err := db.Create(track).Error; err != nil {
		panic(err)
	}
	return track
}

// PlaylistFactory crée des playlists de test avec pattern Builder
type PlaylistFactory struct {
	playlist *models.Playlist
}

// NewPlaylistFactory crée un nouveau factory avec valeurs par défaut
func NewPlaylistFactory(userID uuid.UUID) *PlaylistFactory {
	return &PlaylistFactory{
		playlist: &models.Playlist{
			UserID:      userID,
			Title:       "Test Playlist",
			Description: "A test playlist",
		},
	}
}

// WithName définit le titre (Mapped to Title)
func (f *PlaylistFactory) WithName(name string) *PlaylistFactory {
	f.playlist.Title = name
	return f
}

// WithDescription définit la description
func (f *PlaylistFactory) WithDescription(description string) *PlaylistFactory {
	f.playlist.Description = description
	return f
}

// Build construit la playlist sans sauvegarder
func (f *PlaylistFactory) Build() *models.Playlist {
	return f.playlist
}

// MustBuild construit et sauvegarde en DB
func (f *PlaylistFactory) MustBuild(db *gorm.DB) *models.Playlist {
	playlist := f.Build()
	if err := db.Create(playlist).Error; err != nil {
		panic(err)
	}
	return playlist
}

// CreateUsers crée N utilisateurs avec factories
func CreateUsers(db *gorm.DB, count int) []*models.User {
	users := make([]*models.User, count)
	for i := 0; i < count; i++ {
		factory := NewUserFactory().
			WithUsername(fmt.Sprintf("user%d", i)).
			WithEmail(fmt.Sprintf("user%d@example.com", i))
		users[i] = factory.MustBuild(db)
	}
	return users
}

// CreateTracks crée N tracks avec factories
func CreateTracks(db *gorm.DB, userID uuid.UUID, count int) []*models.Track {
	tracks := make([]*models.Track, count)
	for i := 0; i < count; i++ {
		factory := NewTrackFactory(userID).
			WithTitle(fmt.Sprintf("Test Track %d", i+1)).
			WithArtist(fmt.Sprintf("Test Artist %d", i+1))
		tracks[i] = factory.MustBuild(db)
	}
	return tracks
}
