package track

import (
	"bytes"
	"context"
	"mime/multipart" // Removed "net/http" since it is not used in the existing imports
	"os"             // Added "path" import
	"path/filepath"
	"testing"
	"time"

	"veza-backend-api/internal/models"

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

func setupTestTrackService(t *testing.T) (*TrackService, *gorm.DB, func()) {
	logger := zaptest.NewLogger(t)

	// Create temp upload dir
	uploadDir, err := os.MkdirTemp("", "track_service_test")
	require.NoError(t, err)

	// Setup SQLite database file
	dbPath := filepath.Join(uploadDir, "test.db")
	db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
	require.NoError(t, err)

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

	// Auto-migrate models
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.TrackLike{}, // Added TrackLike model to migration
	)
	require.NoError(t, err)

	service := NewTrackService(db, logger, uploadDir)

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

	return service, db, cleanup
}

func createMultipartFileHeader(t *testing.T, filename string, content []byte, contentType string) *multipart.FileHeader {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", filename)
	require.NoError(t, err)
	_, err = part.Write(content)
	require.NoError(t, err)
	err = writer.Close()
	require.NoError(t, err)

	reader := multipart.NewReader(body, writer.Boundary())
	form, err := reader.ReadForm(1024 * 1024)
	require.NoError(t, err)

	headers := form.File["file"]
	require.NotEmpty(t, headers)
	headers[0].Header.Set("Content-Type", contentType)

	return headers[0]
}

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

	// Test case: valid MP3 (mock content with ID3 header)
	mp3Content := append([]byte("ID3"), make([]byte, 100)...)
	header := createMultipartFileHeader(t, "test.mp3", mp3Content, "audio/mpeg")
	err := service.ValidateTrackFile(header)
	assert.NoError(t, err)

	// Test case: valid WAV (mock content with RIFF/WAVE header)
	wavContent := append([]byte("RIFF"), make([]byte, 4)...)
	wavContent = append(wavContent, []byte("WAVE")...)
	wavContent = append(wavContent, make([]byte, 100)...)
	headerWav := createMultipartFileHeader(t, "test.wav", wavContent, "audio/wav")
	err = service.ValidateTrackFile(headerWav)
	assert.NoError(t, err)

	// Test case: invalid extension
	headerInvalid := createMultipartFileHeader(t, "test.txt", []byte("some text"), "text/plain")
	err = service.ValidateTrackFile(headerInvalid)
	assert.ErrorIs(t, err, ErrInvalidTrackFormat)

	// Test case: file too large (manually set size to mock large file without large content)
	headerTooLarge := createMultipartFileHeader(t, "large.mp3", mp3Content, "audio/mpeg")
	headerTooLarge.Size = 500 * 1024 * 1024 // 500MB
	err = service.ValidateTrackFile(headerTooLarge)
	assert.ErrorIs(t, err, ErrTrackTooLarge)
}

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

	// Create a user
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "quotatest",
		Email:    "quota@example.com",
	}
	db.Create(user)

	ctx := context.Background()

	// Test: Empty user checks OK
	err := service.CheckUserQuota(ctx, userID, 1024*1024)
	assert.NoError(t, err)

	// Create a track consuming storage
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userID,
		Title:    "Big Track",
		FileSize: MaxStoragePerUser - 100, // Almost full
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Now try to upload something bigger than remaining
	err = service.CheckUserQuota(ctx, userID, 200)
	assert.ErrorIs(t, err, ErrStorageQuotaExceeded)
}

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

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "quotauser", Email: "qu@example.com"}
	db.Create(user)

	// Add 2 tracks
	db.Create(&models.Track{ID: uuid.New(), UserID: userID, FileSize: 1000, Status: models.TrackStatusCompleted})
	db.Create(&models.Track{ID: uuid.New(), UserID: userID, FileSize: 2000, Status: models.TrackStatusCompleted})

	ctx := context.Background()
	quota, err := service.GetUserQuota(ctx, userID)
	assert.NoError(t, err)
	assert.NotNil(t, quota)
	assert.Equal(t, int64(2), quota.TracksCount)
	assert.Equal(t, int64(3000), quota.StorageUsed)
	assert.Equal(t, int64(MaxTracksPerUser), quota.TracksLimit)
}

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

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "listuser", Email: "list@example.com"}
	db.Create(user)

	// Create tracks
	for i := 0; i < 5; i++ {
		db.Create(&models.Track{
			ID:        uuid.New(),
			UserID:    userID,
			Title:     "Track " + string(rune('A'+i)),
			Format:    "mp3",
			IsPublic:  true,
			Status:    models.TrackStatusCompleted,
			CreatedAt: time.Now().Add(time.Duration(i) * time.Minute),
		})
	}
	// Private track
	db.Create(&models.Track{
		ID:       uuid.New(),
		UserID:   userID,
		Title:    "Private Track",
		Format:   "wav",
		IsPublic: false,
		Status:   models.TrackStatusCompleted,
	})

	ctx := context.Background()

	// Test: List all
	params := TrackListParams{
		UserID: &userID,
		Page:   1,
		Limit:  10,
	}
	tracks, total, err := service.ListTracks(ctx, params)
	assert.NoError(t, err)
	assert.Equal(t, int64(6), total)
	assert.Len(t, tracks, 6)

	// Test: Filter by format
	fmtMp3 := "mp3"
	params.Format = &fmtMp3
	tracks, total, err = service.ListTracks(ctx, params)
	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
}

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

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "pathuser", Email: "path@example.com"}
	db.Create(user)

	ctx := context.Background()
	filePath := "/tmp/some/file.mp3"
	filename := "file.mp3"
	fileSize := int64(12345)
	format := "mp3"

	track, err := service.CreateTrackFromPath(ctx, userID, filePath, filename, fileSize, format)
	assert.NoError(t, err)
	assert.NotNil(t, track)
	assert.Equal(t, models.TrackStatusUploading, track.Status)
	assert.Equal(t, filePath, track.FilePath)

	// Verify in DB
	var dbTrack models.Track
	err = db.First(&dbTrack, "id = ?", track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, fileSize, dbTrack.FileSize)
}

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

	userID := uuid.New()
	db.Create(&models.User{ID: userID})
	trackID := uuid.New()
	db.Create(&models.Track{ID: trackID, UserID: userID, Status: models.TrackStatusProcessing})

	ctx := context.Background()
	err := service.UpdateStreamStatus(ctx, trackID, "ready", "http://manifest.url")
	assert.NoError(t, err)

	var track models.Track
	db.First(&track, "id = ?", trackID)
	assert.Equal(t, models.TrackStatusCompleted, track.Status)
	assert.Equal(t, "ready", track.StreamStatus)
	assert.Equal(t, "http://manifest.url", track.StreamManifestURL)

	// Test error status
	err = service.UpdateStreamStatus(ctx, trackID, "error", "")
	assert.NoError(t, err)
	db.First(&track, "id = ?", trackID)
	assert.Equal(t, models.TrackStatusFailed, track.Status)
	assert.Equal(t, "error", track.StreamStatus)
}

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

	userID := uuid.New()
	db.Create(&models.User{ID: userID})

	ids := []uuid.UUID{uuid.New(), uuid.New(), uuid.New()}
	for _, id := range ids {
		db.Create(&models.Track{ID: id, UserID: userID, Title: "Original", Status: models.TrackStatusCompleted})
	}

	ctx := context.Background()

	// Batch Update
	updates := map[string]interface{}{
		"title": "Batch Updated",
	}
	result, err := service.BatchUpdateTracks(ctx, ids, userID, updates)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(result.Updated))

	var tracks []models.Track
	db.Find(&tracks, "id IN ?", ids)
	for _, tr := range tracks {
		assert.Equal(t, "Batch Updated", tr.Title)
	}

	// Batch Delete
	deleteResult, err := service.BatchDeleteTracks(ctx, ids, userID)
	assert.NoError(t, err)
	assert.Equal(t, 3, len(deleteResult.Deleted))

	var count int64
	db.Model(&models.Track{}).Where("id IN ?", ids).Count(&count)
	assert.Equal(t, int64(0), count)
}

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

	userID := uuid.New()
	db.Create(&models.User{ID: userID, Username: "getuser", Email: "get@example.com"})

	trackID := uuid.New()
	// Pre-create track
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		Status:   models.TrackStatusCompleted,
		IsPublic: true,
	}
	db.Create(track)

	ctx := context.Background()

	// Test: Success
	found, err := service.GetTrackByID(ctx, trackID)
	assert.NoError(t, err)
	assert.Equal(t, trackID, found.ID)
	assert.Equal(t, "Test Track", found.Title)

	// Test: NotFound
	_, err = service.GetTrackByID(ctx, uuid.New())
	assert.ErrorIs(t, err, ErrTrackNotFound)
}

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

	ownerID := uuid.New()
	otherID := uuid.New()
	db.Create(&models.User{ID: ownerID, Username: "owner", Email: "owner@example.com"})
	db.Create(&models.User{ID: otherID, Username: "other", Email: "other@example.com"})

	trackID := uuid.New()
	db.Create(&models.Track{
		ID:       trackID,
		UserID:   ownerID,
		Title:    "Original Title",
		Genre:    "Pop",
		IsPublic: true,
	})

	ctx := context.Background()

	// Test: Update Success (Owner)
	newTitle := "Updated Title"
	newGenre := "Rock"
	isPublic := false
	params := UpdateTrackParams{
		Title:    &newTitle,
		Genre:    &newGenre,
		IsPublic: &isPublic,
	}
	updated, err := service.UpdateTrack(ctx, trackID, ownerID, params)
	assert.NoError(t, err)
	assert.Equal(t, "Updated Title", updated.Title)
	assert.Equal(t, "Rock", updated.Genre)
	assert.False(t, updated.IsPublic)

	// Test: Forbidden (Other User)
	params2 := UpdateTrackParams{Title: &newTitle}
	_, err = service.UpdateTrack(ctx, trackID, otherID, params2)
	assert.ErrorIs(t, err, ErrForbidden)

	// Test: Admin Override
	// (Assuming context key "is_admin" works as implemented in service)
	adminCtx := context.WithValue(ctx, "is_admin", true)
	adminTitle := "Admin Title"
	params3 := UpdateTrackParams{Title: &adminTitle}
	updatedAdmin, err := service.UpdateTrack(adminCtx, trackID, otherID, params3)
	assert.NoError(t, err)
	assert.Equal(t, "Admin Title", updatedAdmin.Title)
}

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

	ownerID := uuid.New()
	otherID := uuid.New()
	db.Create(&models.User{ID: ownerID})
	db.Create(&models.User{ID: otherID})

	// Create file for deletion test
	tmpFile, err := os.CreateTemp(service.uploadDir, "track_*.mp3")
	require.NoError(t, err)
	tmpFile.Close()
	filePath := tmpFile.Name()

	trackID := uuid.New()
	db.Create(&models.Track{
		ID:       trackID,
		UserID:   ownerID,
		FilePath: filePath,
	})

	ctx := context.Background()

	// Test: Forbidden
	err = service.DeleteTrack(ctx, trackID, otherID)
	assert.ErrorIs(t, err, ErrForbidden)

	// Check file still exists
	_, err = os.Stat(filePath)
	assert.NoError(t, err)

	// Test: Success
	err = service.DeleteTrack(ctx, trackID, ownerID)
	assert.NoError(t, err)

	// Verify DB deletion
	var count int64
	db.Model(&models.Track{}).Where("id = ?", trackID).Count(&count)
	assert.Equal(t, int64(0), count)

	// Verify File deletion
	_, err = os.Stat(filePath)
	assert.True(t, os.IsNotExist(err))
}

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

	userID := uuid.New()
	db.Create(&models.User{ID: userID})

	ctx := context.Background()

	// Mock file header
	content := []byte{0xFF, 0xFB, 0x00, 0x00} // Fake MP3 frame header
	header := createMultipartFileHeader(t, "upload.mp3", content, "audio/mpeg")

	metadata := TrackMetadata{
		Title:    "Uploaded Track",
		IsPublic: true,
	}

	// Test Upload
	track, err := service.UploadTrack(ctx, userID, header, metadata)
	assert.NoError(t, err)
	assert.NotNil(t, track)
	assert.Equal(t, "Uploaded Track", track.Title)
	assert.Equal(t, models.TrackStatusUploading, track.Status)
	assert.NotEmpty(t, track.FilePath)

	// Verify DB
	var dbTrack models.Track
	db.First(&dbTrack, "id = ?", track.ID)
	assert.Equal(t, "Uploaded Track", dbTrack.Title)

	// Wait for async processing to finish to avoid "Log in goroutine after Test has completed"
	assert.Eventually(t, func() bool {
		db.First(&dbTrack, "id = ?", track.ID)
		return dbTrack.Status != models.TrackStatusUploading
	}, 2*time.Second, 100*time.Millisecond)
}
