//go:build security
// +build security

package security

import (
	"bytes"
	"encoding/json"
	"fmt"
	"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/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"veza-backend-api/internal/core/track"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/handlers"
	"veza-backend-api/internal/middleware"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"veza-backend-api/internal/services"

	"github.com/golang-jwt/jwt/v5"
)

// setupAuthorizationTestRouter crée un router de test avec authentification complète
func setupAuthorizationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *services.JWTService, map[uuid.UUID]*models.User, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

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

	// Auto-migrate models
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.Playlist{},
		&models.PlaylistTrack{},
		&models.RefreshToken{},
		&models.Session{},
		&models.Role{},
		&models.UserRole{},
		&models.Permission{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	dbWrapper := &database.Database{GormDB: db}

	// Create test users with different roles
	users := make(map[uuid.UUID]*models.User)

	// Regular user
	regularUserID := uuid.New()
	regularUser := &models.User{
		ID:           regularUserID,
		Email:        "user@example.com",
		Username:     "regularuser",
		PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890",
		IsVerified:   true,
		TokenVersion: 1,
	}
	err = db.Create(regularUser).Error
	require.NoError(t, err)
	users[regularUserID] = regularUser

	// Admin user
	adminUserID := uuid.New()
	adminUser := &models.User{
		ID:           adminUserID,
		Email:        "admin@example.com",
		Username:     "adminuser",
		PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890",
		IsVerified:   true,
		TokenVersion: 1,
	}
	err = db.Create(adminUser).Error
	require.NoError(t, err)
	users[adminUserID] = adminUser

	// Creator user
	creatorUserID := uuid.New()
	creatorUser := &models.User{
		ID:           creatorUserID,
		Email:        "creator@example.com",
		Username:     "creatoruser",
		PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890",
		IsVerified:   true,
		TokenVersion: 1,
	}
	err = db.Create(creatorUser).Error
	require.NoError(t, err)
	users[creatorUserID] = creatorUser

	// Setup roles and permissions
	adminRole := &models.Role{Name: "admin", Description: "Administrator"}
	creatorRole := &models.Role{Name: "creator", Description: "Content Creator"}
	premiumRole := &models.Role{Name: "premium", Description: "Premium User"}
	err = db.Create(adminRole).Error
	require.NoError(t, err)
	err = db.Create(creatorRole).Error
	require.NoError(t, err)
	err = db.Create(premiumRole).Error
	require.NoError(t, err)

	// Assign roles
	adminUserRole := &models.UserRole{UserID: adminUserID, RoleID: adminRole.ID}
	creatorUserRole := &models.UserRole{UserID: creatorUserID, RoleID: creatorRole.ID}
	err = db.Create(adminUserRole).Error
	require.NoError(t, err)
	err = db.Create(creatorUserRole).Error
	require.NoError(t, err)

	// Setup services
	jwtService, err := services.NewJWTService("test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience")
	require.NoError(t, err)

	sessionService := services.NewSessionService(dbWrapper, logger)
	permissionService := services.NewPermissionService(db)
	auditService := services.NewAuditService(dbWrapper, logger)

	// Create auth middleware
	authMiddleware := middleware.NewAuthMiddleware(
		sessionService,
		auditService,
		permissionService,
		jwtService,
		services.NewUserServiceWithDB(repositories.NewGormUserRepository(db), db),
		logger,
	)

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

	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)
	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	playlistHandler := handlers.NewPlaylistHandler(playlistService, db, logger)

	userService := services.NewUserServiceWithDB(userRepo, db)
	profileHandler := handlers.NewProfileHandler(userService, logger)

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

	// Public routes
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	// Protected routes
	protected := router.Group("/api/v1")
	protected.Use(authMiddleware.RequireAuth())

	// Admin routes
	admin := protected.Group("/admin")
	admin.Use(authMiddleware.RequireAdmin())
	{
		admin.GET("/users", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "admin only"})
		})
	}

	// Content creator routes
	creator := protected.Group("/tracks")
	creator.POST("", authMiddleware.RequireContentCreatorRole(), trackHandler.UploadTrack)

	// Track routes with ownership
	tracks := protected.Group("/tracks")
	{
		tracks.GET("", trackHandler.ListTracks)
		tracks.GET("/:id", trackHandler.GetTrack)

		// Ownership resolver for tracks
		trackOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
			trackIDStr := c.Param("id")
			trackID, err := uuid.Parse(trackIDStr)
			if err != nil {
				return uuid.Nil, err
			}
			track, err := trackService.GetTrackByID(c.Request.Context(), trackID)
			if err != nil {
				return uuid.Nil, err
			}
			return track.UserID, nil
		}
		tracks.PUT("/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), trackHandler.UpdateTrack)
		tracks.DELETE("/:id", authMiddleware.RequireOwnershipOrAdmin("track", trackOwnerResolver), trackHandler.DeleteTrack)
	}

	// Playlist routes
	playlists := protected.Group("/playlists")
	{
		playlists.GET("", playlistHandler.GetPlaylists)
		playlists.POST("", playlistHandler.CreatePlaylist)
		playlists.GET("/:id", playlistHandler.GetPlaylist)
	}

	// User routes
	usersGroup := protected.Group("/users")
	{
		usersGroup.GET("", profileHandler.ListUsers)
		usersGroup.GET("/:id", profileHandler.GetProfile)

		// Ownership resolver for users
		userOwnerResolver := func(c *gin.Context) (uuid.UUID, error) {
			userIDStr := c.Param("id")
			userID, err := uuid.Parse(userIDStr)
			if err != nil {
				return uuid.Nil, err
			}
			return userID, nil
		}
		usersGroup.PUT("/:id", authMiddleware.RequireOwnershipOrAdmin("user", userOwnerResolver), profileHandler.UpdateProfile)
	}

	cleanup := func() {
		// Cleanup handled by t.TempDir()
	}

	return router, db, jwtService, users, cleanup
}

