package services

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

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

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

func setupTestHLSService(t *testing.T) (*HLSService, *gorm.DB, string, func()) {
	// Setup in-memory database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Enable foreign keys
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate
	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{})
	require.NoError(t, err)

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

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create test directory structure
	testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_service_test_%d", os.Getpid()))
	require.NoError(t, os.MkdirAll(testDir, 0755))

	trackDir := filepath.Join(testDir, fmt.Sprintf("track_%s", track.ID.String()))
	require.NoError(t, os.MkdirAll(trackDir, 0755))

	// Create master playlist
	masterPlaylistPath := filepath.Join(trackDir, "master.m3u8")
	masterPlaylistContent := `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=128000
128k/playlist.m3u8
`
	require.NoError(t, os.WriteFile(masterPlaylistPath, []byte(masterPlaylistContent), 0644))

	// Create quality playlist
	qualityDir := filepath.Join(trackDir, "128k")
	require.NoError(t, os.MkdirAll(qualityDir, 0755))
	qualityPlaylistPath := filepath.Join(qualityDir, "playlist.m3u8")
	qualityPlaylistContent := `#EXTM3U
#EXT-X-VERSION:3
#EXTINF:10.0,
segment_000.ts
`
	require.NoError(t, os.WriteFile(qualityPlaylistPath, []byte(qualityPlaylistContent), 0644))

	// Create test segment
	segmentPath := filepath.Join(qualityDir, "segment_000.ts")
	require.NoError(t, os.WriteFile(segmentPath, []byte("test segment data"), 0644))

	// Create HLS stream
	hlsStream := &models.HLSStream{
		TrackID:       track.ID,
		PlaylistURL:   filepath.Join(fmt.Sprintf("track_%s", track.ID.String()), "master.m3u8"),
		SegmentsCount: 1,
		Bitrates:      models.BitrateList{128},
		Status:        models.HLSStatusReady,
	}
	err = db.Create(hlsStream).Error
	require.NoError(t, err)

	// Create service
	logger := zaptest.NewLogger(t)
	service := NewHLSService(db, testDir, logger)

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

	return service, db, testDir, cleanup
}

func TestNewHLSService(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	service := NewHLSService(db, "/tmp", logger)

	assert.NotNil(t, service)
	assert.Equal(t, "/tmp", service.outputDir)
	assert.NotNil(t, service.logger)
}

func TestNewHLSService_NilLogger(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	service := NewHLSService(db, "/tmp", nil)

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

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

	ctx := context.Background()
	// We need to find the track ID created in setup
	// Since we can't easily get it returned from setup without changing signature,
	// let's query the DB or rely on the fact that setup creates one track.
	// Actually, setupTestHLSService returns (service, db, testDir, cleanup).
	// We can query the DB.
	var track models.Track
	result := service.db.First(&track)
	require.NoError(t, result.Error)

	playlist, err := service.GetMasterPlaylist(ctx, track.ID)

	assert.NoError(t, err)
	assert.Contains(t, playlist, "#EXTM3U")
	assert.Contains(t, playlist, "128k/playlist.m3u8")
}

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

	ctx := context.Background()
	playlist, err := service.GetMasterPlaylist(ctx, uuid.New())

	assert.Error(t, err)
	assert.Empty(t, playlist)
	assert.Contains(t, err.Error(), "not found")
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)

	playlist, err := service.GetQualityPlaylist(ctx, track.ID, "128k")

	assert.NoError(t, err)
	assert.Contains(t, playlist, "#EXTM3U")
	assert.Contains(t, playlist, "segment_000.ts")
}

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

	ctx := context.Background()
	playlist, err := service.GetQualityPlaylist(ctx, uuid.New(), "128k")

	assert.Error(t, err)
	assert.Empty(t, playlist)
	assert.Contains(t, err.Error(), "not found")
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)

	playlist, err := service.GetQualityPlaylist(ctx, track.ID, "999k")

	assert.Error(t, err)
	assert.Empty(t, playlist)
	assert.Contains(t, err.Error(), "not found")
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)

	segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "segment_000.ts")

	assert.NoError(t, err)
	assert.NotEmpty(t, segmentPath)
	assert.FileExists(t, segmentPath)
}

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

	ctx := context.Background()
	segmentPath, err := service.GetSegmentPath(ctx, uuid.New(), "128k", "segment_000.ts")

	assert.Error(t, err)
	assert.Empty(t, segmentPath)
	assert.Contains(t, err.Error(), "not found")
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)

	segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "nonexistent.ts")

	assert.Error(t, err)
	assert.Empty(t, segmentPath)
	assert.Contains(t, err.Error(), "not found")
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)
	// Tentative de directory traversal
	segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "../../../etc/passwd")

	assert.Error(t, err)
	assert.Empty(t, segmentPath)
	// Le fichier n'existe pas, donc erreur "not found" ou "invalid path"
	assert.True(t, err != nil)
}

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

	ctx := context.Background()
	var track models.Track
	service.db.First(&track)

	status, err := service.GetStreamStatus(ctx, track.ID)

	assert.NoError(t, err)
	assert.NotNil(t, status)
	assert.Equal(t, models.HLSStatusReady, status["status"])
	assert.Equal(t, models.BitrateList{128}, status["bitrates"])
	assert.Equal(t, 1, status["segments_count"])
	assert.Contains(t, status["playlist_url"], "master.m3u8")
	assert.Equal(t, track.ID, status["track_id"])
}

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

	ctx := context.Background()
	status, err := service.GetStreamStatus(ctx, uuid.New())

	assert.Error(t, err)
	assert.Nil(t, status)
	assert.Contains(t, err.Error(), "not found")
}

