package middleware

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"veza-backend-api/internal/models"
)

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

func (m *MockPlaylistService) CheckPermission(ctx context.Context, playlistID, userID int64, 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 userID := c.Query("user_id"); userID != "" {
			var uid int64
			_, err := fmt.Sscanf(userID, "%d", &uid)
			if 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()

	mockService.On("CheckPermission", mock.Anything, int64(1), int64(1), models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/1?user_id=1", 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()

	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/1?user_id=2", 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()

	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionRead).Return(false, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/1?user_id=2", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Contains(t, response["error"], "forbidden")
	mockService.AssertExpectations(t)
}

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

	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionRead).Return(true, nil)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/1?user_id=2", 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)
	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionWrite).Return(true, nil)

	routerWrite := gin.New()
	routerWrite.Use(func(c *gin.Context) {
		if userID := c.Query("user_id"); userID != "" {
			var uid int64
			_, err := fmt.Sscanf(userID, "%d", &uid)
			if 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", "/test/1?user_id=2", 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)
	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionWrite).Return(false, nil)

	routerWrite := gin.New()
	routerWrite.Use(func(c *gin.Context) {
		if userID := c.Query("user_id"); userID != "" {
			var uid int64
			_, err := fmt.Sscanf(userID, "%d", &uid)
			if 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", "/test/1?user_id=2", 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()

	mockService.On("CheckPermission", mock.Anything, int64(99999), int64(1), models.PlaylistPermissionRead).Return(false, fmt.Errorf("playlist not found"))

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/99999?user_id=1", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Contains(t, response["error"], "playlist not found")
	mockService.AssertExpectations(t)
}

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

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/1", nil) // Pas de user_id
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Contains(t, response["error"], "unauthorized")
	mockService.AssertNotCalled(t, "CheckPermission")
}

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

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test/invalid?user_id=1", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
	var response map[string]string
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Contains(t, response["error"], "invalid playlist id")
	mockService.AssertNotCalled(t, "CheckPermission")
}

func TestRequirePlaylistOwner(t *testing.T) {
	gin.SetMode(gin.TestMode)
	mockService := new(MockPlaylistService)
	mockService.On("CheckPermission", mock.Anything, int64(1), int64(1), models.PlaylistPermissionAdmin).Return(true, nil)
	mockService.On("CheckPermission", mock.Anything, int64(1), int64(2), models.PlaylistPermissionAdmin).Return(false, nil)

	routerOwner := gin.New()
	routerOwner.Use(func(c *gin.Context) {
		if userID := c.Query("user_id"); userID != "" {
			var uid int64
			_, err := fmt.Sscanf(userID, "%d", &uid)
			if 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", "/test/1?user_id=1", 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", "/test/1?user_id=2", nil)
	routerOwner.ServeHTTP(w2, req2)
	assert.Equal(t, http.StatusForbidden, w2.Code)

	mockService.AssertExpectations(t)
}
