package org.raddatz.familienarchiv.tag;

import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.tag.TagTreeNodeDTO;
import org.raddatz.familienarchiv.tag.Tag;
import org.raddatz.familienarchiv.security.PermissionAspect;
import org.raddatz.familienarchiv.user.CustomUserDetailsService;
import org.raddatz.familienarchiv.document.DocumentService;
import org.raddatz.familienarchiv.tag.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.raddatz.familienarchiv.security.SecurityConfig;
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
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 org.raddatz.familienarchiv.tag.MergeTagDTO;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;

import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

@WebMvcTest(TagController.class)
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
class TagControllerTest {

    @Autowired MockMvc mockMvc;

    @MockitoBean TagService tagService;
    @MockitoBean DocumentService documentService;
    @MockitoBean CustomUserDetailsService customUserDetailsService;

    // ─── GET /api/tags ────────────────────────────────────────────────────────

    @Test
    void searchTags_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(get("/api/tags"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void searchTags_returns200_whenAuthenticated() throws Exception {
        when(tagService.search(any())).thenReturn(List.of());

        mockMvc.perform(get("/api/tags"))
                .andExpect(status().isOk());
    }

    // ─── PUT /api/tags/{id} ───────────────────────────────────────────────────

    @Test
    void updateTag_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(put("/api/tags/" + UUID.randomUUID()).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\": \"New\"}"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void updateTag_returns403_whenMissingAdminTagPermission() throws Exception {
        mockMvc.perform(put("/api/tags/" + UUID.randomUUID()).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\": \"New\"}"))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void updateTag_returns200_whenHasAdminTagPermission() throws Exception {
        Tag tag = Tag.builder().id(UUID.randomUUID()).name("New").build();
        when(tagService.update(any(), any())).thenReturn(tag);

        mockMvc.perform(put("/api/tags/" + UUID.randomUUID()).with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\": \"New\"}"))
                .andExpect(status().isOk());
    }

    // ─── GET /api/tags/tree ───────────────────────────────────────────────────

    @Test
    void getTagTree_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(get("/api/tags/tree"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void getTagTree_returns200_withTreeStructure() throws Exception {
        UUID parentId = UUID.randomUUID();
        UUID childId = UUID.randomUUID();
        TagTreeNodeDTO child = new TagTreeNodeDTO(childId, "Haus", null, 0, 0, List.of(), parentId);
        TagTreeNodeDTO parent = new TagTreeNodeDTO(parentId, "Immobilie", "teal", 0, 0, List.of(child), null);
        when(tagService.getTagTree()).thenReturn(List.of(parent));

        mockMvc.perform(get("/api/tags/tree"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].name").value("Immobilie"))
                .andExpect(jsonPath("$[0].color").value("teal"))
                .andExpect(jsonPath("$[0].children[0].name").value("Haus"));
    }

    // ─── POST /api/tags/{id}/merge ────────────────────────────────────────────

    @Test
    void mergeTag_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(post("/api/tags/" + UUID.randomUUID() + "/merge").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"targetId\": \"" + UUID.randomUUID() + "\"}"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void mergeTag_returns403_whenMissingAdminTagPermission() throws Exception {
        mockMvc.perform(post("/api/tags/" + UUID.randomUUID() + "/merge").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"targetId\": \"" + UUID.randomUUID() + "\"}"))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void mergeTag_returns400_whenTargetIdIsNull() throws Exception {
        mockMvc.perform(post("/api/tags/" + UUID.randomUUID() + "/merge").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{}"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void mergeTag_returns404_whenSourceTagNotFound() throws Exception {
        when(tagService.mergeTags(any(), any()))
                .thenThrow(DomainException.notFound(ErrorCode.TAG_NOT_FOUND, "Tag not found"));

        mockMvc.perform(post("/api/tags/" + UUID.randomUUID() + "/merge").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"targetId\": \"" + UUID.randomUUID() + "\"}"))
                .andExpect(status().isNotFound());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void mergeTag_returns200_withTargetTag_onSuccess() throws Exception {
        UUID targetId = UUID.randomUUID();
        Tag target = Tag.builder().id(targetId).name("Target").build();
        when(tagService.mergeTags(any(), any())).thenReturn(target);

        mockMvc.perform(post("/api/tags/" + UUID.randomUUID() + "/merge").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"targetId\": \"" + targetId + "\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(targetId.toString()))
                .andExpect(jsonPath("$.name").value("Target"));
    }

    // ─── DELETE /api/tags/{id}/subtree ────────────────────────────────────────

    @Test
    void deleteSubtree_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID() + "/subtree").with(csrf()))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void deleteSubtree_returns403_whenMissingAdminTagPermission() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID() + "/subtree").with(csrf()))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void deleteSubtree_returns204_whenHasAdminTagPermission() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID() + "/subtree").with(csrf()))
                .andExpect(status().isNoContent());
    }

    // ─── DELETE /api/tags/{id} ────────────────────────────────────────────────

    @Test
    void deleteTag_returns401_whenUnauthenticated() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID()).with(csrf()))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser
    void deleteTag_returns403_whenMissingAdminTagPermission() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID()).with(csrf()))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(authorities = "ADMIN_TAG")
    void deleteTag_returns200_whenHasAdminTagPermission() throws Exception {
        mockMvc.perform(delete("/api/tags/" + UUID.randomUUID()).with(csrf()))
                .andExpect(status().isOk());
    }
}