// generateToken génère un token JWT pour un utilisateur
func generateToken(jwtService *services.JWTService, user *models.User) (string, error) {
	return jwtService.GenerateAccessToken(user)
}

// TestAuthorization_NoToken teste que les requêtes sans token sont rejetées
func TestAuthorization_NoToken(t *testing.T) {
	router, _, _, _, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Test various protected endpoints without token
	endpoints := []struct {
		method string
		path   string
	}{
		{"GET", "/api/v1/tracks"},
		{"GET", "/api/v1/tracks/123e4567-e89b-12d3-a456-426614174000"},
		{"POST", "/api/v1/tracks"},
		{"PUT", "/api/v1/tracks/123e4567-e89b-12d3-a456-426614174000"},
		{"DELETE", "/api/v1/tracks/123e4567-e89b-12d3-a456-426614174000"},
		{"GET", "/api/v1/admin/users"},
		{"GET", "/api/v1/users"},
		{"PUT", "/api/v1/users/123e4567-e89b-12d3-a456-426614174000"},
	}

	for _, endpoint := range endpoints {
		t.Run(fmt.Sprintf("%s %s", endpoint.method, endpoint.path), func(t *testing.T) {
			req := httptest.NewRequest(endpoint.method, endpoint.path, nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, http.StatusUnauthorized, w.Code,
				"Request without token should be rejected with 401")
		})
	}
}

// TestAuthorization_InvalidToken teste que les requêtes avec token invalide sont rejetées
func TestAuthorization_InvalidToken(t *testing.T) {
	router, _, _, _, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	invalidTokens := []string{
		"invalid-token",
		"Bearer invalid-token",
		"not-a-bearer-token",
		"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalid",
		"",
	}

	for _, token := range invalidTokens {
		t.Run(fmt.Sprintf("Invalid token: %s", token), func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks", nil)
			if token != "" {
				req.Header.Set("Authorization", token)
			}
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, http.StatusUnauthorized, w.Code,
				"Request with invalid token should be rejected with 401")
		})
	}
}

// TestAuthorization_ExpiredToken teste que les requêtes avec token expiré sont rejetées
func TestAuthorization_ExpiredToken(t *testing.T) {
	router, _, _, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Create expired token manually
	userID := uuid.New()
	for uid := range users {
		userID = uid
		break
	}
	user := users[userID]

	// Create expired claims manually
	claims := models.CustomClaims{
		UserID:       user.ID,
		Email:        user.Email,
		Username:     user.Username,
		Role:         user.Role,
		TokenVersion: user.TokenVersion,
		TokenType:    "access",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)), // Expired
			IssuedAt:  jwt.NewNumericDate(time.Now().Add(-2 * time.Hour)),
			Issuer:    "test-issuer",
			Audience:  jwt.ClaimStrings{"test-audience"},
			ID:        uuid.NewString(),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	expiredToken, err := token.SignedString([]byte("test-secret-key-must-be-32-chars-long"))
	require.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks", nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", expiredToken))
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code,
		"Request with expired token should be rejected with 401")
}

// TestAuthorization_RegularUser_AdminEndpoint teste qu'un utilisateur régulier ne peut pas accéder aux endpoints admin
func TestAuthorization_RegularUser_AdminEndpoint(t *testing.T) {
	router, _, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Find regular user (not admin)
	var regularUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "user@example.com" {
			regularUserID = uid
			break
		}
	}

	regularUser := users[regularUserID]
	token, err := generateToken(jwtService, regularUser)
	require.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/users", nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code,
		"Regular user should not be able to access admin endpoints")
}

// TestAuthorization_RegularUser_CreatorEndpoint teste qu'un utilisateur régulier ne peut pas créer de contenu
func TestAuthorization_RegularUser_CreatorEndpoint(t *testing.T) {
	router, _, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Find regular user
	var regularUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "user@example.com" {
			regularUserID = uid
			break
		}
	}

	regularUser := users[regularUserID]
	token, err := generateToken(jwtService, regularUser)
	require.NoError(t, err)

	// Try to upload a track (requires creator role)
	payload := map[string]interface{}{
		"title":  "Test Track",
		"artist": "Test Artist",
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", bytes.NewBuffer(body))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code,
		"Regular user should not be able to create content")
}

