//go:build integration
// +build integration

package track

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

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

// setupTrackUploadIntegrationTestRouter crée un router de test pour les tests d'upload de tracks
func setupTrackUploadIntegrationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *TrackHandler, func()) {
	gin.SetMode(gin.TestMode)

	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

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

	// Auto-migrate - include all models needed
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	// Setup logger
	logger := zap.NewNop()

	// Create upload directories
	uploadDir := "/tmp/test-uploads"
	chunksDir := "/tmp/test-chunks"
	os.MkdirAll(uploadDir, 0755)
	os.MkdirAll(chunksDir, 0755)

	// Setup services
	trackService := NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService(chunksDir, nil, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("http://localhost:8082", logger)
	trackHandler := NewTrackHandler(
		trackService,
		trackUploadService,
		chunkService,
		likeService,
		streamService,
	)

	// Setup PermissionService for admin checks
	permissionService := services.NewPermissionService(db)
	trackHandler.SetPermissionService(permissionService)

	// Setup UploadValidator (without ClamAV for tests)
	uploadConfig := &services.UploadConfig{
		ClamAVEnabled:  false,
		ClamAVRequired: false,
	}
	uploadValidator, err := services.NewUploadValidator(uploadConfig, logger)
	require.NoError(t, err)
	trackHandler.SetUploadValidator(uploadValidator)

	// Create router
	router := gin.New()
	v1 := router.Group("/api/v1")
	{
		// Protected routes with mock auth middleware
		protected := v1.Group("/tracks")
		protected.Use(func(c *gin.Context) {
			// Mock auth middleware - set user_id from header
			userIDStr := c.GetHeader("X-User-ID")
			if userIDStr == "" {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
				c.Abort()
				return
			}
			uid, err := uuid.Parse(userIDStr)
			if err != nil {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user id"})
				c.Abort()
				return
			}
			c.Set("user_id", uid)
			c.Next()
		})
		{
			// Upload endpoints
			protected.POST("", trackHandler.UploadTrack)
			protected.GET("/:id/status", trackHandler.GetUploadStatus)
			protected.POST("/initiate", trackHandler.InitiateChunkedUpload)
			protected.POST("/chunk", trackHandler.UploadChunk)
			protected.POST("/complete", trackHandler.CompleteChunkedUpload)
			protected.GET("/quota/:id", trackHandler.GetUploadQuota)
			protected.GET("/resume/:uploadId", trackHandler.ResumeUpload)
		}
	}

	cleanup := func() {
		// Cleanup test directories
		os.RemoveAll(uploadDir)
		os.RemoveAll(chunksDir)
	}

	return router, db, trackHandler, cleanup
}

// createTestUserForUpload crée un utilisateur de test avec le rôle content creator
func createTestUserForUpload(t *testing.T, db *gorm.DB, userID uuid.UUID, username string) *models.User {
	user := &models.User{
		ID:           userID,
		Username:     username,
		Slug:         username,
		Email:        username + "@example.com",
		PasswordHash: "hashed_password",
		IsActive:     true,
		Role:         "content_creator", // Required for upload
		CreatedAt:    time.Now(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestFile crée un fichier de test pour l'upload
func createTestFile(t *testing.T, filename string, size int) *os.File {
	file, err := os.CreateTemp("", filename)
	require.NoError(t, err)

	// Write some test data
	data := make([]byte, size)
	for i := range data {
		data[i] = byte(i % 256)
	}
	_, err = file.Write(data)
	require.NoError(t, err)

	err = file.Close()
	require.NoError(t, err)

	return file
}

// TestTrackUploadFlow_SimpleUpload tests simple track upload
func TestTrackUploadFlow_SimpleUpload(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, _, cleanup := setupTrackUploadIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	createTestUserForUpload(t, db, userID, "testuser")

	// Create test file
	testFile := createTestFile(t, "test.mp3", 1024)
	defer os.Remove(testFile.Name())

	// Create multipart form
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add file
	fileWriter, err := writer.CreateFormFile("file", "test.mp3")
	require.NoError(t, err)

	file, err := os.Open(testFile.Name())
	require.NoError(t, err)
	defer file.Close()

	_, err = io.Copy(fileWriter, file)
	require.NoError(t, err)

	// Add metadata
	writer.WriteField("title", "Test Track")
	writer.WriteField("artist", "Test Artist")
	writer.WriteField("is_public", "true")

	err = writer.Close()
	require.NoError(t, err)

	// Make request
	req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Should return 202 Accepted (async upload)
	assert.Equal(t, http.StatusAccepted, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.NotNil(t, response["data"])

	data := response["data"].(map[string]interface{})
	assert.Contains(t, data, "track_id")
	assert.Contains(t, data, "status")
}

// TestTrackUploadFlow_ChunkedUpload tests chunked upload flow
func TestTrackUploadFlow_ChunkedUpload(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, _, cleanup := setupTrackUploadIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	createTestUserForUpload(t, db, userID, "testuser")

	// Step 1: Initiate chunked upload
	initiateReq := map[string]interface{}{
		"total_chunks": 3,
		"total_size":   3072, // 3 chunks of 1024 bytes each
		"filename":     "test.mp3",
	}
	initiateBody, _ := json.Marshal(initiateReq)
	initiateHTTPReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/initiate", bytes.NewBuffer(initiateBody))
	initiateHTTPReq.Header.Set("Content-Type", "application/json")
	initiateHTTPReq.Header.Set("X-User-ID", userID.String())
	initiateW := httptest.NewRecorder()

	router.ServeHTTP(initiateW, initiateHTTPReq)

	assert.Equal(t, http.StatusOK, initiateW.Code)
	var initiateResponse map[string]interface{}
	err := json.Unmarshal(initiateW.Body.Bytes(), &initiateResponse)
	require.NoError(t, err)
	assert.NotNil(t, initiateResponse["data"])

	initiateData := initiateResponse["data"].(map[string]interface{})
	uploadID, ok := initiateData["upload_id"].(string)
	require.True(t, ok)
	assert.NotEmpty(t, uploadID)

	// Step 2: Upload chunks
	for chunkNum := 1; chunkNum <= 3; chunkNum++ {
		// Create test chunk file
		chunkFile := createTestFile(t, fmt.Sprintf("chunk%d.bin", chunkNum), 1024)
		defer os.Remove(chunkFile.Name())

		// Create multipart form for chunk
		chunkBody := &bytes.Buffer{}
		chunkWriter := multipart.NewWriter(chunkBody)

		// Add chunk file
		chunkFileWriter, err := chunkWriter.CreateFormFile("chunk", fmt.Sprintf("chunk%d.bin", chunkNum))
		require.NoError(t, err)

		chunkFileHandle, err := os.Open(chunkFile.Name())
		require.NoError(t, err)

		_, err = io.Copy(chunkFileWriter, chunkFileHandle)
		require.NoError(t, err)
		chunkFileHandle.Close()

		// Add form fields
		chunkWriter.WriteField("upload_id", uploadID)
		chunkWriter.WriteField("chunk_number", fmt.Sprintf("%d", chunkNum))
		chunkWriter.WriteField("total_chunks", "3")
		chunkWriter.WriteField("total_size", "3072")
		chunkWriter.WriteField("filename", "test.mp3")

		err = chunkWriter.Close()
		require.NoError(t, err)

		// Make request
		chunkReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/chunk", chunkBody)
		chunkReq.Header.Set("Content-Type", chunkWriter.FormDataContentType())
		chunkReq.Header.Set("X-User-ID", userID.String())
		chunkW := httptest.NewRecorder()

		router.ServeHTTP(chunkW, chunkReq)

		assert.Equal(t, http.StatusOK, chunkW.Code)
		var chunkResponse map[string]interface{}
		err = json.Unmarshal(chunkW.Body.Bytes(), &chunkResponse)
		require.NoError(t, err)
		assert.NotNil(t, chunkResponse["data"])

		chunkData := chunkResponse["data"].(map[string]interface{})
		assert.Equal(t, float64(chunkNum), chunkData["received_chunks"])
	}

	// Step 3: Complete chunked upload
	completeReq := map[string]interface{}{
		"upload_id": uploadID,
	}
	completeBody, _ := json.Marshal(completeReq)
	completeHTTPReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/complete", bytes.NewBuffer(completeBody))
	completeHTTPReq.Header.Set("Content-Type", "application/json")
	completeHTTPReq.Header.Set("X-User-ID", userID.String())
	completeW := httptest.NewRecorder()

	router.ServeHTTP(completeW, completeHTTPReq)

	// Should return 201 Created
	assert.Equal(t, http.StatusCreated, completeW.Code)

	var completeResponse map[string]interface{}
	err = json.Unmarshal(completeW.Body.Bytes(), &completeResponse)
	require.NoError(t, err)
	assert.NotNil(t, completeResponse["data"])

	completeData := completeResponse["data"].(map[string]interface{})
	assert.Contains(t, completeData, "track")
}

// TestTrackUploadFlow_GetUploadStatus tests getting upload status
func TestTrackUploadFlow_GetUploadStatus(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, _, cleanup := setupTrackUploadIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	createTestUserForUpload(t, db, userID, "testuser")

	// Create a test track
	trackID := uuid.New()
	track := &models.Track{
		ID:        trackID,
		UserID:    userID,
		Title:     "Test Track",
		Artist:    "Test Artist",
		FilePath:  "/tmp/test-uploads/test.mp3",
		Status:    models.TrackStatusUploading,
		IsPublic:  true,
		CreatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Get upload status
	statusReq := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks/%s/status", trackID.String()), nil)
	statusReq.Header.Set("X-User-ID", userID.String())
	statusW := httptest.NewRecorder()

	router.ServeHTTP(statusW, statusReq)

	assert.Equal(t, http.StatusOK, statusW.Code)
	var statusResponse map[string]interface{}
	err = json.Unmarshal(statusW.Body.Bytes(), &statusResponse)
	require.NoError(t, err)
	assert.NotNil(t, statusResponse["data"])

	statusData := statusResponse["data"].(map[string]interface{})
	assert.Contains(t, statusData, "progress")
}

// TestTrackUploadFlow_GetUploadQuota tests getting upload quota
func TestTrackUploadFlow_GetUploadQuota(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, _, cleanup := setupTrackUploadIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	createTestUserForUpload(t, db, userID, "testuser")

	// Get upload quota
	quotaReq := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks/quota/%s", userID.String()), nil)
	quotaReq.Header.Set("X-User-ID", userID.String())
	quotaW := httptest.NewRecorder()

	router.ServeHTTP(quotaW, quotaReq)

	assert.Equal(t, http.StatusOK, quotaW.Code)
	var quotaResponse map[string]interface{}
	err := json.Unmarshal(quotaW.Body.Bytes(), &quotaResponse)
	require.NoError(t, err)
	assert.NotNil(t, quotaResponse["data"])

	quotaData := quotaResponse["data"].(map[string]interface{})
	assert.Contains(t, quotaData, "quota")
}

// TestTrackUploadFlow_ResumeUpload tests resuming an interrupted upload
func TestTrackUploadFlow_ResumeUpload(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, _, cleanup := setupTrackUploadIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	userID := uuid.New()
	createTestUserForUpload(t, db, userID, "testuser")

	// Step 1: Initiate chunked upload
	initiateReq := map[string]interface{}{
		"total_chunks": 3,
		"total_size":   3072,
		"filename":     "test.mp3",
	}
	initiateBody, _ := json.Marshal(initiateReq)
	initiateHTTPReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/initiate", bytes.NewBuffer(initiateBody))
	initiateHTTPReq.Header.Set("Content-Type", "application/json")
	initiateHTTPReq.Header.Set("X-User-ID", userID.String())
	initiateW := httptest.NewRecorder()

	router.ServeHTTP(initiateW, initiateHTTPReq)

	assert.Equal(t, http.StatusOK, initiateW.Code)
	var initiateResponse map[string]interface{}
	err := json.Unmarshal(initiateW.Body.Bytes(), &initiateResponse)
	require.NoError(t, err)

	initiateData := initiateResponse["data"].(map[string]interface{})
	uploadID := initiateData["upload_id"].(string)

	// Step 2: Upload only first chunk
	chunkFile := createTestFile(t, "chunk1.bin", 1024)
	defer os.Remove(chunkFile.Name())

	chunkBody := &bytes.Buffer{}
	chunkWriter := multipart.NewWriter(chunkBody)

	chunkFileWriter, err := chunkWriter.CreateFormFile("chunk", "chunk1.bin")
	require.NoError(t, err)

	chunkFileHandle, err := os.Open(chunkFile.Name())
	require.NoError(t, err)

	_, err = io.Copy(chunkFileWriter, chunkFileHandle)
	require.NoError(t, err)
	chunkFileHandle.Close()

	chunkWriter.WriteField("upload_id", uploadID)
	chunkWriter.WriteField("chunk_number", "1")
	chunkWriter.WriteField("total_chunks", "3")
	chunkWriter.WriteField("total_size", "3072")
	chunkWriter.WriteField("filename", "test.mp3")

	err = chunkWriter.Close()
	require.NoError(t, err)

	chunkReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/chunk", chunkBody)
	chunkReq.Header.Set("Content-Type", chunkWriter.FormDataContentType())
	chunkReq.Header.Set("X-User-ID", userID.String())
	chunkW := httptest.NewRecorder()

	router.ServeHTTP(chunkW, chunkReq)
	assert.Equal(t, http.StatusOK, chunkW.Code)

	// Step 3: Resume upload (get state)
	resumeReq := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks/resume/%s", uploadID), nil)
	resumeReq.Header.Set("X-User-ID", userID.String())
	resumeW := httptest.NewRecorder()

	router.ServeHTTP(resumeW, resumeReq)

	assert.Equal(t, http.StatusOK, resumeW.Code)
	var resumeResponse map[string]interface{}
	err = json.Unmarshal(resumeW.Body.Bytes(), &resumeResponse)
	require.NoError(t, err)
	assert.NotNil(t, resumeResponse["data"])

	resumeData := resumeResponse["data"].(map[string]interface{})
	assert.Equal(t, uploadID, resumeData["upload_id"])
	assert.Equal(t, float64(1), resumeData["received_chunks"]) // Only 1 chunk uploaded
}
