package handlers

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

	"veza-backend-api/internal/common"
	"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/mock"
	"go.uber.org/zap"
)

// MockRoleService implements RoleService interface for testing
type MockRoleService struct {
	mock.Mock
}

func (m *MockRoleService) GetRoles(ctx context.Context) ([]models.Role, error) {
	args := m.Called(ctx)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.Role), args.Error(1)
}

func (m *MockRoleService) GetRole(ctx context.Context, roleID uuid.UUID) (*models.Role, error) {
	args := m.Called(ctx, roleID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.Role), args.Error(1)
}

func (m *MockRoleService) CreateRole(ctx context.Context, role *models.Role) error {
	args := m.Called(ctx, role)
	return args.Error(0)
}

func (m *MockRoleService) UpdateRole(ctx context.Context, roleID uuid.UUID, updates *models.Role) error {
	args := m.Called(ctx, roleID, updates)
	return args.Error(0)
}

func (m *MockRoleService) DeleteRole(ctx context.Context, roleID uuid.UUID) error {
	args := m.Called(ctx, roleID)
	return args.Error(0)
}

func (m *MockRoleService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID, assignedBy uuid.UUID, expiresAt *time.Time) error {
	args := m.Called(ctx, userID, roleID, assignedBy, expiresAt)
	return args.Error(0)
}

func (m *MockRoleService) RevokeRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
	args := m.Called(ctx, userID, roleID)
	return args.Error(0)
}

func (m *MockRoleService) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]models.Role, error) {
	args := m.Called(ctx, userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]models.Role), args.Error(1)
}

func setupTestRoleRouter(mockService *MockRoleService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zap.NewNop()
	handler := NewRoleHandlerWithInterface(mockService, logger)

	api := router.Group("/api/v1")
	api.Use(func(c *gin.Context) {
		// Mock auth middleware - set user_id from header if present
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				common.SetUserIDInContext(c, uid)
			}
		}
		c.Next()
	})
	{
		api.GET("/roles", handler.GetRoles)
		api.GET("/roles/:id", handler.GetRole)
		api.POST("/roles", handler.CreateRole)
		api.PUT("/roles/:id", handler.UpdateRole)
		api.DELETE("/roles/:id", handler.DeleteRole)
		api.POST("/users/:userId/roles", handler.AssignRole)
		api.DELETE("/users/:userId/roles/:roleId", handler.RevokeRole)
		api.GET("/users/:id/roles", handler.GetUserRoles)
	}

	return router
}

func TestRoleHandler_GetRoles_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	expectedRoles := []models.Role{
		{
			ID:          uuid.New(),
			Name:        "admin",
			Description: "Administrator role",
			IsSystem:    true,
		},
		{
			ID:          uuid.New(),
			Name:        "user",
			Description: "Regular user role",
			IsSystem:    true,
		},
	}

	mockService.On("GetRoles", mock.Anything).Return(expectedRoles, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/roles", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_GetRoles_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	mockService.On("GetRoles", mock.Anything).Return(nil, assert.AnError)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/roles", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_GetRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()
	expectedRole := &models.Role{
		ID:          roleID,
		Name:        "admin",
		Description: "Administrator role",
		IsSystem:    true,
	}

	mockService.On("GetRole", mock.Anything, roleID).Return(expectedRole, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_GetRole_InvalidID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/roles/invalid", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "GetRole")
}

func TestRoleHandler_GetRole_NotFound(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	mockService.On("GetRole", mock.Anything, roleID).Return(nil, assert.AnError)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_CreateRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	role := models.Role{
		Name:        "moderator",
		Description: "Moderator role",
		IsSystem:    false,
	}

	mockService.On("CreateRole", mock.Anything, &role).Return(nil)

	body, _ := json.Marshal(role)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusCreated, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_CreateRole_InvalidJSON(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer([]byte("invalid json")))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "CreateRole")
}

func TestRoleHandler_CreateRole_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	role := models.Role{
		Name:        "moderator",
		Description: "Moderator role",
		IsSystem:    false,
	}

	mockService.On("CreateRole", mock.Anything, &role).Return(assert.AnError)

	body, _ := json.Marshal(role)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_UpdateRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()
	updates := models.Role{
		Name:        "updated_moderator",
		Description: "Updated moderator role",
	}

	mockService.On("UpdateRole", mock.Anything, roleID, &updates).Return(nil)

	body, _ := json.Marshal(updates)

	// Execute
	req, _ := http.NewRequest("PUT", "/api/v1/roles/"+roleID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_UpdateRole_InvalidID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	updates := models.Role{
		Name: "updated_moderator",
	}
	body, _ := json.Marshal(updates)

	// Execute
	req, _ := http.NewRequest("PUT", "/api/v1/roles/invalid", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "UpdateRole")
}

