//go:build integration
// +build integration

package track

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"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"
)

// setupTrackIntegrationTestRouter crée un router de test pour les tests de track ownership
// BE-SEC-002: Tests d'intégration pour vérification ownership sur tracks
func setupTrackIntegrationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, 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()

	// Setup services
	trackService := NewTrackService(db, logger, "/tmp/test-uploads")
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService("/tmp/test-chunks", 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)

	// 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
			// This simulates RequireAuth() middleware
			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()
		})
		{
			// Note: In real router, RequireOwnershipOrAdmin middleware would be here
			// For this test, we're testing the handler's ownership check directly
			// But we also need to simulate the trackOwnerResolver behavior
			protected.PUT("/:id", func(c *gin.Context) {
				// Simulate trackOwnerResolver: load track and check ownership
				trackIDStr := c.Param("id")
				trackID, err := uuid.Parse(trackIDStr)
				if err != nil {
					c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
					c.Abort()
					return
				}
				track, err := trackService.GetTrackByID(c.Request.Context(), trackID)
				if err != nil {
					c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
					c.Abort()
					return
				}
				// Store track owner ID in context for handler to use
				c.Set("track_owner_id", track.UserID)
				trackHandler.UpdateTrack(c)
			})
			protected.DELETE("/:id", func(c *gin.Context) {
				// Simulate trackOwnerResolver: load track and check ownership
				trackIDStr := c.Param("id")
				trackID, err := uuid.Parse(trackIDStr)
				if err != nil {
					c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
					c.Abort()
					return
				}
				track, err := trackService.GetTrackByID(c.Request.Context(), trackID)
				if err != nil {
					c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
					c.Abort()
					return
				}
				// Store track owner ID in context for handler to use
				c.Set("track_owner_id", track.UserID)
				trackHandler.DeleteTrack(c)
			})
		}
	}

	cleanup := func() {
		// Database will be closed automatically
	}

	return router, db, cleanup
}

// createTestUser crée un utilisateur de test
func createTestUserForTrack(t *testing.T, db *gorm.DB, userID uuid.UUID, username string, isAdmin bool) *models.User {
	user := &models.User{
		ID:           userID,
		Username:     username,
		Slug:         username,
		Email:        username + "@example.com",
		PasswordHash: "hashed_password",
		IsActive:     true,
		CreatedAt:    time.Now(),
	}
	if isAdmin {
		user.Role = "admin"
		user.IsAdmin = true
	} else {
		user.Role = "user"
	}
	// Create user first
	err := db.Create(user).Error
	require.NoError(t, err)

	// Then create admin role and assign it if admin
	if isAdmin {
		// Create admin role and assign it to user
		adminRole := &models.Role{
			Name:        "admin",
			DisplayName: "Administrator",
			IsSystem:    true,
			IsActive:    true,
		}
		err = db.FirstOrCreate(adminRole, models.Role{Name: "admin"}).Error
		require.NoError(t, err)

		userRole := &models.UserRole{
			UserID:   userID,
			RoleID:   adminRole.ID,
			RoleName: "admin",
			IsActive: true,
		}
		err = db.Create(userRole).Error
		require.NoError(t, err)
	}
	return user
}

// createTestTrack crée un track de test
func createTestTrackForOwnership(t *testing.T, db *gorm.DB, trackID uuid.UUID, userID uuid.UUID, title string) *models.Track {
	track := &models.Track{
		ID:        trackID,
		UserID:    userID,
		Title:     title,
		Artist:    "Test Artist",
		FilePath:  "/tmp/test-uploads/test.mp3",
		IsPublic:  true,
		CreatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)
	return track
}

