package transactions

import (
	"context"
	"testing"

	"veza-backend-api/internal/core/social"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/testutils"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// setupTestDB crée une DB de test avec testcontainers
func setupTestDBForSocial(t *testing.T) *gorm.DB {
	ctx := context.Background()
	dsn, err := testutils.GetTestContainerDB(ctx)
	require.NoError(t, err, "Failed to setup test database")

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	require.NoError(t, err, "Failed to open database connection")

	// Note: Les migrations SQL sont déjà exécutées lors du démarrage du conteneur PostgreSQL
	// via postgres.WithInitScripts dans internal/testutils/setup.go
	// AutoMigrate n'est pas nécessaire et peut causer des erreurs avec les vues existantes

	// Créer les tables si elles n'existent pas (simplifié pour les tests)
	db.Exec(`
		CREATE TABLE IF NOT EXISTS likes (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			user_id UUID NOT NULL,
			target_id UUID NOT NULL,
			target_type VARCHAR(50) NOT NULL,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			UNIQUE(user_id, target_id, target_type)
		)
	`)
	db.Exec(`
		CREATE TABLE IF NOT EXISTS posts (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			user_id UUID NOT NULL,
			content TEXT,
			like_count INTEGER DEFAULT 0,
			comment_count INTEGER DEFAULT 0,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			deleted_at TIMESTAMP,
			updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
		)
	`)
	db.Exec(`
		CREATE TABLE IF NOT EXISTS comments (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			user_id UUID NOT NULL,
			target_id UUID NOT NULL,
			target_type VARCHAR(20) NOT NULL,
			content TEXT,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
			deleted_at TIMESTAMP
		)
	`)

	return db
}

// cleanupTestDB nettoie la DB entre les tests
func cleanupTestDBForSocial(_ *testing.T, db *gorm.DB) {
	db.Exec("TRUNCATE TABLE likes CASCADE")
	db.Exec("TRUNCATE TABLE comments CASCADE")
	db.Exec("TRUNCATE TABLE posts CASCADE")
	db.Exec("TRUNCATE TABLE users CASCADE")
}

// createTestUser crée un utilisateur de test
func createTestUserForSocial(t *testing.T, db *gorm.DB) *models.User {
	user := &models.User{
		Username:     "testuser_" + uuid.New().String()[:8],
		Slug:         "testuser_" + uuid.New().String()[:8], // Unique slug
		Email:        "test_" + uuid.New().String()[:8] + "@example.com",
		PasswordHash: "$2a$10$examplehash",
		IsActive:     true,
		IsVerified:   true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestPost crée un post de test
func createTestPost(t *testing.T, db *gorm.DB, userID uuid.UUID) uuid.UUID {
	// Scan UUID as string to avoid driver type conversion issues
	var postIDStr string
	err := db.Raw(`
		INSERT INTO posts (id, user_id, content, like_count, comment_count)
		VALUES (gen_random_uuid(), ?, ?, 0, 0)
		RETURNING id
	`, userID, "Test post content").Scan(&postIDStr).Error
	require.NoError(t, err)

	postID, err := uuid.Parse(postIDStr)
	require.NoError(t, err)
	return postID
}

// TestToggleLike_Success vérifie que le like fonctionne correctement
func TestToggleLike_Success(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user.ID)

	// Liker le post
	liked, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post")
	require.NoError(t, err, "ToggleLike should succeed")
	assert.True(t, liked, "Post should be liked")

	// Vérifier que le like existe
	var likeCount int64
	db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ? AND target_type = ?",
		user.ID, postID, "post").Scan(&likeCount)
	assert.Equal(t, int64(1), likeCount, "Like should exist")

	// Vérifier que le compteur est incrémenté
	var postLikeCount int
	db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount)
	assert.Equal(t, 1, postLikeCount, "Post like_count should be 1")
}

// TestToggleLike_Unlike vérifie que l'unlike fonctionne correctement
func TestToggleLike_Unlike(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user.ID)

	// Liker d'abord
	liked, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post")
	require.NoError(t, err)
	assert.True(t, liked)

	// Unliker
	liked, err = socialService.ToggleLike(context.Background(), user.ID, postID, "post")
	require.NoError(t, err, "ToggleLike (unlike) should succeed")
	assert.False(t, liked, "Post should be unliked")

	// Vérifier que le like n'existe plus
	var likeCount int64
	db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ? AND target_type = ?",
		user.ID, postID, "post").Scan(&likeCount)
	assert.Equal(t, int64(0), likeCount, "Like should be removed")

	// Vérifier que le compteur est décrémenté
	var postLikeCount int
	db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount)
	assert.Equal(t, 0, postLikeCount, "Post like_count should be 0")
}

