package sql_test

import (
	"context"
	"testing"
	"tercul/internal/app/analytics"
	"tercul/internal/data/sql"
	"tercul/internal/domain"
	"tercul/internal/platform/config"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// newTestAnalyticsRepoWithSQLite sets up an in-memory SQLite database for testing.
func newTestAnalyticsRepoWithSQLite(t *testing.T) (analytics.Repository, *gorm.DB) {
	// Using "file::memory:?cache=shared" to ensure the in-memory database is shared across connections in the same process.
	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	require.NoError(t, err)

	// Auto-migrate the necessary schemas
	err = db.AutoMigrate(
		&domain.Work{},
		&domain.WorkStats{},
		&domain.Translation{},
		&domain.TranslationStats{},
		&domain.Trending{},
		&domain.UserEngagement{},
		&domain.User{},
	)
	require.NoError(t, err)

	cfg := &config.Config{}
	repo := sql.NewAnalyticsRepository(db, cfg)

	// Clean up the database after the test
	t.Cleanup(func() {
		sqlDB, err := db.DB()
		require.NoError(t, err)
		err = sqlDB.Close()
		require.NoError(t, err)
	})

	return repo, db
}

func TestAnalyticsRepository_IncrementWorkCounter(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup: Create a work to associate stats with
	work := domain.Work{Title: "Test Work"}
	require.NoError(t, db.Create(&work).Error)

	t.Run("creates_new_stats_if_not_exist", func(t *testing.T) {
		err := repo.IncrementWorkCounter(ctx, work.ID, "views", 5)
		require.NoError(t, err)

		var stats domain.WorkStats
		err = db.Where("work_id = ?", work.ID).First(&stats).Error
		require.NoError(t, err)
		assert.Equal(t, int64(5), stats.Views)
	})

	t.Run("increments_existing_stats", func(t *testing.T) {
		// Increment again
		err := repo.IncrementWorkCounter(ctx, work.ID, "views", 3)
		require.NoError(t, err)

		var stats domain.WorkStats
		err = db.Where("work_id = ?", work.ID).First(&stats).Error
		require.NoError(t, err)
		assert.Equal(t, int64(8), stats.Views) // 5 + 3
	})

	t.Run("invalid_field", func(t *testing.T) {
		err := repo.IncrementWorkCounter(ctx, work.ID, "invalid_field", 1)
		assert.Error(t, err)
	})
}

func TestAnalyticsRepository_GetTrendingWorks(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup: Create some works and trending data
	work1 := domain.Work{Title: "Trending Work 1"}
	work2 := domain.Work{Title: "Trending Work 2"}
	require.NoError(t, db.Create(&work1).Error)
	require.NoError(t, db.Create(&work2).Error)

	trendingData := []*domain.Trending{
		{EntityID: work1.ID, EntityType: "Work", Rank: 1, TimePeriod: "daily"},
		{EntityID: work2.ID, EntityType: "Work", Rank: 2, TimePeriod: "daily"},
	}
	require.NoError(t, db.Create(&trendingData).Error)

	t.Run("success", func(t *testing.T) {
		works, err := repo.GetTrendingWorks(ctx, "daily", 5)
		require.NoError(t, err)
		require.Len(t, works, 2)
		assert.Equal(t, work1.ID, works[0].ID)
		assert.Equal(t, work2.ID, works[1].ID)
	})
}

func TestAnalyticsRepository_UpdateTrendingWorks(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup: Create an old trending record
	oldTrending := domain.Trending{EntityID: 99, EntityType: "Work", Rank: 1, TimePeriod: "daily"}
	require.NoError(t, db.Create(&oldTrending).Error)

	newTrendingData := []*domain.Trending{
		{EntityID: 1, EntityType: "Work", Rank: 1, Score: 100, TimePeriod: "daily"},
		{EntityID: 2, EntityType: "Work", Rank: 2, Score: 90, TimePeriod: "daily"},
	}

	err := repo.UpdateTrendingWorks(ctx, "daily", newTrendingData)
	require.NoError(t, err)

	var trendingResult []domain.Trending
	db.Where("time_period = ?", "daily").Order("rank asc").Find(&trendingResult)

	require.Len(t, trendingResult, 2)
	assert.Equal(t, uint(1), trendingResult[0].EntityID)
	assert.Equal(t, uint(2), trendingResult[1].EntityID)
}

func TestAnalyticsRepository_GetOrCreate(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	work := domain.Work{Title: "Test Work"}
	require.NoError(t, db.Create(&work).Error)
	translation := domain.Translation{Title: "Test Translation", TranslatableID: work.ID, TranslatableType: "Work"}
	require.NoError(t, db.Create(&translation).Error)
	user := domain.User{Username: "testuser", Email: "test@test.com"}
	require.NoError(t, db.Create(&user).Error)

	t.Run("GetOrCreateWorkStats", func(t *testing.T) {
		// Create
		stats, err := repo.GetOrCreateWorkStats(ctx, work.ID)
		require.NoError(t, err)
		assert.Equal(t, work.ID, stats.WorkID)

		// Get
		stats2, err := repo.GetOrCreateWorkStats(ctx, work.ID)
		require.NoError(t, err)
		assert.Equal(t, stats.ID, stats2.ID)
	})

	t.Run("GetOrCreateTranslationStats", func(t *testing.T) {
		// Create
		stats, err := repo.GetOrCreateTranslationStats(ctx, translation.ID)
		require.NoError(t, err)
		assert.Equal(t, translation.ID, stats.TranslationID)

		// Get
		stats2, err := repo.GetOrCreateTranslationStats(ctx, translation.ID)
		require.NoError(t, err)
		assert.Equal(t, stats.ID, stats2.ID)
	})

	t.Run("GetOrCreateUserEngagement", func(t *testing.T) {
		date := time.Now().Truncate(24 * time.Hour)
		// Create
		eng, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
		require.NoError(t, err)
		assert.Equal(t, user.ID, eng.UserID)

		// Get
		eng2, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
		require.NoError(t, err)
		assert.Equal(t, eng.ID, eng2.ID)
	})
}

func TestAnalyticsRepository_UpdateUserEngagement(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	user := domain.User{Username: "testuser", Email: "test@test.com"}
	require.NoError(t, db.Create(&user).Error)
	date := time.Now().Truncate(24 * time.Hour)
	engagement, err := repo.GetOrCreateUserEngagement(ctx, user.ID, date)
	require.NoError(t, err)

	engagement.LikesGiven = 15
	engagement.WorksRead = 10

	err = repo.UpdateUserEngagement(ctx, engagement)
	require.NoError(t, err)

	var updatedEngagement domain.UserEngagement
	db.First(&updatedEngagement, engagement.ID)

	assert.Equal(t, 15, updatedEngagement.LikesGiven)
	assert.Equal(t, 10, updatedEngagement.WorksRead)
}

func TestAnalyticsRepository_IncrementTranslationCounter(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup: Create a work and translation to associate stats with
	work := domain.Work{Title: "Test Work"}
	require.NoError(t, db.Create(&work).Error)
	translation := domain.Translation{Title: "Test Translation", TranslatableID: work.ID, TranslatableType: "works"}
	require.NoError(t, db.Create(&translation).Error)

	t.Run("creates_new_stats_if_not_exist", func(t *testing.T) {
		err := repo.IncrementTranslationCounter(ctx, translation.ID, "views", 10)
		require.NoError(t, err)

		var stats domain.TranslationStats
		err = db.Where("translation_id = ?", translation.ID).First(&stats).Error
		require.NoError(t, err)
		assert.Equal(t, int64(10), stats.Views)
	})

	t.Run("increments_existing_stats", func(t *testing.T) {
		// Increment again
		err := repo.IncrementTranslationCounter(ctx, translation.ID, "views", 5)
		require.NoError(t, err)

		var stats domain.TranslationStats
		err = db.Where("translation_id = ?", translation.ID).First(&stats).Error
		require.NoError(t, err)
		assert.Equal(t, int64(15), stats.Views) // 10 + 5
	})

	t.Run("invalid_field", func(t *testing.T) {
		err := repo.IncrementTranslationCounter(ctx, translation.ID, "invalid_field", 1)
		assert.Error(t, err)
	})
}

func TestAnalyticsRepository_UpdateWorkStats(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup
	work := domain.Work{Title: "Test Work"}
	require.NoError(t, db.Create(&work).Error)
	stats := domain.WorkStats{WorkID: work.ID, Views: 10}
	require.NoError(t, db.Create(&stats).Error)

	// Act
	update := domain.WorkStats{ReadingTime: 120, Complexity: 0.5}
	err := repo.UpdateWorkStats(ctx, work.ID, update)
	require.NoError(t, err)

	// Assert
	var updatedStats domain.WorkStats
	err = db.Where("work_id = ?", work.ID).First(&updatedStats).Error
	require.NoError(t, err)
	assert.Equal(t, int64(10), updatedStats.Views) // Should not be zeroed
	assert.Equal(t, 120, updatedStats.ReadingTime)
	assert.Equal(t, 0.5, updatedStats.Complexity)
}

func TestAnalyticsRepository_UpdateTranslationStats(t *testing.T) {
	repo, db := newTestAnalyticsRepoWithSQLite(t)
	ctx := context.Background()

	// Setup
	work := domain.Work{Title: "Test Work"}
	require.NoError(t, db.Create(&work).Error)
	translation := domain.Translation{Title: "Test Translation", TranslatableID: work.ID, TranslatableType: "works"}
	require.NoError(t, db.Create(&translation).Error)
	stats := domain.TranslationStats{TranslationID: translation.ID, Views: 20}
	require.NoError(t, db.Create(&stats).Error)

	// Act
	update := domain.TranslationStats{ReadingTime: 60, Sentiment: 0.8}
	err := repo.UpdateTranslationStats(ctx, translation.ID, update)
	require.NoError(t, err)

	// Assert
	var updatedStats domain.TranslationStats
	err = db.Where("translation_id = ?", translation.ID).First(&updatedStats).Error
	require.NoError(t, err)
	assert.Equal(t, int64(20), updatedStats.Views) // Should not be zeroed
	assert.Equal(t, 60, updatedStats.ReadingTime)
	assert.Equal(t, 0.8, updatedStats.Sentiment)
}