package org.raddatz.familienarchiv.document.comment;

import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.security.SecurityConfig;
import org.raddatz.familienarchiv.document.comment.DocumentComment;
import org.raddatz.familienarchiv.security.PermissionAspect;
import org.raddatz.familienarchiv.document.comment.CommentService;
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.http.MediaType;
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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

@WebMvcTest(CommentController.class)
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
class CommentControllerTest {

    @Autowired MockMvc mockMvc;

    @MockitoBean CommentService commentService;
    @MockitoBean UserService userService;
    @MockitoBean CustomUserDetailsService customUserDetailsService;

    private static final String COMMENT_JSON = "{\"content\":\"Test comment\"}";
    private static final UUID DOC_ID = UUID.randomUUID();
    private static final UUID COMMENT_ID = UUID.randomUUID();

    // ─── Block comment endpoints ─────────────────────────────────────────────

    @Test
    @WithMockUser
    void getBlockComments_returns400_when_documentId_is_not_a_UUID() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(get("/api/documents/NOT-A-UUID/transcription-blocks/" + blockId + "/comments"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @WithMockUser
    void getBlockComments_returns200() throws Exception {
        UUID blockId = UUID.randomUUID();
        when(commentService.getCommentsForBlock(blockId)).thenReturn(List.of());

        mockMvc.perform(get("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments"))
                .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(authorities = "WRITE_ALL")
    void postBlockComment_returns201() throws Exception {
        UUID blockId = UUID.randomUUID();
        DocumentComment saved = DocumentComment.builder()
                .id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).content("Nice").build();
        when(commentService.postBlockComment(any(), any(), any(), any(), any())).thenReturn(saved);

        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.blockId").value(blockId.toString()));
    }

    @Test
    void postBlockComment_returns401_whenUnauthenticated() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void postBlockComment_returns403_whenMissingPermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "READ_ALL")
    void postBlockComment_returns403_whenUserHasOnlyReadAllPermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ANNOTATE_ALL")
    void postBlockComment_returns201_whenHasAnnotatePermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        DocumentComment saved = DocumentComment.builder()
                .id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).content("Nice").build();
        when(commentService.postBlockComment(any(), any(), any(), any(), any())).thenReturn(saved);

        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isCreated());
    }

    @Test
    @WithMockUser(authorities = "WRITE_ALL")
    void postBlockComment_stillSucceeds_whenUserServiceThrows() throws Exception {
        // findByEmail throws → catch block in resolveUser → author null, saves anyway
        UUID blockId = UUID.randomUUID();
        when(userService.findByEmail(any())).thenThrow(new RuntimeException("DB error"));
        DocumentComment saved = DocumentComment.builder()
                .id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).content("Test comment").build();
        when(commentService.postBlockComment(any(), any(), any(), any(), any())).thenReturn(saved);

        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isCreated());
    }

    // ─── Block reply endpoints ───────────────────────────────────────────────

    @Test
    @WithMockUser(authorities = "ANNOTATE_ALL")
    void replyToBlockComment_returns400_when_blockId_is_not_a_UUID() throws Exception {
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/NOT-A-UUID"
                                + "/comments/" + COMMENT_ID + "/replies").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isBadRequest());
    }

    @Test
    void replyToBlockComment_returns401_whenUnauthenticated() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId
                                + "/comments/" + COMMENT_ID + "/replies").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(authorities = "READ_ALL")
    void replyToBlockComment_returns403_whenUserHasOnlyReadAllPermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId
                                + "/comments/" + COMMENT_ID + "/replies").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ANNOTATE_ALL")
    void replyToBlockComment_returns201_whenHasPermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        DocumentComment saved = DocumentComment.builder()
                .id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).parentId(COMMENT_ID)
                .authorName("Anna").content("Reply").build();
        when(commentService.replyToComment(any(), any(), any(), any(), any())).thenReturn(saved);

        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId
                                + "/comments/" + COMMENT_ID + "/replies").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isCreated());
    }

    @Test
    @WithMockUser(authorities = "WRITE_ALL")
    void replyToBlockComment_returns201_whenHasWriteAllPermission() throws Exception {
        UUID blockId = UUID.randomUUID();
        DocumentComment saved = DocumentComment.builder()
                .id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).parentId(COMMENT_ID)
                .authorName("Anna").content("Reply").build();
        when(commentService.replyToComment(any(), any(), any(), any(), any())).thenReturn(saved);

        mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId
                                + "/comments/" + COMMENT_ID + "/replies").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isCreated());
    }

    // ─── PATCH /api/documents/{documentId}/comments/{commentId} (shared edit) ──

    @Test
    void editComment_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(authorities = "READ_ALL")
    void editComment_returns403_whenUserHasOnlyReadAllPermission() throws Exception {
        mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ANNOTATE_ALL")
    void editComment_returns200_whenHasPermission() throws Exception {
        DocumentComment updated = DocumentComment.builder()
                .id(COMMENT_ID).documentId(DOC_ID).authorName("Hans").content("Test comment").build();
        when(commentService.editComment(any(), any(), any(), any())).thenReturn(updated);

        mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(authorities = "WRITE_ALL")
    void editComment_returns200_whenHasWriteAllPermission() throws Exception {
        DocumentComment updated = DocumentComment.builder()
                .id(COMMENT_ID).documentId(DOC_ID).authorName("Hans").content("Test comment").build();
        when(commentService.editComment(any(), any(), any(), any())).thenReturn(updated);

        mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
                .andExpect(status().isOk());
    }

    // ─── DELETE /api/documents/{documentId}/comments/{commentId} (shared) ────

    @Test
    void deleteComment_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(delete("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf()))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void deleteComment_returns204_whenAuthenticated() throws Exception {
        mockMvc.perform(delete("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID).with(csrf()))
                .andExpect(status().isNoContent());
    }
}
