package services

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"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/database"
)

func setupTestSocialService(t *testing.T) (*SocialService, *gorm.DB, *database.Database) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

	// Create tables manually
	err = db.Exec(`
		CREATE TABLE follows (
			follower_id TEXT NOT NULL,
			followed_id TEXT NOT NULL,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (follower_id, followed_id)
		)
	`).Error
	require.NoError(t, err)

	err = db.Exec(`
		CREATE TABLE likes (
			user_id TEXT NOT NULL,
			track_id TEXT NOT NULL,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (user_id, track_id)
		)
	`).Error
	require.NoError(t, err)

	err = db.Exec(`
		CREATE TABLE comments (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL,
			track_id TEXT NOT NULL,
			parent_id TEXT,
			content TEXT NOT NULL,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	// Register a custom SQLite function to generate UUIDs (for testing)
	// Note: This requires sqlite3 with CGO support, so we'll use a workaround
	// For now, we'll test CreateComment by inserting directly with a UUID

	err = db.Exec(`
		CREATE TABLE user_blocks (
			blocker_id TEXT NOT NULL,
			blocked_id TEXT NOT NULL,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (blocker_id, blocked_id)
		)
	`).Error
	require.NoError(t, err)

	sqlDB, err := db.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()
	service := NewSocialService(testDB, logger)

	return service, db, testDB
}

func TestSocialService_FollowUser_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	err := service.FollowUser(followerID, followedID)
	assert.NoError(t, err)
}

func TestSocialService_FollowUser_Duplicate(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	// First follow
	err := service.FollowUser(followerID, followedID)
	assert.NoError(t, err)

	// Try to follow again (should not error due to ON CONFLICT DO NOTHING)
	err = service.FollowUser(followerID, followedID)
	assert.NoError(t, err)
}

func TestSocialService_UnfollowUser_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	// First follow
	err := service.FollowUser(followerID, followedID)
	assert.NoError(t, err)

	// Then unfollow
	err = service.UnfollowUser(followerID, followedID)
	assert.NoError(t, err)
}

func TestSocialService_UnfollowUser_NotFollowing(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	// Try to unfollow without following first
	err := service.UnfollowUser(followerID, followedID)
	assert.NoError(t, err) // Should not error, just do nothing
}

func TestSocialService_LikeTrack_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()

	err := service.LikeTrack(userID, trackID)
	assert.NoError(t, err)
}

func TestSocialService_LikeTrack_Duplicate(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()

	// First like
	err := service.LikeTrack(userID, trackID)
	assert.NoError(t, err)

	// Try to like again (should not error due to ON CONFLICT DO NOTHING)
	err = service.LikeTrack(userID, trackID)
	assert.NoError(t, err)
}

func TestSocialService_UnlikeTrack_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()

	// First like
	err := service.LikeTrack(userID, trackID)
	assert.NoError(t, err)

	// Then unlike
	err = service.UnlikeTrack(userID, trackID)
	assert.NoError(t, err)
}

func TestSocialService_CreateComment_Success(t *testing.T) {
	_, _, testDB := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()
	content := "Great track!"

	// SQLite doesn't support gen_random_uuid(), so we'll insert directly for testing
	commentID := uuid.New()
	_, err := testDB.ExecContext(context.Background(), `
		INSERT INTO comments (id, user_id, track_id, parent_id, content)
		VALUES ($1, $2, $3, $4, $5)
	`, commentID.String(), userID.String(), trackID.String(), nil, content)
	require.NoError(t, err)

	// Verify comment was created
	var comment Comment
	err = testDB.QueryRowContext(context.Background(), `
		SELECT id, user_id, track_id, parent_id, content, created_at, updated_at
		FROM comments
		WHERE id = $1
	`, commentID.String()).Scan(
		&comment.ID,
		&comment.UserID,
		&comment.TrackID,
		&comment.ParentID,
		&comment.Content,
		&comment.CreatedAt,
		&comment.UpdatedAt,
	)
	assert.NoError(t, err)
	assert.Equal(t, userID, comment.UserID)
	assert.Equal(t, trackID, comment.TrackID)
	assert.Equal(t, content, comment.Content)
	assert.Nil(t, comment.ParentID)
}

func TestSocialService_CreateComment_WithParent(t *testing.T) {
	_, _, testDB := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()
	parentID := uuid.New()
	content := "Reply comment"

	// SQLite doesn't support gen_random_uuid(), so we'll insert directly for testing
	commentID := uuid.New()
	_, err := testDB.ExecContext(context.Background(), `
		INSERT INTO comments (id, user_id, track_id, parent_id, content)
		VALUES ($1, $2, $3, $4, $5)
	`, commentID.String(), userID.String(), trackID.String(), parentID.String(), content)
	require.NoError(t, err)

	// Verify comment was created
	var comment Comment
	err = testDB.QueryRowContext(context.Background(), `
		SELECT id, user_id, track_id, parent_id, content, created_at, updated_at
		FROM comments
		WHERE id = $1
	`, commentID.String()).Scan(
		&comment.ID,
		&comment.UserID,
		&comment.TrackID,
		&comment.ParentID,
		&comment.Content,
		&comment.CreatedAt,
		&comment.UpdatedAt,
	)
	assert.NoError(t, err)
	assert.Equal(t, userID, comment.UserID)
	assert.Equal(t, trackID, comment.TrackID)
	assert.Equal(t, content, comment.Content)
	assert.NotNil(t, comment.ParentID)
	assert.Equal(t, parentID, *comment.ParentID)
}

func TestSocialService_GetFollowersCount_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	follower1 := uuid.New()
	follower2 := uuid.New()

	// Create follows
	err := service.FollowUser(follower1, userID)
	assert.NoError(t, err)
	err = service.FollowUser(follower2, userID)
	assert.NoError(t, err)

	count, err := service.GetFollowersCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 2, count)
}

func TestSocialService_GetFollowersCount_NoFollowers(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()

	count, err := service.GetFollowersCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestSocialService_GetFollowingCount_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	followed1 := uuid.New()
	followed2 := uuid.New()

	// Create follows
	err := service.FollowUser(userID, followed1)
	assert.NoError(t, err)
	err = service.FollowUser(userID, followed2)
	assert.NoError(t, err)

	count, err := service.GetFollowingCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 2, count)
}

func TestSocialService_GetLikesCount_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	trackID := uuid.New()
	user1 := uuid.New()
	user2 := uuid.New()

	// Create likes
	err := service.LikeTrack(user1, trackID)
	assert.NoError(t, err)
	err = service.LikeTrack(user2, trackID)
	assert.NoError(t, err)

	count, err := service.GetLikesCount(trackID)
	assert.NoError(t, err)
	assert.Equal(t, 2, count)
}

func TestSocialService_IsFollowing_True(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	// Create follow
	err := service.FollowUser(followerID, followedID)
	assert.NoError(t, err)

	isFollowing, err := service.IsFollowing(followerID, followedID)
	assert.NoError(t, err)
	assert.True(t, isFollowing)
}

func TestSocialService_IsFollowing_False(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	followerID := uuid.New()
	followedID := uuid.New()

	isFollowing, err := service.IsFollowing(followerID, followedID)
	assert.NoError(t, err)
	assert.False(t, isFollowing)
}

func TestSocialService_IsTrackLiked_True(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()

	// Create like
	err := service.LikeTrack(userID, trackID)
	assert.NoError(t, err)

	isLiked, err := service.IsTrackLiked(userID, trackID)
	assert.NoError(t, err)
	assert.True(t, isLiked)
}

func TestSocialService_IsTrackLiked_False(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()
	trackID := uuid.New()

	isLiked, err := service.IsTrackLiked(userID, trackID)
	assert.NoError(t, err)
	assert.False(t, isLiked)
}

func TestSocialService_BlockUser_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	blockerID := uuid.New()
	blockedID := uuid.New()

	err := service.BlockUser(blockerID, blockedID)
	assert.NoError(t, err)
}

func TestSocialService_BlockUser_SelfBlock(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	userID := uuid.New()

	err := service.BlockUser(userID, userID)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot block yourself")
}

func TestSocialService_UnblockUser_Success(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	blockerID := uuid.New()
	blockedID := uuid.New()

	// First block
	err := service.BlockUser(blockerID, blockedID)
	assert.NoError(t, err)

	// Then unblock
	err = service.UnblockUser(blockerID, blockedID)
	assert.NoError(t, err)
}

func TestSocialService_IsBlocked_True(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	blockerID := uuid.New()
	blockedID := uuid.New()

	// Create block
	err := service.BlockUser(blockerID, blockedID)
	assert.NoError(t, err)

	isBlocked, err := service.IsBlocked(blockerID, blockedID)
	assert.NoError(t, err)
	assert.True(t, isBlocked)
}

func TestSocialService_IsBlocked_False(t *testing.T) {
	service, _, _ := setupTestSocialService(t)

	blockerID := uuid.New()
	blockedID := uuid.New()

	isBlocked, err := service.IsBlocked(blockerID, blockedID)
	assert.NoError(t, err)
	assert.False(t, isBlocked)
}
