package org.raddatz.familienarchiv.user;

import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.security.SecurityConfig;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.security.PermissionAspect;
import org.raddatz.familienarchiv.user.CustomUserDetailsService;
import org.raddatz.familienarchiv.user.UserSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;

import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserSearchController.class)
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
class UserSearchControllerTest {

    @Autowired MockMvc mockMvc;

    @MockitoBean UserSearchService userSearchService;
    @MockitoBean CustomUserDetailsService customUserDetailsService;

    @Test
    void search_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(get("/api/users/search").param("q", "Hans"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void search_returns403_whenUserLacksPermission() throws Exception {
        mockMvc.perform(get("/api/users/search").param("q", "Hans"))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = {"ANNOTATE_ALL"})
    void search_returns200_whenUserHasAnnotateAll() throws Exception {
        when(userSearchService.search("Hans")).thenReturn(List.of());

        mockMvc.perform(get("/api/users/search").param("q", "Hans"))
                .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(authorities = {"READ_ALL"})
    void search_returns200_whenAuthenticated() throws Exception {
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .firstName("Hans").lastName("Mueller").email("hans@example.com").build();
        when(userSearchService.search("Hans")).thenReturn(List.of(user));

        mockMvc.perform(get("/api/users/search").param("q", "Hans"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].firstName").value("Hans"));
    }

    @Test
    @WithMockUser(authorities = {"READ_ALL"})
    void search_returnsEmptyList_whenQueryIsEmpty() throws Exception {
        when(userSearchService.search("")).thenReturn(List.of());

        mockMvc.perform(get("/api/users/search").param("q", ""))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isEmpty());
    }

    @Test
    @WithMockUser(authorities = {"READ_ALL"})
    void search_returnsAtMostTenResults() throws Exception {
        List<AppUser> elevenUsers = IntStream.range(0, 11)
                .mapToObj(i -> AppUser.builder().id(UUID.randomUUID())
                        .firstName("User").lastName(String.valueOf(i)).email("u" + i + "@example.com").build())
                .toList();
        when(userSearchService.search(anyString())).thenReturn(elevenUsers.subList(0, 10));

        mockMvc.perform(get("/api/users/search").param("q", "a"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(lessThanOrEqualTo(10)));
    }
}
