//go:build integration
// +build integration

package middleware

import (
	"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"
)

// setupOwnershipIntegrationTestRouter crée un router de test avec le middleware d'ownership
func setupOwnershipIntegrationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *AuthMiddleware, 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 all models
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.Playlist{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

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

	// Setup PermissionService
	permissionService := services.NewPermissionService(db)

	// Setup AuthMiddleware
	authMiddleware := &AuthMiddleware{
		permissionService: permissionService,
		logger:            logger,
	}

	// Create router
	router := gin.New()

	// Mock authentication middleware - set user_id from header
	router.Use(func(c *gin.Context) {
		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()
	})

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

	return router, db, authMiddleware, cleanup
}

// createTestUser crée un utilisateur de test
func createTestUserForOwnership(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(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	if isAdmin {
		// Create admin role
		adminRole := &models.Role{
			ID:        uuid.New(),
			Name:      "admin",
			IsActive:  true,
			IsSystem:  true,
			CreatedAt: time.Now(),
		}
		err = db.FirstOrCreate(adminRole, models.Role{Name: "admin"}).Error
		require.NoError(t, err)

		// Assign admin role to user
		userRole := &models.UserRole{
			ID:         uuid.New(),
			UserID:     userID,
			RoleID:     adminRole.ID,
			RoleName:   "admin",
			IsActive:   true,
			AssignedAt: time.Now(),
		}
		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) *models.Track {
	track := &models.Track{
		ID:        trackID,
		UserID:    userID,
		Title:     "Test Track",
		Artist:    "Test Artist",
		FilePath:  "/tmp/test.mp3",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)
	return track
}

// createTestPlaylist crée une playlist de test
func createTestPlaylistForOwnership(t *testing.T, db *gorm.DB, playlistID uuid.UUID, userID uuid.UUID) *models.Playlist {
	playlist := &models.Playlist{
		ID:          playlistID,
		UserID:      userID,
		Title:       "Test Playlist",
		Description: "Test Description",
		IsPublic:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)
	return playlist
}

// TestOwnershipMiddleware_TrackOwnerAccess teste que le propriétaire d'un track peut y accéder
func TestOwnershipMiddleware_TrackOwnerAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	ownerID := uuid.New()
	createTestUserForOwnership(t, db, ownerID, "owner", false)

	// Create test track
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID)

	// Setup track owner resolver
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err
		}
		return track.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Owner tries to access their own track
	req := httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", ownerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestOwnershipMiddleware_TrackNonOwnerAccess teste qu'un non-propriétaire ne peut pas accéder à un track
func TestOwnershipMiddleware_TrackNonOwnerAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

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

	// Create test track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID)

	// Setup track owner resolver
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err
		}
		return track.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Other user tries to access owner's track
	req := httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", otherUserID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestOwnershipMiddleware_TrackAdminAccess teste qu'un admin peut accéder à n'importe quel track
func TestOwnershipMiddleware_TrackAdminAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

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

	// Create test track owned by owner
	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID)

	// Setup track owner resolver
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err
		}
		return track.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Admin tries to access owner's track
	req := httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", adminID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestOwnershipMiddleware_PlaylistOwnerAccess teste que le propriétaire d'une playlist peut y accéder
