package services

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

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

func setupTestHLSDir(t *testing.T) (string, func()) {
	testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_test_%d", time.Now().UnixNano()))
	err := os.MkdirAll(testDir, 0755)
	require.NoError(t, err)

	cleanup := func() {
		os.RemoveAll(testDir)
	}

	return testDir, cleanup
}

func createTestTrack(t *testing.T, filePath string) *models.Track {
	// Créer un fichier audio de test minimal
	err := os.WriteFile(filePath, []byte("fake audio content"), 0644)
	require.NoError(t, err)

	// GO-004: Utiliser UUID au lieu de int
	trackID := uuid.New()
	userID := uuid.New()
	return &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: filePath,
		FileSize: 1024,
		Format:   "mp3",
		Duration: 180,
		Status:   models.TrackStatusCompleted,
	}
}

func TestNewHLSTranscodeService(t *testing.T) {
	logger := zaptest.NewLogger(t)
	service := NewHLSTranscodeService("/tmp/hls", logger)

	assert.NotNil(t, service)
	assert.Equal(t, "/tmp/hls", service.outputDir)
	assert.Equal(t, []int{128, 192, 320}, service.bitrates)
	assert.NotNil(t, service.logger)
}

func TestNewHLSTranscodeService_NilLogger(t *testing.T) {
	service := NewHLSTranscodeService("/tmp/hls", nil)

	assert.NotNil(t, service)
	assert.NotNil(t, service.logger) // Devrait créer un logger Nop
}

func TestHLSTranscodeService_SetBitrates(t *testing.T) {
	logger := zaptest.NewLogger(t)
	service := NewHLSTranscodeService("/tmp/hls", logger)

	customBitrates := []int{64, 128, 256}
	service.SetBitrates(customBitrates)

	assert.Equal(t, customBitrates, service.bitrates)
}

func TestHLSTranscodeService_TranscodeTrack_NilTrack(t *testing.T) {
	logger := zaptest.NewLogger(t)
	service := NewHLSTranscodeService("/tmp/hls", logger)

	ctx := context.Background()
	result, err := service.TranscodeTrack(ctx, nil)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "track cannot be nil")
}

func TestHLSTranscodeService_TranscodeTrack_EmptyFilePath(t *testing.T) {
	logger := zaptest.NewLogger(t)
	service := NewHLSTranscodeService("/tmp/hls", logger)

	// GO-004: Utiliser UUID au lieu de int
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		FilePath: "",
	}

	ctx := context.Background()
	result, err := service.TranscodeTrack(ctx, track)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "file path is empty")
}

func TestHLSTranscodeService_TranscodeTrack_FileNotExists(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// GO-004: Utiliser UUID au lieu de int
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		FilePath: "/nonexistent/file.mp3",
	}

	ctx := context.Background()
	result, err := service.TranscodeTrack(ctx, track)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "file does not exist")
}

func TestHLSTranscodeService_TranscodeTrack_CreatesDirectory(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer un fichier audio de test
	testAudioFile := filepath.Join(testDir, "test.mp3")
	track := createTestTrack(t, testAudioFile)

	ctx := context.Background()

	// Note: Ce test échouera si ffmpeg n'est pas installé
	// C'est acceptable car c'est un test d'intégration
	result, err := service.TranscodeTrack(ctx, track)

	// Si ffmpeg n'est pas disponible, on s'attend à une erreur
	if err != nil {
		// Vérifier que le répertoire a été créé même en cas d'erreur
		trackDir := filepath.Join(testDir, fmt.Sprintf("track_%d", track.ID))
		// Le répertoire peut ne pas exister si l'erreur survient avant sa création
		// ou peut exister si l'erreur survient après
		_ = trackDir
		assert.Error(t, err)
		assert.Nil(t, result)
	} else {
		// Si ffmpeg est disponible, vérifier que tout a été créé
		assert.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, track.ID, result.TrackID)
		assert.Contains(t, result.PlaylistURL, "master.m3u8")
		assert.Greater(t, result.SegmentsCount, 0)
		assert.Equal(t, models.HLSStatusReady, result.Status)
	}
}

