package handlers

import (
	"bytes"
	"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"
	"go.uber.org/zap/zaptest"

	"veza-backend-api/internal/services"
)

// MockRBACService is a mock implementation of RBACService for testing
type MockRBACService struct {
	mock.Mock
}

func (m *MockRBACService) CreateRole(ctx context.Context, name, description string, permissions []uuid.UUID) (*services.Role, error) {
	args := m.Called(ctx, name, description, permissions)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*services.Role), args.Error(1)
}

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

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

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

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

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

func (m *MockRBACService) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]services.Permission, error) {
	args := m.Called(ctx, userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]services.Permission), args.Error(1)
}

func (m *MockRBACService) CheckPermission(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 (m *MockRBACService) CreatePermission(ctx context.Context, name, description, resource, action string) (*services.Permission, error) {
	args := m.Called(ctx, name, description, resource, action)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*services.Permission), args.Error(1)
}

// setupTestRBACRouter creates a test router with RBAC handlers
func setupTestRBACRouter(mockService *MockRBACService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zaptest.NewLogger(&testing.T{})
	handler := NewRBACHandlersWithInterface(mockService, logger)

	api := router.Group("/api/v1")
	{
		roles := api.Group("/roles")
		{
			roles.POST("", handler.CreateRole)
			roles.GET("", handler.GetAllRoles)
			roles.GET("/:id", handler.GetRole)
		}

		permissions := api.Group("/permissions")
		{
			permissions.POST("", handler.CreatePermission)
		}

		users := api.Group("/users")
		{
			users.POST("/:user_id/roles", handler.AssignRoleToUser)
			users.DELETE("/:user_id/roles/:role_id", handler.RemoveRoleFromUser)
			users.GET("/:user_id/roles", handler.GetUserRoles)
			users.GET("/:user_id/permissions", handler.GetUserPermissions)
			users.GET("/:user_id/permissions/check", handler.CheckPermission)
		}
	}

	return router
}

func TestRBACHandlers_CreateRole_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	roleID := uuid.New()
	expectedRole := &services.Role{
		ID:          roleID,
		Name:        "test-role",
		Description: "Test role description",
		Permissions: []services.Permission{},
		IsSystem:    false,
	}

	mockService.On("CreateRole", mock.Anything, "test-role", "Test role description", []uuid.UUID{}).Return(expectedRole, nil)

	reqBody := map[string]interface{}{
		"name":        "test-role",
		"description": "Test role description",
		"permissions": []uuid.UUID{},
	}
	body, _ := json.Marshal(reqBody)

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

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

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

func TestRBACHandlers_CreateRole_InvalidJSON(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

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

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRBACHandlers_CreateRole_MissingName(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	reqBody := map[string]interface{}{
		"description": "Test role description",
	}
	body, _ := json.Marshal(reqBody)

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

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRBACHandlers_GetRole_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	roleID := uuid.New()
	expectedRole := &services.Role{
		ID:          roleID,
		Name:        "test-role",
		Description: "Test role description",
		Permissions: []services.Permission{},
		IsSystem:    false,
	}

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

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

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

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

func TestRBACHandlers_GetRole_InvalidID(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

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

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRBACHandlers_GetRole_NotFound(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	roleID := uuid.New()
	mockService.On("GetRoleByID", mock.Anything, roleID).Return(nil, assert.AnError)

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

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

func TestRBACHandlers_GetAllRoles_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	expectedRoles := []*services.Role{
		{
			ID:          uuid.New(),
			Name:        "role1",
			Description: "Role 1",
			Permissions: []services.Permission{},
			IsSystem:    false,
		},
		{
			ID:          uuid.New(),
			Name:        "role2",
			Description: "Role 2",
			Permissions: []services.Permission{},
			IsSystem:    false,
		},
	}

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

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

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

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

func TestRBACHandlers_AssignRoleToUser_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

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

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

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

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

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

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

func TestRBACHandlers_AssignRoleToUser_InvalidUserID(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

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

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

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRBACHandlers_RemoveRoleFromUser_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

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

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

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

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

func TestRBACHandlers_GetUserRoles_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	userID := uuid.New()
	expectedRoles := []*services.Role{
		{
			ID:          uuid.New(),
			Name:        "admin",
			Description: "Admin role",
			Permissions: []services.Permission{},
			IsSystem:    true,
		},
	}

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

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

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

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

func TestRBACHandlers_GetUserPermissions_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	userID := uuid.New()
	expectedPermissions := []services.Permission{
		{
			ID:          uuid.New(),
			Name:        "read:tracks",
			Description: "Read tracks",
			Resource:    "tracks",
			Action:      "read",
		},
	}

	mockService.On("GetUserPermissions", mock.Anything, userID).Return(expectedPermissions, nil)

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

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

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

func TestRBACHandlers_CheckPermission_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	userID := uuid.New()
	mockService.On("CheckPermission", mock.Anything, userID, "tracks", "read").Return(true, nil)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/permissions/check?resource=tracks&action=read", nil)
	router.ServeHTTP(w, req)

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

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

func TestRBACHandlers_CheckPermission_MissingParams(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	userID := uuid.New()

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/permissions/check?resource=tracks", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRBACHandlers_CreatePermission_Success(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	permissionID := uuid.New()
	expectedPermission := &services.Permission{
		ID:          permissionID,
		Name:        "read:tracks",
		Description: "Read tracks",
		Resource:    "tracks",
		Action:      "read",
	}

	mockService.On("CreatePermission", mock.Anything, "read:tracks", "Read tracks", "tracks", "read").Return(expectedPermission, nil)

	reqBody := map[string]interface{}{
		"name":        "read:tracks",
		"description": "Read tracks",
		"resource":    "tracks",
		"action":      "read",
	}
	body, _ := json.Marshal(reqBody)

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

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

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

func TestRBACHandlers_CreatePermission_MissingFields(t *testing.T) {
	mockService := new(MockRBACService)
	router := setupTestRBACRouter(mockService)

	reqBody := map[string]interface{}{
		"name": "read:tracks",
		// Missing required fields: resource, action
	}
	body, _ := json.Marshal(reqBody)

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

	assert.Equal(t, http.StatusBadRequest, w.Code)
}