func TestRoleHandler_UpdateRole_NotFound(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()
	updates := models.Role{
		Name: "updated_moderator",
	}

	mockService.On("UpdateRole", mock.Anything, roleID, &updates).Return(assert.AnError)

	body, _ := json.Marshal(updates)

	// Execute
	req, _ := http.NewRequest("PUT", "/api/v1/roles/"+roleID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_DeleteRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	mockService.On("DeleteRole", mock.Anything, roleID).Return(nil)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_DeleteRole_InvalidID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/roles/invalid", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "DeleteRole")
}

func TestRoleHandler_DeleteRole_NotFound(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	// The handler returns 400 for "role not found" or "cannot delete system role", but 500 for other errors
	mockService.On("DeleteRole", mock.Anything, roleID).Return(assert.AnError)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert - handler returns 500 for generic errors, 400 only for specific error messages
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_DeleteRole_RoleNotFoundMessage(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	// Mock error with specific message that handler checks for 400
	mockService.On("DeleteRole", mock.Anything, roleID).Return(&mockError{message: "role not found"})

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert - handler checks error.Error() == "role not found" for 400
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_DeleteRole_SystemRole(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	// Mock error with specific message that handler checks for 400
	mockService.On("DeleteRole", mock.Anything, roleID).Return(&mockError{message: "cannot delete system role"})

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert - handler checks error.Error() == "cannot delete system role" for 400
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertExpectations(t)
}

// mockError is a simple error type for testing
type mockError struct {
	message string
}

func (e *mockError) Error() string {
	return e.message
}

func TestRoleHandler_AssignRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	userID := uuid.New()
	roleID := uuid.New()
	assignedBy := uuid.New()

	reqBody := map[string]interface{}{
		"role_id": roleID.String(),
	}
	body, _ := json.Marshal(reqBody)

	mockService.On("AssignRoleToUser", mock.Anything, userID, roleID, assignedBy, (*time.Time)(nil)).Return(nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/roles", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", assignedBy.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_AssignRole_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

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

	reqBody := map[string]interface{}{
		"role_id": roleID.String(),
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/roles", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockService.AssertNotCalled(t, "AssignRoleToUser")
}

func TestRoleHandler_AssignRole_InvalidUserID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()
	assignedBy := uuid.New()

	reqBody := map[string]interface{}{
		"role_id": roleID.String(),
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/users/invalid/roles", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", assignedBy.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "AssignRoleToUser")
}

func TestRoleHandler_RevokeRole_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

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

	mockService.On("RevokeRoleFromUser", mock.Anything, userID, roleID).Return(nil)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_RevokeRole_InvalidUserID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	roleID := uuid.New()

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/users/invalid/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "RevokeRoleFromUser")
}

func TestRoleHandler_RevokeRole_InvalidRoleID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	userID := uuid.New()

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/invalid", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "RevokeRoleFromUser")
}

func TestRoleHandler_RevokeRole_NotFound(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

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

	mockService.On("RevokeRoleFromUser", mock.Anything, userID, roleID).Return(assert.AnError)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/"+roleID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestRoleHandler_GetUserRoles_Success(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	userID := uuid.New()
	expectedRoles := []models.Role{
		{
			ID:   uuid.New(),
			Name: "admin",
		},
		{
			ID:   uuid.New(),
			Name: "user",
		},
	}

	mockService.On("GetUserRoles", mock.Anything, userID).Return(expectedRoles, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/roles", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestRoleHandler_GetUserRoles_InvalidID(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/users/invalid/roles", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "GetUserRoles")
}

func TestRoleHandler_GetUserRoles_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockRoleService)
	router := setupTestRoleRouter(mockService)

	userID := uuid.New()

	mockService.On("GetUserRoles", mock.Anything, userID).Return(nil, assert.AnError)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/roles", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestNewRoleHandler(t *testing.T) {
	// Setup
	mockService := &services.RoleService{}
	logger := zap.NewNop()

	// Execute
	handler := NewRoleHandler(mockService, logger)

	// Assert
	assert.NotNil(t, handler)
	assert.NotNil(t, handler.roleService)
	assert.NotNil(t, handler.commonHandler)
}
