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 setupTestCommentService(t *testing.T) (*CommentService, *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.TrackComment{})
	require.NoError(t, err)

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

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

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

	return service, db, cleanup
}

func TestCommentService_CreateComment_Success(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 comment
	comment, err := service.CreateComment(ctx, track.ID, userID, "Great track!", 0.0, nil)
	assert.NoError(t, err)
	assert.NotNil(t, comment)
	assert.Equal(t, track.ID, comment.TrackID)
	assert.Equal(t, userID, comment.UserID)
	assert.Equal(t, "Great track!", comment.Content)
	assert.Nil(t, comment.ParentID)
	assert.False(t, comment.IsEdited)
	assert.NotNil(t, comment.User)
	assert.Equal(t, "testuser", comment.User.Username)
}

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

	ctx := context.Background()
	userID := uuid.New()

	// Try to create comment on non-existent track
	comment, err := service.CreateComment(ctx, uuid.New(), userID, "Great track!", 0.0, nil)
	assert.Error(t, err)
	assert.Nil(t, comment)
	assert.ErrorIs(t, err, ErrTrackNotFound)
}

func TestCommentService_CreateComment_WithParent(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 parent comment
	parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil)
	require.NoError(t, err)

	// Create reply
	reply, err := service.CreateComment(ctx, track.ID, userID, "Reply to parent", 0.0, &parentComment.ID)
	assert.NoError(t, err)
	assert.NotNil(t, reply)
	assert.NotNil(t, reply.ParentID)
	assert.Equal(t, parentComment.ID, *reply.ParentID)
	assert.Equal(t, "Reply to parent", reply.Content)
}

func TestCommentService_CreateComment_ParentNotFound(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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)

	// Try to create reply with non-existent parent
	parentID := uuid.New()
	reply, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentID)
	assert.Error(t, err)
	assert.Nil(t, reply)
	assert.ErrorIs(t, err, ErrParentCommentNotFound)
}

func TestCommentService_GetComments_Success(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 multiple comments
	for i := 0; i < 5; i++ {
		_, err := service.CreateComment(ctx, track.ID, userID, "Comment "+string(rune('0'+i)), 0.0, nil)
		require.NoError(t, err)
	}

	// Get comments
	comments, total, err := service.GetComments(ctx, track.ID, 1, 10)
	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, comments, 5)
	assert.NotNil(t, comments[0].User)
}

func TestCommentService_GetComments_Pagination(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 multiple comments
	for i := 0; i < 10; i++ {
		_, err := service.CreateComment(ctx, track.ID, userID, "Comment", 0.0, nil)
		require.NoError(t, err)
	}

	// Get first page
	comments, total, err := service.GetComments(ctx, track.ID, 1, 3)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total)
	assert.Len(t, comments, 3)

	// Get second page
	comments2, total2, err := service.GetComments(ctx, track.ID, 2, 3)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total2)
	assert.Len(t, comments2, 3)
}

func TestCommentService_GetComments_OnlyRootComments(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 root comment
	rootComment, err := service.CreateComment(ctx, track.ID, userID, "Root comment", 0.0, nil)
	require.NoError(t, err)

	// Create reply (should not appear in GetComments)
	_, err = service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &rootComment.ID)
	require.NoError(t, err)

	// Get comments (should only return root comment)
	comments, total, err := service.GetComments(ctx, track.ID, 1, 10)
	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, comments, 1)
	assert.Equal(t, rootComment.ID, comments[0].ID)
}

func TestCommentService_UpdateComment_Success(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 comment
	comment, err := service.CreateComment(ctx, track.ID, userID, "Original content", 0.0, nil)
	require.NoError(t, err)

	// Update comment
	updatedComment, err := service.UpdateComment(ctx, comment.ID, userID, "Updated content")
	assert.NoError(t, err)
	assert.NotNil(t, updatedComment)
	assert.Equal(t, "Updated content", updatedComment.Content)
	assert.True(t, updatedComment.IsEdited)
}

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

	ctx := context.Background()
	userID := uuid.New()

	// Try to update non-existent comment
	comment, err := service.UpdateComment(ctx, uuid.New(), userID, "Updated content")
	assert.Error(t, err)
	assert.Nil(t, comment)
	assert.ErrorIs(t, err, ErrCommentNotFound)
}

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

	ctx := context.Background()

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

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

	// Create test track
	track := &models.Track{
		UserID:   user1ID,
		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 comment with user1
	comment, err := service.CreateComment(ctx, track.ID, user1ID, "Original content", 0.0, nil)
	require.NoError(t, err)

	// Try to update with user2 (should fail)
	updatedComment, err := service.UpdateComment(ctx, comment.ID, user2ID, "Updated content")
	assert.Error(t, err)
	assert.Nil(t, updatedComment)
	assert.ErrorIs(t, err, ErrForbidden)
}

func TestCommentService_DeleteComment_Success(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 comment
	comment, err := service.CreateComment(ctx, track.ID, userID, "Comment to delete", 0.0, nil)
	require.NoError(t, err)

	// Delete comment
	err = service.DeleteComment(ctx, comment.ID, userID, false)
	assert.NoError(t, err)

	// Verify comment is soft deleted
	var deletedComment models.TrackComment
	err = db.First(&deletedComment, comment.ID).Error
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

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

	ctx := context.Background()
	userID := uuid.New()

	// Try to delete non-existent comment
	err := service.DeleteComment(ctx, uuid.New(), userID, false)
	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrCommentNotFound)
}

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

	ctx := context.Background()

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

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

	// Create test track
	track := &models.Track{
		UserID:   user1ID,
		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 comment with user1
	comment, err := service.CreateComment(ctx, track.ID, user1ID, "Comment", 0.0, nil)
	require.NoError(t, err)

	// Try to delete with user2 (should fail)
	err = service.DeleteComment(ctx, comment.ID, user2ID, false)
	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrForbidden)
}

func TestCommentService_GetReplies_Success(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 parent comment
	parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil)
	require.NoError(t, err)

	// Create multiple replies
	for i := 0; i < 5; i++ {
		_, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentComment.ID)
		require.NoError(t, err)
	}

	// Get replies
	replies, total, err := service.GetReplies(ctx, parentComment.ID, 1, 10)
	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, replies, 5)
	assert.NotNil(t, replies[0].User)
}

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

	ctx := context.Background()

	// Try to get replies for non-existent parent
	replies, total, err := service.GetReplies(ctx, uuid.New(), 1, 10)
	assert.Error(t, err)
	assert.Nil(t, replies)
	assert.Equal(t, int64(0), total)
	assert.ErrorIs(t, err, ErrParentCommentNotFound)
}

func TestCommentService_GetReplies_Pagination(t *testing.T) {
	service, db, cleanup := setupTestCommentService(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 parent comment
	parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil)
	require.NoError(t, err)

	// Create multiple replies
	for i := 0; i < 10; i++ {
		_, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentComment.ID)
		require.NoError(t, err)
	}

	// Get first page
	replies, total, err := service.GetReplies(ctx, parentComment.ID, 1, 3)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total)
	assert.Len(t, replies, 3)

	// Get second page
	replies2, total2, err := service.GetReplies(ctx, parentComment.ID, 2, 3)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total2)
	assert.Len(t, replies2, 3)
}