// TestAuthorization_AdminUser_AdminEndpoint teste qu'un admin peut accéder aux endpoints admin
func TestAuthorization_AdminUser_AdminEndpoint(t *testing.T) {
	router, _, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Find admin user
	var adminUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "admin@example.com" {
			adminUserID = uid
			break
		}
	}

	adminUser := users[adminUserID]
	token, err := generateToken(jwtService, adminUser)
	require.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/users", nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code,
		"Admin user should be able to access admin endpoints")
}

// TestAuthorization_CreatorUser_CreatorEndpoint teste qu'un creator peut créer du contenu
func TestAuthorization_CreatorUser_CreatorEndpoint(t *testing.T) {
	router, _, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Find creator user
	var creatorUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "creator@example.com" {
			creatorUserID = uid
			break
		}
	}

	creatorUser := users[creatorUserID]
	token, err := generateToken(jwtService, creatorUser)
	require.NoError(t, err)

	// Try to upload a track (requires creator role)
	payload := map[string]interface{}{
		"title":  "Test Track",
		"artist": "Test Artist",
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", bytes.NewBuffer(body))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should not be forbidden (may be 400/422 for validation, but not 403)
	assert.NotEqual(t, http.StatusForbidden, w.Code,
		"Creator user should be able to create content")
}

// TestAuthorization_Ownership_OtherUserResource teste qu'un utilisateur ne peut pas modifier les ressources d'un autre utilisateur
func TestAuthorization_Ownership_OtherUserResource(t *testing.T) {
	router, db, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Create a track owned by creator user
	var creatorUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "creator@example.com" {
			creatorUserID = uid
			break
		}
	}

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   creatorUserID,
		Title:    "Creator's Track",
		IsPublic: true,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Try to update as regular user
	var regularUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "user@example.com" {
			regularUserID = uid
			break
		}
	}

	regularUser := users[regularUserID]
	token, err := generateToken(jwtService, regularUser)
	require.NoError(t, err)

	payload := map[string]interface{}{
		"title": "Hacked Title",
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/tracks/%s", trackID), bytes.NewBuffer(body))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code,
		"User should not be able to modify another user's resource")
}

// TestAuthorization_Ownership_OwnResource teste qu'un utilisateur peut modifier ses propres ressources
func TestAuthorization_Ownership_OwnResource(t *testing.T) {
	router, db, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Create a track owned by regular user
	var regularUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "user@example.com" {
			regularUserID = uid
			break
		}
	}

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   regularUserID,
		Title:    "My Track",
		IsPublic: true,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	regularUser := users[regularUserID]
	token, err := generateToken(jwtService, regularUser)
	require.NoError(t, err)

	payload := map[string]interface{}{
		"title": "Updated Title",
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/tracks/%s", trackID), bytes.NewBuffer(body))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should not be forbidden (may be 400/422 for validation, but not 403)
	assert.NotEqual(t, http.StatusForbidden, w.Code,
		"User should be able to modify their own resource")
}

// TestAuthorization_Admin_OwnershipOverride teste qu'un admin peut modifier n'importe quelle ressource
func TestAuthorization_Admin_OwnershipOverride(t *testing.T) {
	router, db, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Create a track owned by regular user
	var regularUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "user@example.com" {
			regularUserID = uid
			break
		}
	}

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   regularUserID,
		Title:    "User's Track",
		IsPublic: true,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Try to update as admin
	var adminUserID uuid.UUID
	for uid, user := range users {
		if user.Email == "admin@example.com" {
			adminUserID = uid
			break
		}
	}

	adminUser := users[adminUserID]
	token, err := generateToken(jwtService, adminUser)
	require.NoError(t, err)

	payload := map[string]interface{}{
		"title": "Admin Updated Title",
	}
	body, _ := json.Marshal(payload)

	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/tracks/%s", trackID), bytes.NewBuffer(body))
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should not be forbidden (admin can override ownership)
	assert.NotEqual(t, http.StatusForbidden, w.Code,
		"Admin should be able to modify any resource")
}

// TestAuthorization_TokenVersionMismatch teste que les tokens avec version incorrecte sont rejetés
func TestAuthorization_TokenVersionMismatch(t *testing.T) {
	router, db, jwtService, users, cleanup := setupAuthorizationTestRouter(t)
	defer cleanup()

	// Get a user
	var userID uuid.UUID
	for uid := range users {
		userID = uid
		break
	}

	// Get user and generate token with old version
	user := users[userID]
	token, err := generateToken(jwtService, user)
	require.NoError(t, err)

	// Update user's token version (simulating logout or password change)
	err = db.Model(&models.User{}).Where("id = ?", userID).Update("token_version", 2).Error
	require.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks", nil)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code,
		"Token with mismatched version should be rejected")
}
