package middleware

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

// MockRoleService est un mock du RoleService pour les tests RBAC
// Implémente l'interface RoleChecker pour être compatible avec RequireRole
type MockRoleService struct {
	mock.Mock
}

func (m *MockRoleService) HasRole(ctx context.Context, userID uuid.UUID, roleName string) (bool, error) {
	args := m.Called(ctx, userID, roleName)
	return args.Bool(0), args.Error(1)
}

func (m *MockRoleService) HasPermission(ctx context.Context, userID uuid.UUID, resource, action string) (bool, error) {
	args := m.Called(ctx, userID, resource, action)
	return args.Bool(0), args.Error(1)
}

func TestRequireRole_WithValidRole(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(true, nil)

	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockRoleService.AssertExpectations(t)
}

func TestRequireRole_WithInvalidRole(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(false, nil)

	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	assert.Equal(t, "insufficient permissions", errorObj["message"])

	mockRoleService.AssertExpectations(t)
}

func TestRequireRole_WithoutUserID(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	handlerCalled := false
	router := gin.New()
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()
	// user_id not set

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	assert.Equal(t, "unauthorized", errorObj["message"])

	mockRoleService.AssertNotCalled(t, "HasRole")
}

func TestRequireRole_WithServiceError(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(false, assert.AnError)

	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

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

	mockRoleService.AssertExpectations(t)
}

func TestRequireRole_WithIntUserIDType(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	// No expectations - should fail before calling service

	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", 123) // int instead of uuid.UUID
		c.Next()
	})
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	mockRoleService.AssertNotCalled(t, "HasRole")
}

func TestRequirePermission_WithValidPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "create").Return(true, nil)

	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequirePermission(mockRoleService, "tracks", "create"))
	router.POST("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodPost, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	mockRoleService.AssertExpectations(t)
}

func TestRequirePermission_WithInvalidPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "delete").Return(false, nil)

	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequirePermission(mockRoleService, "tracks", "delete"))
	router.DELETE("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodDelete, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	assert.Equal(t, "insufficient permissions", errorObj["message"])

	mockRoleService.AssertExpectations(t)
}

func TestRequirePermission_WithoutUserID(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	handlerCalled := false
	router := gin.New()
	router.Use(RequirePermission(mockRoleService, "tracks", "create"))
	router.POST("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodPost, "/test", nil)
	w := httptest.NewRecorder()
	// user_id not set

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	assert.Equal(t, "unauthorized", errorObj["message"])

	mockRoleService.AssertNotCalled(t, "HasPermission")
}

func TestRequirePermission_WithServiceError(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	userID := uuid.New()
	mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "create").Return(false, assert.AnError)

	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.Use(RequirePermission(mockRoleService, "tracks", "create"))
	router.POST("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodPost, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

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

	mockRoleService.AssertExpectations(t)
}

func TestRequirePermission_WithInvalidUserIDType(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", "invalid") // Invalid type
		c.Next()
	})
	router.Use(RequirePermission(mockRoleService, "tracks", "create"))
	router.POST("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodPost, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Contains(t, response, "error")
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	// "invalid" est une string, donc le code essaie de la parser en UUID et échoue
	// Le message d'erreur est donc "invalid user id format" et non "invalid user id type"
	assert.Equal(t, "invalid user id format", errorObj["message"])

	mockRoleService.AssertNotCalled(t, "HasPermission")
}

func TestRequireRole_WithInvalidUserIDType(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockRoleService := new(MockRoleService)
	handlerCalled := false
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", "invalid") // Invalid type
		c.Next()
	})
	router.Use(RequireRole(mockRoleService, "admin"))
	router.GET("/test", func(c *gin.Context) {
		handlerCalled = true
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	assert.False(t, handlerCalled, "Handler should not be called")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Contains(t, response, "error")
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	// "invalid" est une string, donc le code essaie de la parser en UUID et échoue
	// Le message d'erreur est donc "invalid user id format" et non "invalid user id type"
	assert.Equal(t, "invalid user id format", errorObj["message"])

	mockRoleService.AssertNotCalled(t, "HasRole")
}