func TestOwnershipMiddleware_PlaylistOwnerAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

	// Create test user
	ownerID := uuid.New()
	createTestUserForOwnership(t, db, ownerID, "owner", false)

	// Create test playlist
	playlistID := uuid.New()
	createTestPlaylistForOwnership(t, db, playlistID, ownerID)

	// Setup playlist owner resolver
	playlistOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		playlistIDStr := c.Param("id")
		playlistID, err := uuid.Parse(playlistIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var playlist models.Playlist
		if err := db.First(&playlist, "id = ?", playlistID).Error; err != nil {
			return uuid.Nil, err
		}
		return playlist.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/playlists/:id", authMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Owner tries to access their own playlist
	req := httptest.NewRequest("PUT", "/playlists/"+playlistID.String(), nil)
	req.Header.Set("X-User-ID", ownerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestOwnershipMiddleware_PlaylistNonOwnerAccess teste qu'un non-propriétaire ne peut pas accéder à une playlist
func TestOwnershipMiddleware_PlaylistNonOwnerAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

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

	// Create test playlist owned by owner
	playlistID := uuid.New()
	createTestPlaylistForOwnership(t, db, playlistID, ownerID)

	// Setup playlist owner resolver
	playlistOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		playlistIDStr := c.Param("id")
		playlistID, err := uuid.Parse(playlistIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var playlist models.Playlist
		if err := db.First(&playlist, "id = ?", playlistID).Error; err != nil {
			return uuid.Nil, err
		}
		return playlist.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/playlists/:id", authMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Other user tries to access owner's playlist
	req := httptest.NewRequest("PUT", "/playlists/"+playlistID.String(), nil)
	req.Header.Set("X-User-ID", otherUserID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestOwnershipMiddleware_ResourceNotFound teste que le middleware retourne 404 si la ressource n'existe pas
func TestOwnershipMiddleware_ResourceNotFound(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

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

	// Setup track owner resolver
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err // Resource not found
		}
		return track.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Try to access non-existent track
	nonExistentTrackID := uuid.New()
	req := httptest.NewRequest("PUT", "/tracks/"+nonExistentTrackID.String(), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestOwnershipMiddleware_UnauthenticatedAccess teste que le middleware rejette les requêtes non authentifiées
func TestOwnershipMiddleware_UnauthenticatedAccess(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

	// Create test track
	ownerID := uuid.New()
	createTestUserForOwnership(t, db, ownerID, "owner", false)

	trackID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID)

	// Setup track owner resolver
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err
		}
		return track.UserID, nil
	}

	// Setup route with ownership middleware
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Test: Try to access without authentication
	req := httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	// No X-User-ID header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

// TestOwnershipMiddleware_CompleteFlow teste le flux complet d'ownership avec différentes ressources
func TestOwnershipMiddleware_CompleteFlow(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, authMiddleware, cleanup := setupOwnershipIntegrationTestRouter(t)
	defer cleanup()

	// Create users: owner, other user, and admin
	ownerID := uuid.New()
	otherUserID := uuid.New()
	adminID := uuid.New()
	createTestUserForOwnership(t, db, ownerID, "owner", false)
	createTestUserForOwnership(t, db, otherUserID, "otheruser", false)
	createTestUserForOwnership(t, db, adminID, "admin", true)

	// Create resources
	trackID := uuid.New()
	playlistID := uuid.New()
	createTestTrackForOwnership(t, db, trackID, ownerID)
	createTestPlaylistForOwnership(t, db, playlistID, ownerID)

	// Setup resolvers
	trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		trackIDStr := c.Param("id")
		trackID, err := uuid.Parse(trackIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var track models.Track
		if err := db.First(&track, "id = ?", trackID).Error; err != nil {
			return uuid.Nil, err
		}
		return track.UserID, nil
	}

	playlistOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
		playlistIDStr := c.Param("id")
		playlistID, err := uuid.Parse(playlistIDStr)
		if err != nil {
			return uuid.Nil, err
		}
		var playlist models.Playlist
		if err := db.First(&playlist, "id = ?", playlistID).Error; err != nil {
			return uuid.Nil, err
		}
		return playlist.UserID, nil
	}

	// Setup routes
	router.PUT("/tracks/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "track updated"})
	})
	router.PUT("/playlists/:id", authMiddleware.RequireOwnershipOrAdmin("playlist", playlistOwnerResolver), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "playlist updated"})
	})

	// Test 1: Owner can access their own resources
	req := httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", ownerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	req = httptest.NewRequest("PUT", "/playlists/"+playlistID.String(), nil)
	req.Header.Set("X-User-ID", ownerID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Test 2: Other user cannot access owner's resources
	req = httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", otherUserID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusForbidden, w.Code)

	req = httptest.NewRequest("PUT", "/playlists/"+playlistID.String(), nil)
	req.Header.Set("X-User-ID", otherUserID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusForbidden, w.Code)

	// Test 3: Admin can access any resource
	req = httptest.NewRequest("PUT", "/tracks/"+trackID.String(), nil)
	req.Header.Set("X-User-ID", adminID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	req = httptest.NewRequest("PUT", "/playlists/"+playlistID.String(), nil)
	req.Header.Set("X-User-ID", adminID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
}