// TestToggleLike_RollbackOnError vérifie le rollback si une erreur survient
func TestToggleLike_RollbackOnError(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user.ID)

	// Supprimer le post pour forcer une erreur lors de l'UPDATE du compteur
	db.Exec("DELETE FROM posts WHERE id = ?", postID)

	// Tenter de liker (devrait échouer car le post n'existe plus)
	_, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post")
	require.Error(t, err, "ToggleLike should fail")

	// Vérifier qu'aucun like n'a été créé (rollback)
	var likeCount int64
	db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ?",
		user.ID, postID).Scan(&likeCount)
	assert.Equal(t, int64(0), likeCount, "No like should be created on error")
}

// TestToggleLike_Coherence vérifie la cohérence entre likes et compteurs
func TestToggleLike_Coherence(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user1 := createTestUserForSocial(t, db)
	user2 := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user1.ID)

	// User1 like
	_, err := socialService.ToggleLike(context.Background(), user1.ID, postID, "post")
	require.NoError(t, err)

	// User2 like
	_, err = socialService.ToggleLike(context.Background(), user2.ID, postID, "post")
	require.NoError(t, err)

	// Vérifier la cohérence
	var actualLikeCount int64
	db.Raw("SELECT COUNT(*) FROM likes WHERE target_id = ? AND target_type = ?",
		postID, "post").Scan(&actualLikeCount)

	var postLikeCount int
	db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount)

	assert.Equal(t, int64(postLikeCount), actualLikeCount, "Like count should match actual likes")
	assert.Equal(t, int64(2), actualLikeCount, "Should have 2 likes")
}

// TestAddComment_Success vérifie que l'ajout de commentaire fonctionne
func TestAddComment_Success(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user.ID)

	// Ajouter un commentaire
	comment, err := socialService.AddComment(
		context.Background(),
		user.ID,
		postID,
		"post",
		"Test comment",
	)
	require.NoError(t, err, "AddComment should succeed")
	require.NotNil(t, comment, "Comment should be created")
	assert.Equal(t, "Test comment", comment.Content, "Comment content should match")

	// Vérifier que le commentaire existe
	var commentCount int64
	db.Raw("SELECT COUNT(*) FROM comments WHERE user_id = ? AND target_id = ? AND target_type = ?",
		user.ID, postID, "post").Scan(&commentCount)
	assert.Equal(t, int64(1), commentCount, "Comment should exist")

	// Vérifier que le compteur est incrémenté
	var postCommentCount int
	db.Raw("SELECT comment_count FROM posts WHERE id = ?", postID).Scan(&postCommentCount)
	assert.Equal(t, 1, postCommentCount, "Post comment_count should be 1")
}

// TestAddComment_RollbackOnError vérifie le rollback si une erreur survient
func TestAddComment_RollbackOnError(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user.ID)

	// Supprimer le post pour forcer une erreur lors de l'UPDATE du compteur
	db.Exec("DELETE FROM posts WHERE id = ?", postID)

	// Tenter d'ajouter un commentaire (devrait échouer)
	_, err := socialService.AddComment(
		context.Background(),
		user.ID,
		postID,
		"post",
		"Test comment",
	)
	require.Error(t, err, "AddComment should fail")

	// Vérifier qu'aucun commentaire n'a été créé (rollback)
	var commentCount int64
	db.Raw("SELECT COUNT(*) FROM comments WHERE user_id = ? AND target_id = ?",
		user.ID, postID).Scan(&commentCount)
	assert.Equal(t, int64(0), commentCount, "No comment should be created on error")
}

// TestAddComment_Coherence vérifie la cohérence entre comments et compteurs
func TestAddComment_Coherence(t *testing.T) {
	db := setupTestDBForSocial(t)
	defer cleanupTestDBForSocial(t, db)

	logger := zaptest.NewLogger(t)
	socialService := social.NewService(db, logger)

	user1 := createTestUserForSocial(t, db)
	user2 := createTestUserForSocial(t, db)
	postID := createTestPost(t, db, user1.ID)

	// User1 commente
	_, err := socialService.AddComment(context.Background(), user1.ID, postID, "post", "Comment 1")
	require.NoError(t, err)

	// User2 commente
	_, err = socialService.AddComment(context.Background(), user2.ID, postID, "post", "Comment 2")
	require.NoError(t, err)

	// Vérifier la cohérence
	var actualCommentCount int64
	db.Raw("SELECT COUNT(*) FROM comments WHERE target_id = ? AND target_type = ?",
		postID, "post").Scan(&actualCommentCount)

	var postCommentCount int
	db.Raw("SELECT comment_count FROM posts WHERE id = ?", postID).Scan(&postCommentCount)

	assert.Equal(t, int64(postCommentCount), actualCommentCount, "Comment count should match actual comments")
	assert.Equal(t, int64(2), actualCommentCount, "Should have 2 comments")
}
