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 setupTestTrackHistoryService(t *testing.T) (*TrackHistoryService, *gorm.DB, func()) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.TrackHistory{})
	require.NoError(t, err)

	logger := zap.NewNop()
	service := NewTrackHistoryService(db, logger)

	cleanup := func() {
		// No cleanup needed for in-memory database
	}

	return service, db, cleanup
}

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

	ctx := context.Background()

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

	// Create track
	track := &models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		FilePath: "/path/to/track.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Record history
	params := RecordHistoryParams{
		TrackID:  track.ID,
		UserID:   user.ID,
		Action:   models.TrackHistoryActionCreated,
		OldValue: nil,
		NewValue: map[string]interface{}{"title": "Test Track"},
	}

	history, err := service.RecordHistory(ctx, params)
	assert.NoError(t, err)
	assert.NotNil(t, history)
	assert.Equal(t, track.ID, history.TrackID)
	assert.Equal(t, user.ID, history.UserID)
	assert.Equal(t, models.TrackHistoryActionCreated, history.Action)
	assert.NotEmpty(t, history.NewValue)
}

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

	ctx := context.Background()

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

	// Record history with non-existent track
	params := RecordHistoryParams{
		TrackID:  uuid.New(),
		UserID:   user.ID,
		Action:   models.TrackHistoryActionCreated,
		OldValue: nil,
		NewValue: map[string]interface{}{"title": "Test Track"},
	}

	_, err := service.RecordHistory(ctx, params)
	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrTrackNotFound)
}

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

	ctx := context.Background()

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

	// Create track
	track := &models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		FilePath: "/path/to/track.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Record history with string values
	params := RecordHistoryParams{
		TrackID:  track.ID,
		UserID:   user.ID,
		Action:   models.TrackHistoryActionUpdated,
		OldValue: "Old Title",
		NewValue: "New Title",
	}

	history, err := service.RecordHistory(ctx, params)
	assert.NoError(t, err)
	assert.NotNil(t, history)
	assert.Contains(t, history.OldValue, "Old Title")
	assert.Contains(t, history.NewValue, "New Title")
}

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

	ctx := context.Background()

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

	// Create track
	track := &models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		FilePath: "/path/to/track.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Record multiple history entries
	for i := 0; i < 5; i++ {
		params := RecordHistoryParams{
			TrackID:  track.ID,
			UserID:   user.ID,
			Action:   models.TrackHistoryActionUpdated,
			OldValue: map[string]interface{}{"iteration": i},
			NewValue: map[string]interface{}{"iteration": i + 1},
		}
		_, err := service.RecordHistory(ctx, params)
		require.NoError(t, err)
	}

	// Get history
	histories, total, err := service.GetHistory(ctx, track.ID, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, histories, 5)

	// Verify ordering (should be DESC by created_at)
	for i := 0; i < len(histories)-1; i++ {
		assert.True(t, histories[i].CreatedAt.After(histories[i+1].CreatedAt) || histories[i].CreatedAt.Equal(histories[i+1].CreatedAt))
	}
}

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

	ctx := context.Background()

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

	// Create track
	track := &models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		FilePath: "/path/to/track.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Record multiple history entries
	for i := 0; i < 10; i++ {
		params := RecordHistoryParams{
			TrackID:  track.ID,
			UserID:   user.ID,
			Action:   models.TrackHistoryActionUpdated,
			OldValue: map[string]interface{}{"iteration": i},
			NewValue: map[string]interface{}{"iteration": i + 1},
		}
		_, err := service.RecordHistory(ctx, params)
		require.NoError(t, err)
	}

	// Get first page
	histories, total, err := service.GetHistory(ctx, track.ID, 5, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total)
	assert.Len(t, histories, 5)

	// Get second page
	histories2, total2, err := service.GetHistory(ctx, track.ID, 5, 5)
	assert.NoError(t, err)
	assert.Equal(t, int64(10), total2)
	assert.Len(t, histories2, 5)

	// Verify no overlap
	assert.NotEqual(t, histories[0].ID, histories2[0].ID)
}

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

	ctx := context.Background()

	_, _, err := service.GetHistory(ctx, uuid.New(), 10, 0)
	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrTrackNotFound)
}

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

	ctx := context.Background()

	// Create users
	user1 := &models.User{
		ID:       uuid.New(),
		Username: "user1",
		Email:    "user1@example.com",
		IsActive: true,
	}
	user2 := &models.User{
		ID:       uuid.New(),
		Username: "user2",
		Email:    "user2@example.com",
		IsActive: true,
	}
	db.Create(user1)
	db.Create(user2)

	// Create tracks
	track1 := &models.Track{
		UserID:   user1.ID,
		Title:    "Track 1",
		FilePath: "/path/to/track1.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	track2 := &models.Track{
		UserID:   user2.ID,
		Title:    "Track 2",
		FilePath: "/path/to/track2.mp3",
		FileSize: 2048,
		Format:   "MP3",
		Duration: 240,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track1)
	db.Create(track2)

	// Record history for user1
	for i := 0; i < 3; i++ {
		params := RecordHistoryParams{
			TrackID:  track1.ID,
			UserID:   user1.ID,
			Action:   models.TrackHistoryActionUpdated,
			OldValue: map[string]interface{}{"iteration": i},
			NewValue: map[string]interface{}{"iteration": i + 1},
		}
		_, err := service.RecordHistory(ctx, params)
		require.NoError(t, err)
	}

	// Record history for user2
	for i := 0; i < 2; i++ {
		params := RecordHistoryParams{
			TrackID:  track2.ID,
			UserID:   user2.ID,
			Action:   models.TrackHistoryActionUpdated,
			OldValue: map[string]interface{}{"iteration": i},
			NewValue: map[string]interface{}{"iteration": i + 1},
		}
		_, err := service.RecordHistory(ctx, params)
		require.NoError(t, err)
	}

	// Get history for user1
	histories, total, err := service.GetHistoryByUser(ctx, user1.ID, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, histories, 3)

	// Verify all entries belong to user1
	for _, h := range histories {
		assert.Equal(t, user1.ID, h.UserID)
	}
}

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

	ctx := context.Background()

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

	// Create track
	track := &models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		FilePath: "/path/to/track.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Record different actions
	actions := []models.TrackHistoryAction{
		models.TrackHistoryActionCreated,
		models.TrackHistoryActionUpdated,
		models.TrackHistoryActionUpdated,
		models.TrackHistoryActionPublished,
		models.TrackHistoryActionUpdated,
	}

	for _, action := range actions {
		params := RecordHistoryParams{
			TrackID:  track.ID,
			UserID:   user.ID,
			Action:   action,
			OldValue: nil,
			NewValue: map[string]interface{}{"action": string(action)},
		}
		_, err := service.RecordHistory(ctx, params)
		require.NoError(t, err)
	}

	// Get history for "updated" action only
	histories, total, err := service.GetHistoryByAction(ctx, track.ID, models.TrackHistoryActionUpdated, 10, 0)
	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, histories, 3)

	// Verify all entries have "updated" action
	for _, h := range histories {
		assert.Equal(t, models.TrackHistoryActionUpdated, h.Action)
	}
}

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

	ctx := context.Background()

	_, _, err := service.GetHistoryByAction(ctx, uuid.New(), models.TrackHistoryActionUpdated, 10, 0)
	assert.Error(t, err)
	assert.ErrorIs(t, err, ErrTrackNotFound)
}
