//go:build !integration
// +build !integration

package track

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"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"

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

// setupTestTrackHandlerOwnership crée un handler de test avec une DB en mémoire
func setupTestTrackHandlerOwnership(t *testing.T) (*TrackHandler, *gorm.DB, *services.PermissionService, func()) {
	// DB en mémoire SQLite
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Migrations minimales
	err = db.AutoMigrate(
		&models.Track{},
		&models.User{},
		&models.Role{},
		&models.UserRole{},
	)
	require.NoError(t, err)

	// Services
	logger := zap.NewNop()
	trackService := NewTrackService(db, logger, "/tmp/test-uploads")
	trackUploadService := &services.TrackUploadService{} // Mock minimal
	chunkService := &services.TrackChunkService{}        // Mock minimal
	likeService := &services.TrackLikeService{}          // Mock minimal
	streamService := &services.StreamService{}           // Mock minimal
	permissionService := services.NewPermissionService(db)

	// Handler
	handler := NewTrackHandler(
		trackService,
		trackUploadService,
		chunkService,
		likeService,
		streamService,
	)
	handler.SetPermissionService(permissionService)

	cleanup := func() {
		sqlDB, _ := db.DB()
		if sqlDB != nil {
			sqlDB.Close()
		}
	}

	return handler, db, permissionService, cleanup
}

// createAdminRole crée un rôle admin dans la DB
func createAdminRole(t *testing.T, db *gorm.DB) uuid.UUID {
	adminRole := &models.Role{
		ID:        uuid.New(),
		Name:      "admin",
		IsActive:  true,
		CreatedAt: time.Now(),
	}
	err := db.Create(adminRole).Error
	require.NoError(t, err)
	return adminRole.ID
}

// assignAdminRole assigne le rôle admin à un utilisateur
func assignAdminRole(t *testing.T, db *gorm.DB, userID uuid.UUID, roleID uuid.UUID) {
	userRole := &models.UserRole{
		ID:         uuid.New(),
		UserID:     userID,
		RoleID:     roleID,
		RoleName:   "admin",
		AssignedAt: time.Now(),
		IsActive:   true,
	}
	err := db.Create(userRole).Error
	require.NoError(t, err)
}

