package services

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/google/uuid"

	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
)

// MockStore for testing
type MockStore struct {
	data map[string]*ChunkUploadInfo
}

func NewMockStore() *MockStore {
	return &MockStore{data: make(map[string]*ChunkUploadInfo)}
}

func (m *MockStore) SetState(ctx context.Context, info *ChunkUploadInfo) error {
	m.data[info.UploadID] = info
	return nil
}

func (m *MockStore) GetState(ctx context.Context, uploadID string) (*ChunkUploadInfo, error) {
	info, ok := m.data[uploadID]
	if !ok {
		return nil, fmt.Errorf("upload not found")
	}
	return info, nil
}

func (m *MockStore) DeleteState(ctx context.Context, uploadID string) error {
	delete(m.data, uploadID)
	return nil
}

func setupTestTrackChunkServiceForResume(_ *testing.T) (*TrackChunkService, func()) {
	logger := zap.NewNop()
	chunksDir := "test_uploads/tracks/chunks"

	// Create service with MockStore
	service := &TrackChunkService{
		chunksDir: chunksDir,
		store:     NewMockStore(),
		logger:    logger,
		// other fields defaults
	}

	cleanup := func() {
		// Cleanup will be handled by the service
		// os.RemoveAll(chunksDir) if needed
	}

	return service, cleanup
}

func TestTrackChunkService_GetUploadState_Success(t *testing.T) {
	service, cleanup := setupTestTrackChunkServiceForResume(t)
	defer cleanup()

	// Initialiser un upload
	userID := uuid.New()
	uploadID, err := service.InitiateChunkedUpload(userID, 5, 1024*1024*50, "test.mp3")
	assert.NoError(t, err)
	assert.NotEmpty(t, uploadID)

	// Récupérer l'état initial (aucun chunk reçu)
	state, err := service.GetUploadState(uploadID)
	assert.NoError(t, err)
	assert.NotNil(t, state)
	assert.Equal(t, uploadID, state.UploadID)
	assert.Equal(t, userID, state.UserID)
	assert.Equal(t, 5, state.TotalChunks)
	assert.Equal(t, int64(1024*1024*50), state.TotalSize)
	assert.Equal(t, "test.mp3", state.Filename)
	assert.Empty(t, state.ChunksReceived)
	assert.Equal(t, 0, state.LastChunk)
	assert.Equal(t, 0, state.ReceivedCount)
	assert.Equal(t, 0, state.Progress)
}

func TestTrackChunkService_GetUploadState_NotFound(t *testing.T) {
	service, cleanup := setupTestTrackChunkServiceForResume(t)
	defer cleanup()

	// Essayer de récupérer l'état d'un upload inexistant
	state, err := service.GetUploadState("non-existent-upload-id")
	assert.Error(t, err)
	assert.Nil(t, state)
	assert.Contains(t, err.Error(), "upload not found")
}

func TestTrackChunkService_GetUploadState_WithChunks(t *testing.T) {
	service, cleanup := setupTestTrackChunkServiceForResume(t)
	defer cleanup()

	// Initialiser un upload
	userID := uuid.New()
	uploadID, err := service.InitiateChunkedUpload(userID, 5, 1024*1024*50, "test.mp3")
	assert.NoError(t, err)

	// Simuler l'ajout de quelques chunks via l'interface
	// Note: In real Redis implementation we can't lock keys like this.
	// For testing, we update the MockStore directly.
	mockStore := service.store.(*MockStore)
	uploadInfo := mockStore.data[uploadID]
	assert.NotNil(t, uploadInfo)

	// Modifying the struct directly for test setup

	uploadInfo.Chunks[1] = ChunkInfo{
		ChunkNumber: 1,
		Size:        1024 * 1024 * 10,
		MD5:         "chunk1md5",
		FilePath:    "test/chunk_1",
		Received:    true,
	}
	uploadInfo.Chunks[2] = ChunkInfo{
		ChunkNumber: 2,
		Size:        1024 * 1024 * 10,
		MD5:         "chunk2md5",
		FilePath:    "test/chunk_2",
		Received:    true,
	}
	uploadInfo.Chunks[4] = ChunkInfo{
		ChunkNumber: 4,
		Size:        1024 * 1024 * 10,
		MD5:         "chunk4md5",
		FilePath:    "test/chunk_4",
		Received:    true,
	}
	// Update store manually
	mockStore.data[uploadID] = uploadInfo

	// Récupérer l'état
	state, err := service.GetUploadState(uploadID)
	assert.NoError(t, err)
	assert.NotNil(t, state)

	// Vérifier les chunks reçus
	assert.Equal(t, 3, state.ReceivedCount)
	assert.Equal(t, 4, state.LastChunk) // Le dernier chunk reçu est le 4
	assert.Equal(t, 60, state.Progress) // 3/5 = 60%
	assert.Contains(t, state.ChunksReceived, 1)
	assert.Contains(t, state.ChunksReceived, 2)
	assert.Contains(t, state.ChunksReceived, 4)
	assert.NotContains(t, state.ChunksReceived, 3)
	assert.NotContains(t, state.ChunksReceived, 5)
}

func TestTrackChunkService_GetUploadState_Complete(t *testing.T) {
	service, cleanup := setupTestTrackChunkServiceForResume(t)
	defer cleanup()

	// Initialiser un upload
	userID := uuid.New()
	uploadID, err := service.InitiateChunkedUpload(userID, 3, 1024*1024*30, "complete.mp3")
	assert.NoError(t, err)

	// Simuler tous les chunks reçus
	mockStore := service.store.(*MockStore)
	uploadInfo := mockStore.data[uploadID]
	assert.NotNil(t, uploadInfo)

	for i := 1; i <= 3; i++ {
		uploadInfo.Chunks[i] = ChunkInfo{
			ChunkNumber: i,
			Size:        1024 * 1024 * 10,
			MD5:         "chunkmd5",
			FilePath:    "test/chunk_" + string(rune(i)),
			Received:    true,
		}
	}
	uploadInfo.UpdatedAt = time.Now()
	// Update store manually
	mockStore.data[uploadID] = uploadInfo

	// Récupérer l'état
	state, err := service.GetUploadState(uploadID)
	assert.NoError(t, err)
	assert.NotNil(t, state)

	assert.Equal(t, 3, state.ReceivedCount)
	assert.Equal(t, 3, state.LastChunk)
	assert.Equal(t, 100, state.Progress)
	assert.Equal(t, 3, len(state.ChunksReceived))
}

func TestTrackChunkService_GetUploadState_MultipleUsers(t *testing.T) {
	service, cleanup := setupTestTrackChunkServiceForResume(t)
	defer cleanup()

	// Créer deux uploads pour deux utilisateurs différents
	userID1 := uuid.New()
	uploadID1, err := service.InitiateChunkedUpload(userID1, 5, 1024*1024*50, "user1.mp3")
	assert.NoError(t, err)

	userID2 := uuid.New()
	uploadID2, err := service.InitiateChunkedUpload(userID2, 3, 1024*1024*30, "user2.mp3")
	assert.NoError(t, err)

	// Récupérer les états
	state1, err := service.GetUploadState(uploadID1)
	assert.NoError(t, err)
	assert.Equal(t, userID1, state1.UserID)

	state2, err := service.GetUploadState(uploadID2)
	assert.NoError(t, err)
	assert.Equal(t, userID2, state2.UserID)

	// Vérifier que les états sont isolés
	assert.NotEqual(t, state1.UploadID, state2.UploadID)
}