func TestHLSTranscodeService_CountSegments(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer une structure de test
	trackDir := filepath.Join(testDir, "track_123")
	qualityDir1 := filepath.Join(trackDir, "128k")
	qualityDir2 := filepath.Join(trackDir, "192k")

	require.NoError(t, os.MkdirAll(qualityDir1, 0755))
	require.NoError(t, os.MkdirAll(qualityDir2, 0755))

	// Créer des segments de test
	for i := 0; i < 3; i++ {
		segmentPath := filepath.Join(qualityDir1, fmt.Sprintf("segment_%03d.ts", i))
		require.NoError(t, os.WriteFile(segmentPath, []byte("test"), 0644))
	}

	for i := 0; i < 2; i++ {
		segmentPath := filepath.Join(qualityDir2, fmt.Sprintf("segment_%03d.ts", i))
		require.NoError(t, os.WriteFile(segmentPath, []byte("test"), 0644))
	}

	count, err := service.countSegments(trackDir)

	assert.NoError(t, err)
	// Devrait retourner le maximum (3 segments dans 128k)
	assert.Equal(t, 3, count)
}

func TestHLSTranscodeService_CountSegments_EmptyDir(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	trackDir := filepath.Join(testDir, "track_123")
	require.NoError(t, os.MkdirAll(trackDir, 0755))

	// Créer les répertoires de qualité vides
	for _, bitrate := range service.bitrates {
		qualityDir := filepath.Join(trackDir, fmt.Sprintf("%dk", bitrate))
		require.NoError(t, os.MkdirAll(qualityDir, 0755))
	}

	count, err := service.countSegments(trackDir)

	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestHLSTranscodeService_CountSegments_NonexistentDir(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	count, err := service.countSegments("/nonexistent/dir")

	assert.Error(t, err)
	assert.Equal(t, 0, count)
}

func TestHLSTranscodeService_CountSegments_MultipleBitrates(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer un répertoire de track avec des segments
	trackDir := filepath.Join(testDir, "track_123")
	require.NoError(t, os.MkdirAll(trackDir, 0755))

	// Créer des répertoires de qualité avec différents nombres de segments
	qualityDir128 := filepath.Join(trackDir, "128k")
	require.NoError(t, os.MkdirAll(qualityDir128, 0755))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir128, "segment_000.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir128, "segment_001.ts"), []byte("data"), 0644))

	qualityDir192 := filepath.Join(trackDir, "192k")
	require.NoError(t, os.MkdirAll(qualityDir192, 0755))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_000.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_001.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_002.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_003.ts"), []byte("data"), 0644))

	qualityDir320 := filepath.Join(trackDir, "320k")
	require.NoError(t, os.MkdirAll(qualityDir320, 0755))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir320, "segment_000.ts"), []byte("data"), 0644))

	count, err := service.countSegments(trackDir)

	assert.NoError(t, err)
	// Devrait retourner le maximum (4 segments dans 192k)
	assert.Equal(t, 4, count)
}

func TestHLSTranscodeService_CountSegments_OnlySegmentFiles(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer un répertoire de track avec des segments
	trackDir := filepath.Join(testDir, "track_123")
	require.NoError(t, os.MkdirAll(trackDir, 0755))

	qualityDir := filepath.Join(trackDir, "128k")
	require.NoError(t, os.MkdirAll(qualityDir, 0755))
	// Créer des fichiers segment_*.ts
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_000.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_001.ts"), []byte("data"), 0644))
	// Créer d'autres fichiers qui ne doivent pas être comptés
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "playlist.m3u8"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "other.ts"), []byte("data"), 0644))
	require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_other.txt"), []byte("data"), 0644))

	count, err := service.countSegments(trackDir)

	assert.NoError(t, err)
	// Devrait compter uniquement les fichiers segment_*.ts (2 fichiers)
	assert.Equal(t, 2, count)
}

func TestHLSTranscodeService_GetPlaylistDuration(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer une playlist de test
	playlistContent := `#EXTM3U
#EXT-X-VERSION:3
#EXTINF:10.0,
segment_000.ts
#EXTINF:10.5,
segment_001.ts
#EXTINF:9.5,
segment_002.ts
#EXT-X-ENDLIST
`

	playlistPath := filepath.Join(testDir, "playlist.m3u8")
	require.NoError(t, os.WriteFile(playlistPath, []byte(playlistContent), 0644))

	duration := service.getPlaylistDuration(playlistPath)

	assert.Equal(t, 30.0, duration)
}

func TestHLSTranscodeService_GetPlaylistDuration_NonexistentFile(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	duration := service.getPlaylistDuration("/nonexistent/playlist.m3u8")

	assert.Equal(t, 0.0, duration)
}

