package org.raddatz.familienarchiv.timeline;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.security.PermissionAspect;
import org.raddatz.familienarchiv.security.SecurityConfig;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.user.CustomUserDetailsService;
import org.raddatz.familienarchiv.user.UserService;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;
import java.util.UUID;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
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(TimelineController.class)
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
class TimelineControllerTest {

    @Autowired MockMvc mockMvc;

    @MockitoBean TimelineService timelineService;
    @MockitoBean UserService userService;
    @MockitoBean CustomUserDetailsService customUserDetailsService;

    private static final TimelineDTO EMPTY = new TimelineDTO(List.of(), List.of());

    @BeforeEach
    void resolveDefaultPrincipal() {
        when(userService.findByEmail("user"))
                .thenReturn(AppUser.builder().id(UUID.randomUUID()).email("user").build());
    }

    // ─── Security ─────────────────────────────────────────────────────────────

    @Test
    void returns_401_when_unauthenticated() throws Exception {
        // REQ-014
        mockMvc.perform(get("/api/timeline"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "WRITE_ALL")
    void returns_403_when_authenticated_without_read_all() throws Exception {
        // REQ-015
        mockMvc.perform(get("/api/timeline"))
                .andExpect(status().isForbidden());
    }

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void returns_200_with_read_all_permission() throws Exception {
        // REQ-001
        when(timelineService.assemble(any())).thenReturn(EMPTY);

        mockMvc.perform(get("/api/timeline"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.years").isArray())
                .andExpect(jsonPath("$.undated").isArray());
    }

    // ─── Parameter binding ────────────────────────────────────────────────────

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void valid_params_are_forwarded_to_service() throws Exception {
        UUID personId = UUID.randomUUID();
        when(timelineService.assemble(any())).thenReturn(EMPTY);

        mockMvc.perform(get("/api/timeline")
                        .param("personId", personId.toString())
                        .param("generation", "2")
                        .param("type", "HISTORICAL")
                        .param("fromYear", "1914")
                        .param("toYear", "1918"))
                .andExpect(status().isOk());

        verify(timelineService).assemble(new TimelineFilter(personId, 2, EventType.HISTORICAL, 1914, 1918));
    }

    // ─── Validation errors ────────────────────────────────────────────────────

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void returns_400_on_bad_type_value() throws Exception {
        // REQ-018 — Spring enum binding rejects unknown value
        mockMvc.perform(get("/api/timeline").param("type", "NOT_A_TYPE"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void returns_400_when_fromYear_greater_than_toYear() throws Exception {
        // REQ-016 — service throws bad request, controller propagates it
        when(timelineService.assemble(any()))
                .thenThrow(DomainException.badRequest(ErrorCode.VALIDATION_ERROR,
                        "toYear must not be before fromYear"));

        mockMvc.perform(get("/api/timeline")
                        .param("fromYear", "1920")
                        .param("toYear", "1914"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void returns_400_when_generation_is_negative() throws Exception {
        // REQ-017 — @Min(0) on generation parameter
        mockMvc.perform(get("/api/timeline").param("generation", "-1"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @org.springframework.security.test.context.support.WithMockUser(authorities = "READ_ALL")
    void returns_404_when_person_not_found() throws Exception {
        // REQ-019
        when(timelineService.assemble(any()))
                .thenThrow(DomainException.notFound(ErrorCode.PERSON_NOT_FOUND, "Person not found"));

        mockMvc.perform(get("/api/timeline").param("personId", UUID.randomUUID().toString()))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code", is("PERSON_NOT_FOUND")));
    }
}
