package services

import (
	"context"
	"github.com/google/uuid"
	"testing"

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

func setupTestPlaylistFollowService(t *testing.T) (*PlaylistFollowService, *gorm.DB, func()) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate
	err = db.AutoMigrate(&models.User{}, &models.Playlist{}, &models.PlaylistFollow{})
	require.NoError(t, err)

	logger := zap.NewNop()
	service := NewPlaylistFollowService(db, logger)

	cleanup := func() {
		// Database will be closed automatically
	}

	return service, db, cleanup
}

func TestPlaylistFollowService_FollowPlaylist(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Test follow
	err := service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)

	// Verify follow was created
	var follow models.PlaylistFollow
	err = db.Where("user_id = ? AND playlist_id = ?", user2.ID, playlist.ID).First(&follow).Error
	assert.NoError(t, err)
	assert.Equal(t, user2.ID, follow.UserID)
	assert.Equal(t, playlist.ID, follow.PlaylistID)

	// Verify follower count was updated
	var updatedPlaylist models.Playlist
	db.First(&updatedPlaylist, playlist.ID)
	assert.Equal(t, 1, updatedPlaylist.FollowerCount)
}

func TestPlaylistFollowService_FollowPlaylist_OwnPlaylist(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test user
	user := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Test follow own playlist (should fail)
	err := service.FollowPlaylist(ctx, user.ID, playlist.ID)
	assert.Error(t, err)
	assert.Equal(t, "cannot follow own playlist", err.Error())
}

func TestPlaylistFollowService_FollowPlaylist_NotFound(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test user
	user := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user).Error)

	// Test follow non-existent playlist
	err := service.FollowPlaylist(ctx, user.ID, uuid.New())
	assert.Error(t, err)
	assert.Equal(t, "playlist not found", err.Error())
}

func TestPlaylistFollowService_FollowPlaylist_Idempotent(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Follow twice
	err := service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)
	err = service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err) // Should be idempotent

	// Verify only one follow exists
	var count int64
	db.Model(&models.PlaylistFollow{}).Where("user_id = ? AND playlist_id = ?", user2.ID, playlist.ID).Count(&count)
	assert.Equal(t, int64(1), count)
}

func TestPlaylistFollowService_UnfollowPlaylist(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Follow first
	err := service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	require.NoError(t, err)

	// Unfollow
	err = service.UnfollowPlaylist(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)

	// Verify follow was deleted
	var count int64
	db.Model(&models.PlaylistFollow{}).Where("user_id = ? AND playlist_id = ? AND deleted_at IS NULL", user2.ID, playlist.ID).Count(&count)
	assert.Equal(t, int64(0), count)

	// Verify follower count was updated
	var updatedPlaylist models.Playlist
	db.First(&updatedPlaylist, playlist.ID)
	assert.Equal(t, 0, updatedPlaylist.FollowerCount)
}

func TestPlaylistFollowService_UnfollowPlaylist_Idempotent(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Unfollow without following first (should be idempotent)
	err := service.UnfollowPlaylist(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)
}

func TestPlaylistFollowService_IsFollowing(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Check before following
	isFollowing, err := service.IsFollowing(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)
	assert.False(t, isFollowing)

	// Follow
	err = service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	require.NoError(t, err)

	// Check after following
	isFollowing, err = service.IsFollowing(ctx, user2.ID, playlist.ID)
	assert.NoError(t, err)
	assert.True(t, isFollowing)
}

func TestPlaylistFollowService_GetPlaylistFollowersCount(t *testing.T) {
	service, db, cleanup := setupTestPlaylistFollowService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test users
	user1 := &models.User{
		Username:     "user1",
		Email:        "user1@example.com",
		PasswordHash: "hash1",
		Slug:         "user1",
		IsActive:     true,
	}
	user2 := &models.User{
		Username:     "user2",
		Email:        "user2@example.com",
		PasswordHash: "hash2",
		Slug:         "user2",
		IsActive:     true,
	}
	user3 := &models.User{
		Username:     "user3",
		Email:        "user3@example.com",
		PasswordHash: "hash3",
		Slug:         "user3",
		IsActive:     true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)
	require.NoError(t, db.Create(user3).Error)

	// Create test playlist
	playlist := &models.Playlist{
		UserID:        user1.ID,
		Title:         "Test Playlist",
		Description:   "A test playlist",
		IsPublic:      true,
		FollowerCount: 0,
	}
	require.NoError(t, db.Create(playlist).Error)

	// Check count before following
	count, err := service.GetPlaylistFollowersCount(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Equal(t, int64(0), count)

	// Follow by user2
	err = service.FollowPlaylist(ctx, user2.ID, playlist.ID)
	require.NoError(t, err)

	// Follow by user3
	err = service.FollowPlaylist(ctx, user3.ID, playlist.ID)
	require.NoError(t, err)

	// Check count after following
	count, err = service.GetPlaylistFollowersCount(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Equal(t, int64(2), count)
}
