package de.effigenix.infrastructure.inventory.web;

import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

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 StorageLocationController.
 *
 * Abgedeckte Testfälle:
 * - Story 1.1 – Lagerort anlegen
 * - Story 1.2 – Lagerort bearbeiten und (de-)aktivieren
 */
@DisplayName("StorageLocation Controller Integration Tests")
class StorageLocationControllerIntegrationTest extends AbstractIntegrationTest {

    private String adminToken;
    private String readerToken;
    private String viewerToken;

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

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

        adminToken = generateToken(adminId, "inv.admin", "STOCK_WRITE,STOCK_READ");
        readerToken = generateToken(readerId, "inv.reader", "STOCK_READ");
        viewerToken = generateToken(viewerId, "inv.viewer", "USER_READ");
    }

    // ==================== Lagerort anlegen – Pflichtfelder ====================

    @Test
    @DisplayName("Lagerort mit Pflichtfeldern erstellen → 201")
    void createStorageLocation_withRequiredFields_returns201() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Trockenlager 1", "DRY_STORAGE", null, null);

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").isNotEmpty())
                .andExpect(jsonPath("$.name").value("Trockenlager 1"))
                .andExpect(jsonPath("$.storageType").value("DRY_STORAGE"))
                .andExpect(jsonPath("$.temperatureRange").isEmpty())
                .andExpect(jsonPath("$.active").value(true));
    }

    // ==================== Lagerort mit Temperaturbereich ====================

    @Test
    @DisplayName("Lagerort mit Temperaturbereich erstellen → 201")
    void createStorageLocation_withTemperatureRange_returns201() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Kühlraum 1", "COLD_ROOM", "-2", "8");

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("Kühlraum 1"))
                .andExpect(jsonPath("$.storageType").value("COLD_ROOM"))
                .andExpect(jsonPath("$.temperatureRange.minTemperature").value(-2))
                .andExpect(jsonPath("$.temperatureRange.maxTemperature").value(8))
                .andExpect(jsonPath("$.active").value(true));
    }

    // ==================== Validierungsfehler ====================

    @Test
    @DisplayName("Lagerort ohne Namen → 400")
    void createStorageLocation_withBlankName_returns400() throws Exception {
        var request = new CreateStorageLocationRequest(
                "", "COLD_ROOM", null, null);

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

    @Test
    @DisplayName("Lagerort ohne StorageType → 400")
    void createStorageLocation_withBlankStorageType_returns400() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Test", "", null, null);

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

    @Test
    @DisplayName("Lagerort mit ungültigem StorageType → 400")
    void createStorageLocation_withInvalidStorageType_returns400() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Test", "INVALID_TYPE", null, null);

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_STORAGE_TYPE"));
    }

    @Test
    @DisplayName("Lagerort mit min >= max Temperatur → 400")
    void createStorageLocation_withInvalidTemperatureRange_returns400() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Test", "COLD_ROOM", "10", "5");

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_TEMPERATURE_RANGE"));
    }

    @Test
    @DisplayName("Lagerort mit Temperatur außerhalb des Bereichs → 400")
    void createStorageLocation_withTemperatureOutOfRange_returns400() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Test", "FREEZER", "-51", "-20");

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_TEMPERATURE_RANGE"));
    }

    // ==================== Duplikat-Name ====================

    @Test
    @DisplayName("Lagerort mit doppeltem Namen → 409")
    void createStorageLocation_withDuplicateName_returns409() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Kühlraum Unique", "COLD_ROOM", null, null);

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated());

        mockMvc.perform(post("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isConflict())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NAME_EXISTS"));
    }

    // ==================== Autorisierung ====================

    @Test
    @DisplayName("Lagerort erstellen ohne STOCK_WRITE → 403")
    void createStorageLocation_withViewerToken_returns403() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Kein Zugriff", "DRY_STORAGE", null, null);

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

    @Test
    @DisplayName("Lagerort erstellen ohne Token → 401")
    void createStorageLocation_withoutToken_returns401() throws Exception {
        var request = new CreateStorageLocationRequest(
                "Kein Token", "DRY_STORAGE", null, null);

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

    // ==================== Alle StorageTypes ====================

    @Test
    @DisplayName("Alle StorageTypes erstellen → jeweils 201")
    void createStorageLocation_allTypes_returns201() throws Exception {
        String[] types = {"COLD_ROOM", "FREEZER", "DRY_STORAGE", "DISPLAY_COUNTER", "PRODUCTION_AREA"};

        for (String type : types) {
            var request = new CreateStorageLocationRequest(
                    "Lager " + type, type, null, null);

            mockMvc.perform(post("/api/inventory/storage-locations")
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("$.storageType").value(type));
        }
    }

    // ==================== Einzelnen Lagerort abfragen ====================

    @Test
    @DisplayName("Lagerort per ID abfragen → 200")
    void getStorageLocation_existing_returns200() throws Exception {
        String id = createAndReturnId("Detail Test", "COLD_ROOM", "-2", "8");

        mockMvc.perform(get("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.name").value("Detail Test"))
                .andExpect(jsonPath("$.storageType").value("COLD_ROOM"))
                .andExpect(jsonPath("$.temperatureRange.minTemperature").value(-2))
                .andExpect(jsonPath("$.temperatureRange.maxTemperature").value(8))
                .andExpect(jsonPath("$.active").value(true));
    }

    @Test
    @DisplayName("Nicht existierenden Lagerort per ID abfragen → 404")
    void getStorageLocation_notFound_returns404() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID())
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
    }

    @Test
    @DisplayName("Lagerort per ID abfragen mit STOCK_READ → 200")
    void getStorageLocation_withReaderToken_returns200() throws Exception {
        String id = createAndReturnId("Reader Detail", "DRY_STORAGE", null, null);

        mockMvc.perform(get("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + readerToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.name").value("Reader Detail"));
    }

    @Test
    @DisplayName("Lagerort per ID abfragen ohne passende Berechtigung → 403")
    void getStorageLocation_withViewerToken_returns403() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID())
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("Lagerort per ID abfragen ohne Token → 401")
    void getStorageLocation_withoutToken_returns401() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations/" + UUID.randomUUID()))
                .andExpect(status().isUnauthorized());
    }

    // ==================== Lagerort bearbeiten (Story 1.2) ====================

    @Test
    @DisplayName("Lagerort Name ändern → 200")
    void updateStorageLocation_changeName_returns200() throws Exception {
        String id = createAndReturnId("Update Test", "DRY_STORAGE", null, null);

        var request = new UpdateStorageLocationRequest("Neuer Name", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.name").value("Neuer Name"))
                .andExpect(jsonPath("$.storageType").value("DRY_STORAGE"));
    }

    @Test
    @DisplayName("Lagerort Temperaturbereich ändern → 200")
    void updateStorageLocation_changeTemperatureRange_returns200() throws Exception {
        String id = createAndReturnId("Temp Update", "COLD_ROOM", "-2", "8");

        var request = new UpdateStorageLocationRequest(null, "-5", "12");

        mockMvc.perform(put("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Temp Update"))
                .andExpect(jsonPath("$.temperatureRange.minTemperature").value(-5))
                .andExpect(jsonPath("$.temperatureRange.maxTemperature").value(12));
    }

    @Test
    @DisplayName("Lagerort mit ungültigem Namen aktualisieren → 400")
    void updateStorageLocation_withBlankName_returns400() throws Exception {
        String id = createAndReturnId("Blank Update", "DRY_STORAGE", null, null);

        var request = new UpdateStorageLocationRequest("", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_STORAGE_LOCATION_NAME"));
    }

    @Test
    @DisplayName("Lagerort mit doppeltem Namen aktualisieren → 409")
    void updateStorageLocation_withDuplicateName_returns409() throws Exception {
        createAndReturnId("Existing Name", "DRY_STORAGE", null, null);
        String id = createAndReturnId("To Rename", "COLD_ROOM", null, null);

        var request = new UpdateStorageLocationRequest("Existing Name", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isConflict())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NAME_EXISTS"));
    }

    @Test
    @DisplayName("Lagerort mit eigenem Namen aktualisieren → 200 (kein Duplikat)")
    void updateStorageLocation_withOwnName_returns200() throws Exception {
        String id = createAndReturnId("Same Name", "DRY_STORAGE", null, null);

        var request = new UpdateStorageLocationRequest("Same Name", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + id)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Same Name"));
    }

    @Test
    @DisplayName("Nicht existierenden Lagerort aktualisieren → 404")
    void updateStorageLocation_notFound_returns404() throws Exception {
        var request = new UpdateStorageLocationRequest("New Name", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + UUID.randomUUID())
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
    }

    @Test
    @DisplayName("Lagerort aktualisieren ohne STOCK_WRITE → 403")
    void updateStorageLocation_withViewerToken_returns403() throws Exception {
        var request = new UpdateStorageLocationRequest("Name", null, null);

        mockMvc.perform(put("/api/inventory/storage-locations/" + UUID.randomUUID())
                        .header("Authorization", "Bearer " + viewerToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isForbidden());
    }

    // ==================== Lagerort deaktivieren (Story 1.2) ====================

    @Test
    @DisplayName("Aktiven Lagerort deaktivieren → 200")
    void deactivateStorageLocation_active_returns200() throws Exception {
        String id = createAndReturnId("Deactivate Test", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.active").value(false));
    }

    @Test
    @DisplayName("Lagerort mit Bestand deaktivieren → 409")
    void deactivateStorageLocation_withStock_returns409() throws Exception {
        String id = createAndReturnId("Stock Location", "DRY_STORAGE", null, null);

        // Stock an diesem Lagerort anlegen
        var stockRequest = new CreateStockRequest(
                createArticleId(), id, null, null, null);
        mockMvc.perform(post("/api/inventory/stocks")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(stockRequest)))
                .andExpect(status().isCreated());

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isConflict())
                .andExpect(jsonPath("$.code").value("STOCK_EXISTS_AT_LOCATION"));
    }

    @Test
    @DisplayName("Bereits inaktiven Lagerort deaktivieren → 409")
    void deactivateStorageLocation_alreadyInactive_returns409() throws Exception {
        String id = createAndReturnId("Double Deactivate", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isConflict())
                .andExpect(jsonPath("$.code").value("ALREADY_INACTIVE"));
    }

    @Test
    @DisplayName("Nicht existierenden Lagerort deaktivieren → 404")
    void deactivateStorageLocation_notFound_returns404() throws Exception {
        mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
    }

    @Test
    @DisplayName("Lagerort deaktivieren ohne STOCK_WRITE → 403")
    void deactivateStorageLocation_withViewerToken_returns403() throws Exception {
        mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/deactivate")
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isForbidden());
    }

    // ==================== Lagerort aktivieren (Story 1.2) ====================

    @Test
    @DisplayName("Inaktiven Lagerort aktivieren → 200")
    void activateStorageLocation_inactive_returns200() throws Exception {
        String id = createAndReturnId("Activate Test", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/activate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.active").value(true));
    }

    @Test
    @DisplayName("Bereits aktiven Lagerort aktivieren → 409")
    void activateStorageLocation_alreadyActive_returns409() throws Exception {
        String id = createAndReturnId("Double Activate", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + id + "/activate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isConflict())
                .andExpect(jsonPath("$.code").value("ALREADY_ACTIVE"));
    }

    @Test
    @DisplayName("Nicht existierenden Lagerort aktivieren → 404")
    void activateStorageLocation_notFound_returns404() throws Exception {
        mockMvc.perform(patch("/api/inventory/storage-locations/" + UUID.randomUUID() + "/activate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.code").value("STORAGE_LOCATION_NOT_FOUND"));
    }

    // ==================== Lagerorte abfragen (Story 1.3) ====================

    @Test
    @DisplayName("Alle Lagerorte abfragen → 200 mit Liste")
    void listStorageLocations_returnsAll() throws Exception {
        createAndReturnId("Lager A", "DRY_STORAGE", null, null);
        createAndReturnId("Lager B", "COLD_ROOM", "-2", "8");

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[*].name", containsInAnyOrder("Lager A", "Lager B")))
                .andExpect(jsonPath("$[0].id").isNotEmpty())
                .andExpect(jsonPath("$[0].storageType").isNotEmpty())
                .andExpect(jsonPath("$[0].active").isBoolean());
    }

    @Test
    @DisplayName("Lagerorte nach StorageType filtern → 200")
    void listStorageLocations_filterByStorageType() throws Exception {
        createAndReturnId("Kühlraum", "COLD_ROOM", "-2", "8");
        createAndReturnId("Trockenlager", "DRY_STORAGE", null, null);
        createAndReturnId("Gefrierschrank", "FREEZER", "-25", "-18");

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("storageType", "COLD_ROOM")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].storageType").value("COLD_ROOM"));
    }

    @Test
    @DisplayName("Lagerorte nach active=true filtern → 200 nur aktive")
    void listStorageLocations_filterByActiveTrue() throws Exception {
        String activeId = createAndReturnId("Aktiv", "DRY_STORAGE", null, null);
        String inactiveId = createAndReturnId("Inaktiv", "COLD_ROOM", null, null);

        // Deaktivieren
        mockMvc.perform(patch("/api/inventory/storage-locations/" + inactiveId + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("active", "true")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value("Aktiv"));
    }

    @Test
    @DisplayName("Lagerorte nach active=false filtern → 200 nur inaktive")
    void listStorageLocations_filterByActiveFalse() throws Exception {
        createAndReturnId("Aktiv", "DRY_STORAGE", null, null);
        String inactiveId = createAndReturnId("Inaktiv", "COLD_ROOM", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + inactiveId + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("active", "false")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value("Inaktiv"));
    }

    @Test
    @DisplayName("Lagerorte mit storageType und active=true filtern → 200")
    void listStorageLocations_filterByStorageTypeAndActiveTrue() throws Exception {
        createAndReturnId("Kühl Aktiv", "COLD_ROOM", null, null);
        String inactiveId = createAndReturnId("Kühl Inaktiv", "COLD_ROOM", null, null);
        createAndReturnId("Trocken Aktiv", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + inactiveId + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("storageType", "COLD_ROOM")
                        .param("active", "true")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value("Kühl Aktiv"));
    }

    @Test
    @DisplayName("Lagerorte mit storageType und active=false filtern → 200 nur inaktive des Typs")
    void listStorageLocations_filterByStorageTypeAndActiveFalse() throws Exception {
        createAndReturnId("Kühl Aktiv", "COLD_ROOM", null, null);
        String inactiveId = createAndReturnId("Kühl Inaktiv", "COLD_ROOM", null, null);
        createAndReturnId("Trocken Aktiv", "DRY_STORAGE", null, null);

        mockMvc.perform(patch("/api/inventory/storage-locations/" + inactiveId + "/deactivate")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("storageType", "COLD_ROOM")
                        .param("active", "false")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value("Kühl Inaktiv"));
    }

    @Test
    @DisplayName("Lagerorte mit ungültigem StorageType filtern → 400")
    void listStorageLocations_invalidStorageType_returns400() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations")
                        .param("storageType", "INVALID")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_STORAGE_TYPE"));
    }

    @Test
    @DisplayName("Lagerorte abfragen mit STOCK_READ → 200")
    void listStorageLocations_withReaderToken_returns200() throws Exception {
        createAndReturnId("Reader Test", "DRY_STORAGE", null, null);

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + readerToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)));
    }

    @Test
    @DisplayName("Lagerorte abfragen ohne passende Berechtigung → 403")
    void listStorageLocations_withViewerToken_returns403() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("Lagerorte abfragen ohne Token → 401")
    void listStorageLocations_withoutToken_returns401() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("Leere Liste wenn keine Lagerorte existieren → 200")
    void listStorageLocations_empty_returns200() throws Exception {
        mockMvc.perform(get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
    }

    @Test
    @DisplayName("Lagerorte enthalten vollständige Daten (id, name, storageType, temperatureRange, active)")
    void listStorageLocations_returnsCompleteData() throws Exception {
        createAndReturnId("Vollständig", "COLD_ROOM", "-2", "8");

        mockMvc.perform(get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].id").isNotEmpty())
                .andExpect(jsonPath("$[0].name").value("Vollständig"))
                .andExpect(jsonPath("$[0].storageType").value("COLD_ROOM"))
                .andExpect(jsonPath("$[0].temperatureRange.minTemperature").value(-2))
                .andExpect(jsonPath("$[0].temperatureRange.maxTemperature").value(8))
                .andExpect(jsonPath("$[0].active").value(true));
    }

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

    private String createAndReturnId(String name, String storageType, String minTemp, String maxTemp) throws Exception {
        var request = new CreateStorageLocationRequest(name, storageType, minTemp, maxTemp);

        var result = mockMvc.perform(post("/api/inventory/storage-locations")
                        .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();
    }
}