func TestHLSService_GetStreamStatus_Processing(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	db.Exec("PRAGMA foreign_keys = ON")
	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{}, &models.HLSTranscodeQueue{})
	require.NoError(t, err)

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

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusProcessing,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create HLS stream with processing status
	hlsStream := &models.HLSStream{
		TrackID:       track.ID,
		PlaylistURL:   "track_1/master.m3u8",
		SegmentsCount: 0,
		Bitrates:      models.BitrateList{},
		Status:        models.HLSStatusProcessing,
	}
	err = db.Create(hlsStream).Error
	require.NoError(t, err)

	// Create queue job
	queueJob := &models.HLSTranscodeQueue{
		TrackID:    track.ID,
		Priority:   5,
		Status:     models.QueueStatusProcessing,
		RetryCount: 0,
		MaxRetries: 3,
	}
	err = db.Create(queueJob).Error
	require.NoError(t, err)

	// Create service
	testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_service_test_%d", os.Getpid()))
	service := NewHLSService(db, testDir, logger)

	ctx := context.Background()
	status, err := service.GetStreamStatus(ctx, track.ID)

	assert.NoError(t, err)
	assert.NotNil(t, status)
	assert.Equal(t, models.HLSStatusProcessing, status["status"])
	assert.Equal(t, queueJob.ID, status["queue_job_id"])
	assert.Equal(t, queueJob.RetryCount, status["retry_count"])
}

func TestHLSService_TriggerTranscode(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	db.Exec("PRAGMA foreign_keys = ON")
	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{})
	require.NoError(t, err)

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

	// Create test directory
	testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_trigger_test_%d", os.Getpid()))
	require.NoError(t, os.MkdirAll(testDir, 0755))
	defer os.RemoveAll(testDir)

	// Create test track with audio file
	testAudioFile := filepath.Join(testDir, "test.mp3")
	require.NoError(t, os.WriteFile(testAudioFile, []byte("fake audio content"), 0644))

	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: testAudioFile,
		FileSize: 1024,
		Format:   "mp3",
		Duration: 180,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create transcode service
	transcodeService := NewHLSTranscodeService(testDir, logger)
	hlsService := NewHLSServiceWithTranscode(db, testDir, transcodeService, logger)

	ctx := context.Background()

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

	if err != nil {
		// Si ffmpeg n'est pas disponible, vérifier que l'erreur est logique
		assert.Error(t, err)
		// Vérifier qu'un stream a été créé avec statut "failed"
		var stream models.HLSStream
		err = db.Where("track_id = ?", track.ID).First(&stream).Error
		if err == nil {
			assert.Equal(t, models.HLSStatusFailed, stream.Status)
		}
	} else {
		// Si ffmpeg est disponible, vérifier que le stream a été créé avec succès
		var stream models.HLSStream
		err = db.Where("track_id = ?", track.ID).First(&stream).Error
		assert.NoError(t, err)
		assert.Equal(t, models.HLSStatusReady, stream.Status)
		assert.NotEmpty(t, stream.PlaylistURL)
		assert.Greater(t, stream.SegmentsCount, 0)
	}
}

func TestHLSService_TriggerTranscode_NilTrack(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	transcodeService := NewHLSTranscodeService("/tmp", logger)
	service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)

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

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

func TestHLSService_TriggerTranscode_NoTranscodeService(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	service := NewHLSService(db, "/tmp", logger)

	track := &models.Track{
		ID:       uuid.New(),
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
	}

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

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "transcode service not configured")
}

func TestHLSService_TriggerTranscode_AlreadyExists(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	db.Exec("PRAGMA foreign_keys = ON")
	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{})
	require.NoError(t, err)

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

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 1024,
		Format:   "mp3",
		Duration: 180,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create existing HLS stream with ready status
	hlsStream := &models.HLSStream{
		TrackID:     track.ID,
		PlaylistURL: "/test/master.m3u8",
		Status:      models.HLSStatusReady,
	}
	err = db.Create(hlsStream).Error
	require.NoError(t, err)

	transcodeService := NewHLSTranscodeService("/tmp", logger)
	service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)

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

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "already exists and is ready")
}

func TestHLSService_TriggerTranscode_AlreadyProcessing(t *testing.T) {
	logger := zaptest.NewLogger(t)
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	db.Exec("PRAGMA foreign_keys = ON")
	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{})
	require.NoError(t, err)

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

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 1024,
		Format:   "mp3",
		Duration: 180,
		Status:   models.TrackStatusProcessing,
	}
	err = db.Create(track).Error
	require.NoError(t, err)

	// Create existing HLS stream with processing status
	hlsStream := &models.HLSStream{
		TrackID: track.ID,
		Status:  models.HLSStatusProcessing,
	}
	err = db.Create(hlsStream).Error
	require.NoError(t, err)

	transcodeService := NewHLSTranscodeService("/tmp", logger)
	service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)

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

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "already being processed")
}
