package middleware

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

	"veza-backend-api/internal/models"

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

// MockPlaylistService est un mock du PlaylistService pour les tests
type MockPlaylistService struct {
	mock.Mock
}

func (m *MockPlaylistService) CheckPermission(ctx context.Context, playlistID, userID uuid.UUID, requiredPermission models.PlaylistPermission) (bool, error) {
	args := m.Called(ctx, playlistID, userID, requiredPermission)
	return args.Bool(0), args.Error(1)
}

// setupPlaylistPermissionTestRouter crée un router de test avec le middleware de permissions
func setupPlaylistPermissionTestRouter(t *testing.T) (*gin.Engine, *MockPlaylistService, func()) {
	gin.SetMode(gin.TestMode)

	// Setup mock service
	mockService := new(MockPlaylistService)

	// Setup router
	router := gin.New()
	router.Use(func(c *gin.Context) {
		// Mock authentication middleware - set user_id from query param
		if userIDStr := c.Query("user_id"); userIDStr != "" {
			if uid, err := uuid.Parse(userIDStr); err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

	// Test endpoint
	router.GET("/test/:id", CheckPlaylistPermission(mockService, models.PlaylistPermissionRead), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	cleanup := func() {
		// Nothing to cleanup
	}

	return router, mockService, cleanup
}

func TestCheckPlaylistPermission_Owner(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Equal(t, "success", response["message"])
	mockService.AssertExpectations(t)
}

func TestCheckPlaylistPermission_PublicRead(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Equal(t, "success", response["message"])
	mockService.AssertExpectations(t)
}

func TestCheckPlaylistPermission_PrivateForbidden(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(false, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	router.ServeHTTP(w, req)

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

func TestCheckPlaylistPermission_CollaboratorRead(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Equal(t, "success", response["message"])
	mockService.AssertExpectations(t)
}

func TestCheckPlaylistPermission_CollaboratorWrite(t *testing.T) {
	gin.SetMode(gin.TestMode)
	mockService := new(MockPlaylistService)
	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionWrite).Return(true, nil)

	routerWrite := gin.New()
	routerWrite.Use(func(c *gin.Context) {
		if userIDStr := c.Query("user_id"); userIDStr != "" {
			if uid, err := uuid.Parse(userIDStr); err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})
	routerWrite.GET("/test/:id", RequirePlaylistWrite(mockService), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	routerWrite.ServeHTTP(w, req)

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

func TestCheckPlaylistPermission_CollaboratorReadCannotWrite(t *testing.T) {
	gin.SetMode(gin.TestMode)
	mockService := new(MockPlaylistService)
	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionWrite).Return(false, nil)

	routerWrite := gin.New()
	routerWrite.Use(func(c *gin.Context) {
		if userIDStr := c.Query("user_id"); userIDStr != "" {
			if uid, err := uuid.Parse(userIDStr); err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})
	routerWrite.GET("/test/:id", RequirePlaylistWrite(mockService), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	routerWrite.ServeHTTP(w, req)

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

func TestCheckPlaylistPermission_NotFound(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()
	userID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(false, fmt.Errorf("playlist not found"))

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil)
	router.ServeHTTP(w, req)

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

func TestCheckPlaylistPermission_Unauthorized(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	playlistID := uuid.New()

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s", playlistID), nil) // Pas de user_id
	router.ServeHTTP(w, req)

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

func TestCheckPlaylistPermission_InvalidPlaylistID(t *testing.T) {
	router, mockService, cleanup := setupPlaylistPermissionTestRouter(t)
	defer cleanup()

	userID := uuid.New()

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/invalid?user_id=%s", userID), nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	// P0: Nouveau format AppError
	errorObj, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Error should be a map")
	assert.Contains(t, errorObj["message"].(string), "invalid playlist id")
	mockService.AssertNotCalled(t, "CheckPermission")
}

func TestRequirePlaylistOwner(t *testing.T) {
	gin.SetMode(gin.TestMode)
	mockService := new(MockPlaylistService)
	playlistID := uuid.New()
	ownerID := uuid.New()
	otherID := uuid.New()

	mockService.On("CheckPermission", mock.Anything, playlistID, ownerID, models.PlaylistPermissionAdmin).Return(true, nil)
	mockService.On("CheckPermission", mock.Anything, playlistID, otherID, models.PlaylistPermissionAdmin).Return(false, nil)

	routerOwner := gin.New()
	routerOwner.Use(func(c *gin.Context) {
		if userIDStr := c.Query("user_id"); userIDStr != "" {
			if uid, err := uuid.Parse(userIDStr); err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})
	routerOwner.GET("/test/:id", RequirePlaylistOwner(mockService), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "success"})
	})

	// Owner peut accéder
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, ownerID), nil)
	routerOwner.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Autre utilisateur ne peut pas accéder
	w2 := httptest.NewRecorder()
	req2 := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, otherID), nil)
	routerOwner.ServeHTTP(w2, req2)
	assert.Equal(t, http.StatusForbidden, w2.Code)

	mockService.AssertExpectations(t)
}