// BE-SEC-002: Test that user cannot update another user's track
func TestUpdateTrack_UserCannotUpdateOtherUserTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create two users
	ownerID := uuid.New()
	otherUserID := uuid.New()
	createTestUserForTrack(t, db, ownerID, "owner", false)
	createTestUserForTrack(t, db, otherUserID, "otheruser", false)

	// Create a track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID, "Owner's Track")

	// Try to update owner's track as otherUser
	updateReq := map[string]interface{}{
		"title": "Hacked Title",
	}
	body, _ := json.Marshal(updateReq)

	req := httptest.NewRequest(http.MethodPut, "/api/v1/tracks/"+trackID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", otherUserID.String()) // otherUser is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should return 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// Error can be a string or a map, check both
	if errorStr, ok := response["error"].(string); ok {
		assert.Contains(t, errorStr, "forbidden")
	} else if errorMap, ok := response["error"].(map[string]interface{}); ok {
		if message, ok := errorMap["message"].(string); ok {
			assert.Contains(t, message, "forbidden")
		}
	}
}

// BE-SEC-002: Test that admin can update any track
func TestUpdateTrack_AdminCanUpdateAnyTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create two users: owner and admin
	ownerID := uuid.New()
	adminID := uuid.New()
	createTestUserForTrack(t, db, ownerID, "owner", false)
	createTestUserForTrack(t, db, adminID, "admin", true)

	// Create a track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID, "Owner's Track")

	// Admin tries to update owner's track
	updateReq := map[string]interface{}{
		"title": "Updated by Admin",
	}
	body, _ := json.Marshal(updateReq)

	req := httptest.NewRequest(http.MethodPut, "/api/v1/tracks/"+trackID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", adminID.String()) // admin is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Admin should be able to update
	assert.Equal(t, http.StatusOK, w.Code)

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

// BE-SEC-002: Test that user can update their own track
func TestUpdateTrack_UserCanUpdateOwnTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create a user
	userID := uuid.New()
	createTestUserForTrack(t, db, userID, "testuser", false)

	// Create a track owned by user
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, userID, "My Track")

	// User tries to update their own track
	updateReq := map[string]interface{}{
		"title": "Updated Title",
	}
	body, _ := json.Marshal(updateReq)

	req := httptest.NewRequest(http.MethodPut, "/api/v1/tracks/"+trackID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String()) // user is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// User should be able to update their own track
	assert.Equal(t, http.StatusOK, w.Code)

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

	// Verify the track was actually updated
	trackData := response["data"].(map[string]interface{})
	track := trackData["track"].(map[string]interface{})
	assert.Equal(t, "Updated Title", track["title"])
}

// BE-SEC-002: Test that user cannot delete another user's track
func TestDeleteTrack_UserCannotDeleteOtherUserTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create two users
	ownerID := uuid.New()
	otherUserID := uuid.New()
	createTestUserForTrack(t, db, ownerID, "owner", false)
	createTestUserForTrack(t, db, otherUserID, "otheruser", false)

	// Create a track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID, "Owner's Track")

	// Try to delete owner's track as otherUser
	req := httptest.NewRequest(http.MethodDelete, "/api/v1/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", otherUserID.String()) // otherUser is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should return 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)
}

// BE-SEC-002: Test that admin can delete any track
func TestDeleteTrack_AdminCanDeleteAnyTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create two users: owner and admin
	ownerID := uuid.New()
	adminID := uuid.New()
	createTestUserForTrack(t, db, ownerID, "owner", false)
	createTestUserForTrack(t, db, adminID, "admin", true)

	// Create a track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID, "Owner's Track")

	// Admin tries to delete owner's track
	req := httptest.NewRequest(http.MethodDelete, "/api/v1/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", adminID.String()) // admin is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Admin should be able to delete
	assert.Equal(t, http.StatusOK, w.Code)
}

// BE-SEC-002: Test that user can delete their own track
func TestDeleteTrack_UserCanDeleteOwnTrack(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupTrackIntegrationTestRouter(t)
	defer cleanup()

	// Create a user
	userID := uuid.New()
	createTestUserForTrack(t, db, userID, "testuser", false)

	// Create a track owned by user
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, userID, "My Track")

	// User tries to delete their own track
	req := httptest.NewRequest(http.MethodDelete, "/api/v1/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", userID.String()) // user is authenticated

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// User should be able to delete their own track
	assert.Equal(t, http.StatusOK, w.Code)

	// Verify the track was actually deleted
	var track models.Track
	err := db.First(&track, "id = ?", trackID).Error
	assert.Error(t, err) // Should not find the track
}
