package com.saas.admin.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.saas.admin.dto.request.AssignRolesRequest;
import com.saas.admin.entity.Role;
import com.saas.admin.entity.User;
import com.saas.admin.service.RBACService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.util.HashSet;
import java.util.Set;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integration Tests for AdminUserRoleController
 */
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("AdminUserRoleController Integration Tests")
class AdminUserRoleControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private RBACService rbacService;

    private User testUser;
    private Role testRole;
    private AssignRolesRequest assignRequest;

    @BeforeEach
    void setUp() {
        testRole = Role.builder()
            .id(1L)
            .name("TENANT_USER")
            .description("Tenant user role")
            .isActive(true)
            .build();

        testUser = User.builder()
            .id(100L)
            .email("user@test.com")
            .firstName("Test")
            .lastName("User")
            .isActive(true)
            .roles(new HashSet<>(Set.of(testRole)))
            .build();

        assignRequest = AssignRolesRequest.builder()
            .roleIds(Set.of(1L, 2L))
            .build();
    }

    @Test
    @DisplayName("Should return 401 when not authenticated")
    void testAssignRolesUnauthorized() throws Exception {
        mockMvc.perform(post("/api/admin/users/100/roles")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(assignRequest)))
            .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(roles = "USER")
    @DisplayName("Should return 403 when user lacks SYSTEM_ADMIN role")
    void testAssignRolesForbidden() throws Exception {
        mockMvc.perform(post("/api/admin/users/100/roles")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(assignRequest)))
            .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should assign roles to user successfully")
    void testAssignRolesSuccess() throws Exception {
        // Arrange
        when(rbacService.assignRolesToUser(eq(100L), any(Set.class)))
            .thenReturn(testUser);

        // Act & Assert
        mockMvc.perform(post("/api/admin/users/100/roles")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(assignRequest)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.success").value(true))
            .andExpect(jsonPath("$.data.id").value(100L))
            .andExpect(jsonPath("$.data.email").value("user@test.com"))
            .andExpect(jsonPath("$.data.roles").isArray());
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should return 400 when role IDs are empty")
    void testAssignRolesValidationError() throws Exception {
        // Arrange
        AssignRolesRequest invalidRequest = AssignRolesRequest.builder()
            .roleIds(Set.of()) // Empty set
            .build();

        // Act & Assert
        mockMvc.perform(post("/api/admin/users/100/roles")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
            .andExpect(status().isBadRequest());
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should get user roles successfully")
    void testGetUserRoles() throws Exception {
        // Arrange
        when(rbacService.getUserRoles(100L)).thenReturn(Set.of(testRole));

        // Act & Assert
        mockMvc.perform(get("/api/admin/users/100/roles")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.success").value(true))
            .andExpect(jsonPath("$.data.id").value(100L))
            .andExpect(jsonPath("$.data.roles").isArray())
            .andExpect(jsonPath("$.data.roles[0].name").value("TENANT_USER"));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should remove role from user successfully")
    void testRemoveRoleFromUser() throws Exception {
        // Arrange
        when(rbacService.removeRoleFromUser(100L, 1L)).thenReturn(testUser);

        // Act & Assert
        mockMvc.perform(delete("/api/admin/users/100/roles/1")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isNoContent());
    }
}
