package de.effigenix.infrastructure.masterdata.web;

import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.masterdata.web.dto.CreateProductCategoryRequest;
import de.effigenix.infrastructure.masterdata.web.dto.UpdateProductCategoryRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;

import java.util.Set;
import java.util.UUID;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integrationstests für ProductCategoryController.
 *
 * Abgedeckte Testfälle: TC-CAT-01 bis TC-CAT-06
 */
@DisplayName("ProductCategory Controller Integration Tests")
class ProductCategoryControllerIntegrationTest extends AbstractIntegrationTest {

    private String adminToken;
    private String viewerToken;

    @BeforeEach
    void setUp() {
        String adminRoleId = createRole(RoleName.ADMIN, "Admin");
        String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");

        String adminId = createUser("cat.admin", "cat.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
        String viewerId = createUser("cat.viewer", "cat.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");

        adminToken = generateToken(adminId, "cat.admin", "MASTERDATA_WRITE");
        viewerToken = generateToken(viewerId, "cat.viewer", "USER_READ");
    }

    // ==================== TC-CAT-01: Kategorie erstellen (Happy Path) ====================

    @Test
    @DisplayName("TC-CAT-01: Kategorie mit Name und Beschreibung erstellen → 201")
    void createCategory_withNameAndDescription_returns201() throws Exception {
        var request = new CreateProductCategoryRequest("Obst & Gemüse", "Frische Produkte");

        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").isNotEmpty())
                .andExpect(jsonPath("$.name").value("Obst & Gemüse"))
                .andExpect(jsonPath("$.description").value("Frische Produkte"));
    }

    // ==================== TC-CAT-02: Kategorie ohne Beschreibung ====================

    @Test
    @DisplayName("TC-CAT-02: Kategorie ohne Beschreibung erstellen → 201")
    void createCategory_withoutDescription_returns201() throws Exception {
        var request = new CreateProductCategoryRequest("Milchprodukte", null);

        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("Milchprodukte"))
                .andExpect(jsonPath("$.description").doesNotExist());
    }

    // ==================== TC-CAT-03: Kategorie bearbeiten ====================

    @Test
    @DisplayName("TC-CAT-03: Kategorie bearbeiten → 200 mit aktualisierten Daten")
    void updateCategory_withValidData_returns200() throws Exception {
        String categoryId = createCategory("Obst & Gemüse", "Alt");

        var update = new UpdateProductCategoryRequest("Obst und Gemüse", "Saisonale Frische");

        mockMvc.perform(put("/api/categories/{id}", categoryId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(update)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Obst und Gemüse"))
                .andExpect(jsonPath("$.description").value("Saisonale Frische"));
    }

    // ==================== TC-CAT-04: Doppelter Name wird abgelehnt ====================

    @Test
    @DisplayName("TC-CAT-04: Doppelter Kategoriename → 409 Conflict")
    void createCategory_withDuplicateName_returns409() throws Exception {
        createCategory("Milchprodukte", null);

        var request = new CreateProductCategoryRequest("Milchprodukte", null);

        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isConflict());
    }

    // ==================== TC-CAT-05: Kategorie löschen ====================

    @Test
    @DisplayName("TC-CAT-05: Kategorie löschen → 204 und nicht mehr in Gesamtliste")
    void deleteCategory_existingCategory_returns204() throws Exception {
        String categoryId = createCategory("Löschtest", null);

        mockMvc.perform(delete("/api/categories/{id}", categoryId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNoContent());

        // Verifizierung über Liste – kein GET-by-ID-Endpoint vorhanden
        mockMvc.perform(get("/api/categories")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[?(@.id == '" + categoryId + "')]").isEmpty());
    }

    // ==================== TC-CAT-06: Leerer Name wird abgelehnt ====================

    @Test
    @DisplayName("TC-CAT-06: Leerer Name → 400 Bad Request")
    void createCategory_withBlankName_returns400() throws Exception {
        var request = new CreateProductCategoryRequest("", "Beschreibung");

        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("TC-CAT-06: Null-Name → 400 Bad Request")
    void createCategory_withNullName_returns400() throws Exception {
        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{}"))
                .andExpect(status().isBadRequest());
    }

    // ==================== Liste ====================

    @Test
    @DisplayName("Kategorienliste abfragen → 200 mit Array")
    void listCategories_returns200() throws Exception {
        createCategory("Fleisch", null);
        createCategory("Wurst", null);

        mockMvc.perform(get("/api/categories")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(2))));
    }

    // ==================== TC-AUTH: Autorisierung ====================

    @Test
    @DisplayName("TC-AUTH-01: Kategorienliste ohne MASTERDATA_WRITE → 200 (Lesezugriff)")
    void listCategories_withViewerToken_returns200() throws Exception {
        mockMvc.perform(get("/api/categories")
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isOk());
    }

    @Test
    @DisplayName("TC-AUTH-02: Kategorie erstellen ohne MASTERDATA_WRITE → 403")
    void createCategory_withViewerToken_returns403() throws Exception {
        var request = new CreateProductCategoryRequest("Getränke", null);

        mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + viewerToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("Kategorie erstellen ohne Token → 401")
    void createCategory_withoutToken_returns401() throws Exception {
        var request = new CreateProductCategoryRequest("Getränke", null);

        mockMvc.perform(post("/api/categories")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("Nicht existierende Kategorie aktualisieren → 404")
    void updateCategory_nonExistent_returns404() throws Exception {
        var update = new UpdateProductCategoryRequest("Neuer Name", null);
        mockMvc.perform(put("/api/categories/{id}", UUID.randomUUID())
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(update)))
                .andExpect(status().isNotFound());
    }

    // ==================== Hilfsmethoden ====================

    private String createCategory(String name, String description) throws Exception {
        var request = new CreateProductCategoryRequest(name, description);
        MvcResult result = mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andReturn();
        return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText();
    }
}