// TestUpdateTrack_UserAModifiesTrackOfUserB_Returns403 vérifie qu'un user A ne peut pas modifier un track de user B
func TestUpdateTrack_UserAModifiesTrackOfUserB_Returns403(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer deux utilisateurs
	userAID := uuid.New()
	userBID := uuid.New()

	// Créer un track appartenant à userB
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userBID,
		Title:    "User B's 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)

	// Requête PUT de user A pour modifier le track de user B
	updateData := map[string]interface{}{
		"title": "Hacked Title",
	}
	body, _ := json.Marshal(updateData)

	req := httptest.NewRequest("PUT", "/tracks/"+track.ID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", userAID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.UpdateTrack(c)

	// Vérifier : doit retourner 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	// MOD-P1-RES-001: Le format d'erreur est maintenant structuré (RespondWithAppError)
	errorObj, ok := response["error"].(map[string]interface{})
	if ok {
		// Format structuré : {"code": 1003, "message": "forbidden", ...}
		assert.Contains(t, errorObj, "message", "Error should contain 'message' field")
		assert.Equal(t, "forbidden", errorObj["message"])
	} else {
		// Format simple (fallback) : "forbidden"
		assert.Equal(t, "forbidden", response["error"])
	}

	// Vérifier que le track n'a PAS été modifié
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	require.NoError(t, err)
	assert.Equal(t, "User B's Track", updatedTrack.Title) // Titre original inchangé
}

// TestUpdateTrack_AdminModifiesTrackOfUserB_Returns200 vérifie qu'un admin peut modifier un track de n'importe quel user
func TestUpdateTrack_AdminModifiesTrackOfUserB_Returns200(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer deux utilisateurs
	adminID := uuid.New()
	userBID := uuid.New()

	// Créer un rôle admin et l'assigner à adminID
	adminRoleID := createAdminRole(t, db)
	assignAdminRole(t, db, adminID, adminRoleID)

	// Créer un track appartenant à userB
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userBID,
		Title:    "User B's 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)

	// Requête PUT de admin pour modifier le track de user B
	updateData := map[string]interface{}{
		"title": "Admin Updated Title",
	}
	body, _ := json.Marshal(updateData)

	req := httptest.NewRequest("PUT", "/tracks/"+track.ID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", adminID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.UpdateTrack(c)

	// Vérifier : doit retourner 200 OK
	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
	data := response["data"].(map[string]interface{})
	assert.Contains(t, data, "track")

	// Vérifier que le track a été modifié
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	require.NoError(t, err)
	assert.Equal(t, "Admin Updated Title", updatedTrack.Title)
}

// TestUpdateTrack_UserModifiesOwnTrack_Returns200 vérifie qu'un user peut modifier son propre track
func TestUpdateTrack_UserModifiesOwnTrack_Returns200(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer un utilisateur
	userID := uuid.New()

	// Créer un track appartenant à l'utilisateur
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userID,
		Title:    "My 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)

	// Requête PUT de l'utilisateur pour modifier son propre track
	updateData := map[string]interface{}{
		"title": "Updated My Track",
	}
	body, _ := json.Marshal(updateData)

	req := httptest.NewRequest("PUT", "/tracks/"+track.ID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", userID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.UpdateTrack(c)

	// Vérifier : doit retourner 200 OK
	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
	data := response["data"].(map[string]interface{})
	assert.Contains(t, data, "track")

	// Vérifier que le track a été modifié
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	require.NoError(t, err)
	assert.Equal(t, "Updated My Track", updatedTrack.Title)
}

// TestDeleteTrack_UserADeletesTrackOfUserB_Returns403 vérifie qu'un user A ne peut pas supprimer un track de user B
func TestDeleteTrack_UserADeletesTrackOfUserB_Returns403(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer deux utilisateurs
	userAID := uuid.New()
	userBID := uuid.New()

	// Créer un track appartenant à userB
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userBID,
		Title:    "User B's 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)

	// Requête DELETE de user A pour supprimer le track de user B
	req := httptest.NewRequest("DELETE", "/tracks/"+track.ID.String(), nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", userAID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.DeleteTrack(c)

	// Vérifier : doit retourner 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	// MOD-P1-RES-001: Le format d'erreur est maintenant structuré (RespondWithAppError)
	errorObj, ok := response["error"].(map[string]interface{})
	if ok {
		// Format structuré : {"code": 1003, "message": "forbidden", ...}
		assert.Contains(t, errorObj, "message", "Error should contain 'message' field")
		assert.Equal(t, "forbidden", errorObj["message"])
	} else {
		// Format simple (fallback) : "forbidden"
		assert.Equal(t, "forbidden", response["error"])
	}

	// Vérifier que le track n'a PAS été supprimé
	var deletedTrack models.Track
	err = db.First(&deletedTrack, track.ID).Error
	require.NoError(t, err)
	assert.Equal(t, track.ID, deletedTrack.ID)
}

// TestDeleteTrack_AdminDeletesTrackOfUserB_Returns200 vérifie qu'un admin peut supprimer un track de n'importe quel user
func TestDeleteTrack_AdminDeletesTrackOfUserB_Returns200(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer deux utilisateurs
	adminID := uuid.New()
	userBID := uuid.New()

	// Créer un rôle admin et l'assigner à adminID
	adminRoleID := createAdminRole(t, db)
	assignAdminRole(t, db, adminID, adminRoleID)

	// Créer un track appartenant à userB
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userBID,
		Title:    "User B's 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)

	// Requête DELETE de admin pour supprimer le track de user B
	req := httptest.NewRequest("DELETE", "/tracks/"+track.ID.String(), nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", adminID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.DeleteTrack(c)

	// Vérifier : doit retourner 200 OK
	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
	data := response["data"].(map[string]interface{})
	assert.Contains(t, data, "message")

	// Vérifier que le track a été supprimé
	var deletedTrack models.Track
	err = db.First(&deletedTrack, track.ID).Error
	assert.Error(t, err) // Doit retourner une erreur (record not found)
}

// TestDeleteTrack_UserDeletesOwnTrack_Returns200 vérifie qu'un user peut supprimer son propre track
func TestDeleteTrack_UserDeletesOwnTrack_Returns200(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler, db, _, cleanup := setupTestTrackHandlerOwnership(t)
	defer cleanup()

	// Créer un utilisateur
	userID := uuid.New()

	// Créer un track appartenant à l'utilisateur
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userID,
		Title:    "My 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)

	// Requête DELETE de l'utilisateur pour supprimer son propre track
	req := httptest.NewRequest("DELETE", "/tracks/"+track.ID.String(), nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Set("user_id", userID)
	c.Params = gin.Params{gin.Param{Key: "id", Value: track.ID.String()}}

	// Exécuter
	handler.DeleteTrack(c)

	// Vérifier : doit retourner 200 OK
	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
	data := response["data"].(map[string]interface{})
	assert.Contains(t, data, "message")

	// Vérifier que le track a été supprimé
	var deletedTrack models.Track
	err = db.First(&deletedTrack, track.ID).Error
	assert.Error(t, err) // Doit retourner une erreur (record not found)
}