func TestHLSTranscodeService_GenerateMasterPlaylist(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer les répertoires et playlists de qualité
	bitrates := []int{128, 192, 320}
	for _, bitrate := range bitrates {
		qualityDir := filepath.Join(testDir, fmt.Sprintf("%dk", bitrate))
		require.NoError(t, os.MkdirAll(qualityDir, 0755))
		playlistPath := filepath.Join(qualityDir, "playlist.m3u8")
		require.NoError(t, os.WriteFile(playlistPath, []byte("#EXTM3U\n"), 0644))
	}

	err := service.generateMasterPlaylist(testDir, bitrates)

	assert.NoError(t, err)

	// Vérifier que le fichier master.m3u8 a été créé
	masterPlaylistPath := filepath.Join(testDir, "master.m3u8")
	assert.FileExists(t, masterPlaylistPath)

	// Vérifier le contenu
	content, err := os.ReadFile(masterPlaylistPath)
	require.NoError(t, err)

	contentStr := string(content)
	assert.Contains(t, contentStr, "#EXTM3U")
	assert.Contains(t, contentStr, "#EXT-X-VERSION:3")
	assert.Contains(t, contentStr, "128k/playlist.m3u8")
	assert.Contains(t, contentStr, "192k/playlist.m3u8")
	assert.Contains(t, contentStr, "320k/playlist.m3u8")
}

func TestHLSTranscodeService_CleanupTrackDir(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer un répertoire de track
	// GO-004: Utiliser UUID au lieu de int
	// Note: CleanupTrackDir uses format "track_<trackID>", so we need to match that
	trackID := uuid.New()
	trackDir := filepath.Join(testDir, fmt.Sprintf("track_%s", trackID.String()))
	require.NoError(t, os.MkdirAll(trackDir, 0755))
	require.NoError(t, os.WriteFile(filepath.Join(trackDir, "test.txt"), []byte("test"), 0644))

	// Nettoyer
	err := service.CleanupTrackDir(trackID)

	assert.NoError(t, err)
	assert.NoDirExists(t, trackDir)
}

func TestHLSTranscodeService_CleanupTrackDir_Nonexistent(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Nettoyer un répertoire qui n'existe pas (ne devrait pas retourner d'erreur)
	// GO-004: Utiliser UUID au lieu de int
	nonexistentTrackID := uuid.New()
	err := service.CleanupTrackDir(nonexistentTrackID)

	assert.NoError(t, err)
}

func TestHLSTranscodeService_TranscodeTrack_WithCustomBitrates(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)
	service.SetBitrates([]int{64, 128})

	testAudioFile := filepath.Join(testDir, "test.mp3")
	track := createTestTrack(t, testAudioFile)

	ctx := context.Background()
	result, err := service.TranscodeTrack(ctx, track)

	// Si ffmpeg n'est pas disponible, on s'attend à une erreur
	if err != nil {
		assert.Error(t, err)
		assert.Nil(t, result)
	} else {
		assert.NoError(t, err)
		assert.NotNil(t, result)
		assert.Len(t, result.Bitrates, 2)
		assert.Contains(t, result.Bitrates, 64)
		assert.Contains(t, result.Bitrates, 128)
	}
}

func TestHLSTranscodeService_GetPlaylistDuration_InvalidFormat(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	// Créer une playlist avec format invalide
	playlistContent := `#EXTM3U
#EXTINF:invalid,
segment_000.ts
`

	playlistPath := filepath.Join(testDir, "playlist.m3u8")
	require.NoError(t, os.WriteFile(playlistPath, []byte(playlistContent), 0644))

	duration := service.getPlaylistDuration(playlistPath)

	// Devrait retourner 0 pour format invalide
	assert.Equal(t, 0.0, duration)
}

func TestHLSTranscodeService_GetPlaylistDuration_EmptyFile(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	playlistPath := filepath.Join(testDir, "empty.m3u8")
	require.NoError(t, os.WriteFile(playlistPath, []byte(""), 0644))

	duration := service.getPlaylistDuration(playlistPath)

	assert.Equal(t, 0.0, duration)
}

func TestHLSTranscodeService_GenerateMasterPlaylist_EmptyBitrates(t *testing.T) {
	logger := zaptest.NewLogger(t)
	testDir, cleanup := setupTestHLSDir(t)
	defer cleanup()

	service := NewHLSTranscodeService(testDir, logger)

	err := service.generateMasterPlaylist(testDir, []int{})

	assert.NoError(t, err)

	// Vérifier que le fichier master.m3u8 a été créé
	masterPlaylistPath := filepath.Join(testDir, "master.m3u8")
	assert.FileExists(t, masterPlaylistPath)

	// Vérifier le contenu (devrait contenir seulement le header)
	content, err := os.ReadFile(masterPlaylistPath)
	require.NoError(t, err)

	contentStr := string(content)
	assert.Contains(t, contentStr, "#EXTM3U")
	assert.Contains(t, contentStr, "#EXT-X-VERSION:3")
}
