package de.effigenix.infrastructure.masterdata.web;

import de.effigenix.domain.masterdata.*;
import de.effigenix.domain.masterdata.article.PriceModel;
import de.effigenix.domain.masterdata.customer.CustomerPreference;
import de.effigenix.domain.masterdata.customer.CustomerType;
import de.effigenix.domain.masterdata.customer.DeliveryRhythm;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.masterdata.web.dto.*;
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.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
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 CustomerController.
 *
 * Abgedeckte Testfälle: TC-CUS-01 bis TC-CUS-11, TC-B2B-01/02, TC-AUTH
 */
@DisplayName("Customer Controller Integration Tests")
class CustomerControllerIntegrationTest 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("cus.admin", "cus.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
        String viewerId = createUser("cus.viewer", "cus.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");

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

    // ==================== TC-CUS-01: B2C-Kunde erstellen ====================

    @Test
    @DisplayName("TC-CUS-01: B2C-Kunde erstellen → 201, Typ B2C, Status ACTIVE")
    void createCustomer_b2c_returns201() throws Exception {
        var request = new CreateCustomerRequest(
                "Max Mustermann", CustomerType.B2C,
                "Musterstr. 1", "2", "10115", "Berlin", "DE",
                "+49 176 12345", null, null, null, null);

        mockMvc.perform(post("/api/customers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").isNotEmpty())
                .andExpect(jsonPath("$.name").value("Max Mustermann"))
                .andExpect(jsonPath("$.type").value("B2C"))
                .andExpect(jsonPath("$.status").value("ACTIVE"))
                .andExpect(jsonPath("$.billingAddress.city").value("Berlin"));
    }

    // ==================== TC-CUS-02: B2B-Kunde erstellen ====================

    @Test
    @DisplayName("TC-CUS-02: B2B-Kunde erstellen → 201, Typ B2B")
    void createCustomer_b2b_returns201() throws Exception {
        var request = new CreateCustomerRequest(
                "Gastro GmbH", CustomerType.B2B,
                "Gastrostr. 10", "1", "10178", "Berlin", "DE",
                "+49 30 9876", "gastro@example.com", "Frau Koch",
                30, "30 Tage netto");

        mockMvc.perform(post("/api/customers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("Gastro GmbH"))
                .andExpect(jsonPath("$.type").value("B2B"));
    }

    // ==================== TC-CUS-03: Pflichtfelder ====================

    @Test
    @DisplayName("TC-CUS-03: Kunde ohne Namen → 400")
    void createCustomer_withBlankName_returns400() throws Exception {
        var request = new CreateCustomerRequest(
                "", CustomerType.B2C,
                "Musterstr. 1", "2", "10115", "Berlin", "DE",
                "+49 176 12345", null, null, null, null);

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

    @Test
    @DisplayName("TC-CUS-03: Kunde ohne Telefon → 400")
    void createCustomer_withBlankPhone_returns400() throws Exception {
        var request = new CreateCustomerRequest(
                "Kein Telefon", CustomerType.B2C,
                "Musterstr. 1", "2", "10115", "Berlin", "DE",
                "", null, null, null, null);

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

    @Test
    @DisplayName("TC-CUS-03: Kunde ohne Rechnungsadresse → 400")
    void createCustomer_withMissingBillingAddress_returns400() throws Exception {
        mockMvc.perform(post("/api/customers")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{}"))
                .andExpect(status().isBadRequest());
    }

    // ==================== TC-CUS-04: Doppelter Name ====================

    @Test
    @DisplayName("TC-CUS-04: Doppelter Kundenname → 409 Conflict")
    void createCustomer_withDuplicateName_returns409() throws Exception {
        createB2bCustomer("Gastro GmbH");

        var duplicate = new CreateCustomerRequest(
                "Gastro GmbH", CustomerType.B2B,
                "Andere Str. 5", null, "10179", "Berlin", "DE",
                "+49 30 1111", null, null, null, null);

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

    // ==================== TC-CUS-05: Deaktivieren und Aktivieren ====================

    @Test
    @DisplayName("TC-CUS-05: Kunde deaktivieren → Status INACTIVE")
    void deactivateCustomer_activeCustomer_returnsInactive() throws Exception {
        String customerId = createB2cCustomer("Max Mustermann");

        mockMvc.perform(post("/api/customers/{id}/deactivate", customerId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("INACTIVE"));
    }

    @Test
    @DisplayName("TC-CUS-05: Kunde aktivieren → Status ACTIVE")
    void activateCustomer_inactiveCustomer_returnsActive() throws Exception {
        String customerId = createB2cCustomer("Max Mustermann");

        mockMvc.perform(post("/api/customers/{id}/deactivate", customerId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(post("/api/customers/{id}/activate", customerId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("ACTIVE"));
    }

    // ==================== TC-CUS-06: Filter ====================

    @Test
    @DisplayName("TC-CUS-06: Nur B2B-Kunden filtern → nur B2B")
    void listCustomers_filterByB2B_returnsOnlyB2B() throws Exception {
        createB2bCustomer("Gastro GmbH");
        createB2cCustomer("Max Mustermann");

        mockMvc.perform(get("/api/customers")
                        .param("type", "B2B")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].type", everyItem(is("B2B"))));
    }

    @Test
    @DisplayName("TC-CUS-06: Nur B2C-Kunden filtern → nur B2C")
    void listCustomers_filterByB2C_returnsOnlyB2C() throws Exception {
        createB2bCustomer("Gastro GmbH");
        createB2cCustomer("Max Mustermann");

        mockMvc.perform(get("/api/customers")
                        .param("type", "B2C")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].type", everyItem(is("B2C"))));
    }

    @Test
    @DisplayName("TC-CUS-06: Nur aktive Kunden filtern → nur ACTIVE")
    void listCustomers_filterByActive_returnsOnlyActive() throws Exception {
        String id = createB2cCustomer("Inaktiver Kunde");
        mockMvc.perform(post("/api/customers/{id}/deactivate", id)
                .header("Authorization", "Bearer " + adminToken)).andReturn();

        createB2cCustomer("Aktiver Kunde");

        mockMvc.perform(get("/api/customers")
                        .param("status", "ACTIVE")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].status", everyItem(is("ACTIVE"))));
    }

    // ==================== TC-CUS-07: Lieferadresse hinzufügen ====================

    @Test
    @DisplayName("TC-CUS-07: Lieferadresse hinzufügen → erscheint in Detailansicht")
    void addDeliveryAddress_validAddress_appearsInDetail() throws Exception {
        String customerId = createB2bCustomer("Gastro GmbH");

        var addr = new AddDeliveryAddressRequest(
                "Hauptküche", "Kochstr.", "12", "10963", "Berlin", "DE",
                "Koch Müller", "Bitte kühlen");

        mockMvc.perform(post("/api/customers/{id}/delivery-addresses", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(addr)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.deliveryAddresses", hasSize(1)))
                .andExpect(jsonPath("$.deliveryAddresses[0].label").value("Hauptküche"))
                .andExpect(jsonPath("$.deliveryAddresses[0].address.city").value("Berlin"));
    }

    // ==================== TC-CUS-08: Lieferadresse entfernen ====================

    @Test
    @DisplayName("TC-CUS-08: Lieferadresse entfernen → nicht mehr vorhanden")
    void removeDeliveryAddress_existingAddress_removedFromDetail() throws Exception {
        String customerId = createB2bCustomer("Gastro GmbH");

        var addr = new AddDeliveryAddressRequest(
                "Hauptküche", "Kochstr.", "12", "10963", "Berlin", "DE",
                null, null);

        mockMvc.perform(post("/api/customers/{id}/delivery-addresses", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(addr)))
                .andExpect(status().isCreated());

        mockMvc.perform(delete("/api/customers/{id}/delivery-addresses/{label}", customerId, "Hauptküche")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNoContent());

        mockMvc.perform(get("/api/customers/{id}", customerId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.deliveryAddresses", hasSize(0)));
    }

    // ==================== TC-CUS-09: Mehrere Lieferadressen ====================

    @Test
    @DisplayName("TC-CUS-09: Zwei Lieferadressen → beide in Detailansicht")
    void addTwoDeliveryAddresses_bothAppearInDetail() throws Exception {
        String customerId = createB2bCustomer("Gastro GmbH");

        var nord = new AddDeliveryAddressRequest(
                "Filiale Nord", "Nordstr.", "1", "20095", "Hamburg", "DE",
                null, null);
        var sued = new AddDeliveryAddressRequest(
                "Filiale Süd", "Südstr.", "2", "80331", "München", "DE",
                null, null);

        mockMvc.perform(post("/api/customers/{id}/delivery-addresses", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(nord)))
                .andExpect(status().isCreated());

        mockMvc.perform(post("/api/customers/{id}/delivery-addresses", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(sued)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.deliveryAddresses", hasSize(2)));
    }

    // ==================== TC-CUS-10: Präferenzen setzen ====================

    @Test
    @DisplayName("TC-CUS-10: Präferenzen BIO und REGIONAL setzen → beide sichtbar")
    void setPreferences_bioAndRegional_appearsInDetail() throws Exception {
        String customerId = createB2cCustomer("Max Mustermann");

        var prefs = new SetPreferencesRequest(Set.of(CustomerPreference.BIO, CustomerPreference.REGIONAL));

        mockMvc.perform(put("/api/customers/{id}/preferences", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(prefs)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.preferences", hasSize(2)))
                .andExpect(jsonPath("$.preferences", containsInAnyOrder("BIO", "REGIONAL")));
    }

    @Test
    @DisplayName("TC-CUS-10: Präferenzen überschreiben → nur neue Präferenz gesetzt")
    void setPreferences_replacesPreviousPreferences() throws Exception {
        String customerId = createB2cCustomer("Max Mustermann");

        var firstPrefs = new SetPreferencesRequest(Set.of(CustomerPreference.BIO, CustomerPreference.REGIONAL));
        mockMvc.perform(put("/api/customers/{id}/preferences", customerId)
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(firstPrefs))).andReturn();

        var secondPrefs = new SetPreferencesRequest(Set.of(CustomerPreference.HALAL));
        mockMvc.perform(put("/api/customers/{id}/preferences", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(secondPrefs)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.preferences", hasSize(1)))
                .andExpect(jsonPath("$.preferences[0]").value("HALAL"));
    }

    // ==================== TC-CUS-11: Alle Präferenzen abwählen ====================

    @Test
    @DisplayName("TC-CUS-11: Alle Präferenzen abwählen → leere Liste")
    void setPreferences_empty_clearsPreferences() throws Exception {
        String customerId = createB2cCustomer("Max Mustermann");

        var prefs = new SetPreferencesRequest(Set.of(CustomerPreference.BIO));
        mockMvc.perform(put("/api/customers/{id}/preferences", customerId)
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(prefs))).andReturn();

        var emptyPrefs = new SetPreferencesRequest(Set.of());
        mockMvc.perform(put("/api/customers/{id}/preferences", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(emptyPrefs)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.preferences", hasSize(0)));
    }

    // ==================== TC-B2B-02: Rahmenvertrag für B2C-Kunden ====================

    @Test
    @DisplayName("TC-B2B-02: Rahmenvertrag für B2C-Kunden → 400/422 FrameContractNotAllowed")
    void setFrameContract_forB2cCustomer_returns4xx() throws Exception {
        String b2cId = createB2cCustomer("Privatkunde");

        String articleId = createArticle();

        var contract = new SetFrameContractRequest(
                LocalDate.of(2025, 1, 1), LocalDate.of(2025, 12, 31),
                DeliveryRhythm.WEEKLY,
                List.of(new SetFrameContractRequest.LineItem(
                        articleId, new BigDecimal("1.50"), new BigDecimal("100"), Unit.KG)));

        mockMvc.perform(put("/api/customers/{id}/frame-contract", b2cId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(contract)))
                .andExpect(status().is4xxClientError());
    }

    @Test
    @DisplayName("TC-B2B-01: Rahmenvertrag für B2B-Kunden → 200")
    void setFrameContract_forB2bCustomer_returns200() throws Exception {
        String b2bId = createB2bCustomer("Gastro GmbH");
        String articleId = createArticle();

        var contract = new SetFrameContractRequest(
                LocalDate.of(2025, 1, 1), LocalDate.of(2025, 12, 31),
                DeliveryRhythm.WEEKLY,
                List.of(new SetFrameContractRequest.LineItem(
                        articleId, new BigDecimal("1.50"), new BigDecimal("100"), Unit.KG)));

        mockMvc.perform(put("/api/customers/{id}/frame-contract", b2bId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(contract)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.frameContract").isNotEmpty())
                .andExpect(jsonPath("$.frameContract.deliveryRhythm").value("WEEKLY"));
    }

    // ==================== Rahmenvertrag entfernen ====================

    @Test
    @DisplayName("Rahmenvertrag entfernen → 204, kein FrameContract mehr vorhanden")
    void removeFrameContract_returns204() throws Exception {
        String b2bId = createB2bCustomer("Vertrags GmbH");
        String articleId = createArticle();

        var contract = new SetFrameContractRequest(
                LocalDate.of(2025, 1, 1), LocalDate.of(2025, 12, 31),
                DeliveryRhythm.WEEKLY,
                List.of(new SetFrameContractRequest.LineItem(
                        articleId, new BigDecimal("1.50"), new BigDecimal("100"), Unit.KG)));

        mockMvc.perform(put("/api/customers/{id}/frame-contract", b2bId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(contract)))
                .andExpect(status().isOk());

        mockMvc.perform(delete("/api/customers/{id}/frame-contract", b2bId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNoContent());

        mockMvc.perform(get("/api/customers/{id}", b2bId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.frameContract").isEmpty());
    }

    // ==================== Kunden aktualisieren ====================

    @Test
    @DisplayName("Kundenstammdaten aktualisieren → 200")
    void updateCustomer_returns200() throws Exception {
        String customerId = createB2cCustomer("Alt Name");

        var request = new UpdateCustomerRequest(
                "Neuer Name", "Neue Str.", "5", "20095", "Hamburg", "DE",
                "+49 40 1234", null, null, null, null);

        mockMvc.perform(put("/api/customers/{id}", customerId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Neuer Name"))
                .andExpect(jsonPath("$.billingAddress.city").value("Hamburg"));
    }

    // ==================== Doppelte LineItems im Rahmenvertrag ====================

    @Test
    @DisplayName("Rahmenvertrag mit doppelten ArticleId-LineItems → 400")
    void setFrameContract_duplicateLineItems_returns400() throws Exception {
        String b2bId = createB2bCustomer("Duplikat GmbH");
        String articleId = createArticle();

        var contract = new SetFrameContractRequest(
                LocalDate.of(2025, 1, 1), LocalDate.of(2025, 12, 31),
                DeliveryRhythm.WEEKLY,
                List.of(
                        new SetFrameContractRequest.LineItem(articleId, new BigDecimal("1.50"), new BigDecimal("100"), Unit.KG),
                        new SetFrameContractRequest.LineItem(articleId, new BigDecimal("2.00"), new BigDecimal("200"), Unit.KG)));

        mockMvc.perform(put("/api/customers/{id}/frame-contract", b2bId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(contract)))
                .andExpect(status().isBadRequest());
    }

    // ==================== TC-AUTH ====================

    @Test
    @DisplayName("TC-AUTH-02: Kunde erstellen ohne MASTERDATA_WRITE → 403")
    void createCustomer_withViewerToken_returns403() throws Exception {
        var request = new CreateCustomerRequest(
                "Kein Zugriff", CustomerType.B2C,
                "Str. 1", null, "10115", "Berlin", "DE",
                "+49 30 0", null, null, null, null);

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

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

    @Test
    @DisplayName("Nicht existierenden Kunden abfragen → 404")
    void getCustomer_nonExistent_returns404() throws Exception {
        mockMvc.perform(get("/api/customers/{id}", UUID.randomUUID())
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNotFound());
    }

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

    private String createB2cCustomer(String name) throws Exception {
        var request = new CreateCustomerRequest(
                name, CustomerType.B2C,
                "Musterstr. 1", "2", "10115", "Berlin", "DE",
                "+49 176 12345", null, null, null, null);
        MvcResult result = mockMvc.perform(post("/api/customers")
                        .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();
    }

    private String createB2bCustomer(String name) throws Exception {
        var request = new CreateCustomerRequest(
                name, CustomerType.B2B,
                "Geschäftsstr. 1", "1", "10178", "Berlin", "DE",
                "+49 30 9876", null, null, null, null);
        MvcResult result = mockMvc.perform(post("/api/customers")
                        .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();
    }

    /** Erstellt Hilfskategorie und Artikel für Rahmenvertrags-Tests. */
    private String createArticle() throws Exception {
        var catRequest = new CreateProductCategoryRequest("Testkat", null);
        MvcResult catResult = mockMvc.perform(post("/api/categories")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(catRequest)))
                .andExpect(status().isCreated())
                .andReturn();
        String catId = objectMapper.readTree(catResult.getResponse().getContentAsString()).get("id").asText();

        var artRequest = new CreateArticleRequest(
                "Testartikel", "TA-001", catId,
                Unit.KG, PriceModel.WEIGHT_BASED, new BigDecimal("2.00"));
        MvcResult artResult = mockMvc.perform(post("/api/articles")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(artRequest)))
                .andExpect(status().isCreated())
                .andReturn();
        return objectMapper.readTree(artResult.getResponse().getContentAsString()).get("id").asText();
    }
}
