package services

import (
	"context"
	"fmt"
	"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 setupTestTrackLikeService(t *testing.T) (*TrackLikeService, *gorm.DB, func()) {
	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

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

	// Setup logger
	logger := zap.NewNop()

	// Setup service
	service := NewTrackLikeService(db, logger)

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

	return service, db, cleanup
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:    userID,
		Title:     "Test Track",
		FilePath:  "/test/track.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 0,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Like track
	err = service.LikeTrack(ctx, userID, track.ID)
	assert.NoError(t, err)

	// Verify like was created
	var like models.TrackLike
	err = db.Where("user_id = ? AND track_id = ?", userID, track.ID).First(&like).Error
	assert.NoError(t, err)
	assert.Equal(t, userID, like.UserID)
	assert.Equal(t, track.ID, like.TrackID)

	// Verify track like_count was updated
	var updatedTrack models.Track
	err = db.First(&updatedTrack, "id = ?", track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, int64(1), updatedTrack.LikeCount)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:    userID,
		Title:     "Test Track",
		FilePath:  "/test/track.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 0,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Like track first time
	err = service.LikeTrack(ctx, userID, track.ID)
	assert.NoError(t, err)

	// Try to like again (should be idempotent)
	err = service.LikeTrack(ctx, userID, track.ID)
	assert.NoError(t, err)

	// Verify only one like exists
	var count int64
	db.Model(&models.TrackLike{}).Where("user_id = ? AND track_id = ?", userID, track.ID).Count(&count)
	assert.Equal(t, int64(1), count)
}

func TestTrackLikeService_LikeTrack_TrackNotFound(t *testing.T) {
	service, _, cleanup := setupTestTrackLikeService(t)
	defer cleanup()

	ctx := context.Background()

	// Try to like non-existent track
	err := service.LikeTrack(ctx, uuid.New(), uuid.New())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "track not found")
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:    userID,
		Title:     "Test Track",
		FilePath:  "/test/track.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 1,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create like
	like := &models.TrackLike{
		UserID:  userID,
		TrackID: track.ID,
	}
	err = db.Create(like).Error
	require.NoError(t, err)

	// Unlike track
	err = service.UnlikeTrack(ctx, userID, track.ID)
	assert.NoError(t, err)

	// Verify like was deleted
	var count int64
	db.Model(&models.TrackLike{}).Where("user_id = ? AND track_id = ?", userID, track.ID).Count(&count)
	assert.Equal(t, int64(0), count)

	// Verify track like_count was updated
	var updatedTrack models.Track
	err = db.First(&updatedTrack, "id = ?", track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, int64(0), updatedTrack.LikeCount)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:    userID,
		Title:     "Test Track",
		FilePath:  "/test/track.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 0,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Try to unlike (should be idempotent)
	err = service.UnlikeTrack(ctx, userID, track.ID)
	assert.NoError(t, err)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create like
	like := &models.TrackLike{
		UserID:  userID,
		TrackID: track.ID,
	}
	err = db.Create(like).Error
	require.NoError(t, err)

	// Check if liked
	isLiked, err := service.IsLiked(ctx, userID, track.ID)
	assert.NoError(t, err)
	assert.True(t, isLiked)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Check if liked (should be false)
	isLiked, err := service.IsLiked(ctx, userID, track.ID)
	assert.NoError(t, err)
	assert.False(t, isLiked)
}

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

	ctx := context.Background()

	userID1 := uuid.New()
	userID2 := uuid.New()

	// Create test users
	user1 := &models.User{
		ID:       userID1,
		Username: "testuser1",
		Email:    "test1@example.com",
		IsActive: true,
	}
	err := db.Create(user1).Error
	require.NoError(t, err)

	user2 := &models.User{
		ID:       userID2,
		Username: "testuser2",
		Email:    "test2@example.com",
		IsActive: true,
	}
	err = db.Create(user2).Error
	require.NoError(t, err)

	// Create test track
	track := &models.Track{
		UserID:   userID1,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create likes
	like1 := &models.TrackLike{UserID: userID1, TrackID: track.ID}
	err = db.Create(like1).Error
	require.NoError(t, err)

	like2 := &models.TrackLike{UserID: userID2, TrackID: track.ID}
	err = db.Create(like2).Error
	require.NoError(t, err)

	// Get likes count
	count, err := service.GetTrackLikesCount(ctx, track.ID)
	assert.NoError(t, err)
	assert.Equal(t, int64(2), count)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Get likes count (should be 0)
	count, err := service.GetTrackLikesCount(ctx, track.ID)
	assert.NoError(t, err)
	assert.Equal(t, int64(0), count)
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Track 1",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Track 2",
		FilePath: "/test/track2.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Create likes
	like1 := &models.TrackLike{UserID: userID, TrackID: track1.ID}
	err = db.Create(like1).Error
	require.NoError(t, err)

	like2 := &models.TrackLike{UserID: userID, TrackID: track2.ID}
	err = db.Create(like2).Error
	require.NoError(t, err)

	// Get user liked tracks
	tracks, err := service.GetUserLikedTracks(ctx, userID, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, 2, len(tracks))
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	for i := 1; i <= 5; i++ {
		track := &models.Track{
			UserID:   userID,
			Title:    fmt.Sprintf("Track %d", i),
			FilePath: fmt.Sprintf("/test/track%d.mp3", i),
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err = db.Create(track).Error
		require.NoError(t, err)

		like := &models.TrackLike{UserID: userID, TrackID: track.ID}
		err = db.Create(like).Error
		require.NoError(t, err)
	}

	// Get user liked tracks with limit
	tracks, err := service.GetUserLikedTracks(ctx, userID, 3, 0)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(tracks))
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	for i := 1; i <= 5; i++ {
		track := &models.Track{
			UserID:   userID,
			Title:    fmt.Sprintf("Track %d", i),
			FilePath: fmt.Sprintf("/test/track%d.mp3", i),
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err = db.Create(track).Error
		require.NoError(t, err)

		like := &models.TrackLike{UserID: userID, TrackID: track.ID}
		err = db.Create(like).Error
		require.NoError(t, err)
	}

	// Get user liked tracks with offset
	tracks, err := service.GetUserLikedTracks(ctx, userID, 3, 2)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(tracks))
}

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

	ctx := context.Background()

	userID := uuid.New()
	// Create test user
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	for i := 1; i <= 3; i++ {
		track := &models.Track{
			UserID:   userID,
			Title:    fmt.Sprintf("Track %d", i),
			FilePath: fmt.Sprintf("/test/track%d.mp3", i),
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err = db.Create(track).Error
		require.NoError(t, err)

		like := &models.TrackLike{UserID: userID, TrackID: track.ID}
		err = db.Create(like).Error
		require.NoError(t, err)
	}

	// Get user liked tracks count
	count, err := service.GetUserLikedTracksCount(ctx, userID)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), count)
}
