package de.effigenix.infrastructure.production.web;

import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
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.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@DisplayName("ListBatches Integration Tests")
class ListBatchesIntegrationTest 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("batch.admin", "batch.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
        String viewerId = createUser("batch.viewer", "batch.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");

        adminToken = generateToken(adminId, "batch.admin", "BATCH_WRITE,BATCH_READ,RECIPE_WRITE,RECIPE_READ");
        viewerToken = generateToken(viewerId, "batch.viewer", "USER_READ");
    }

    @Test
    @DisplayName("Alle Batches auflisten → 200 mit Summary-Format")
    void listBatches_returnsAllWithSummaryFormat() throws Exception {
        createBatch("2026-03-01");
        createBatch("2026-03-02");

        mockMvc.perform(get("/api/production/batches")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id").isNotEmpty())
                .andExpect(jsonPath("$[0].batchNumber").isNotEmpty())
                .andExpect(jsonPath("$[0].status").value("PLANNED"));
    }

    @Test
    @DisplayName("Leere Liste → 200 mit leerem Array")
    void listBatches_empty_returns200WithEmptyArray() throws Exception {
        mockMvc.perform(get("/api/production/batches")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
    }

    @Test
    @DisplayName("Batches nach Status filtern → 200 nur passende")
    void listBatches_filteredByStatus_returnsOnlyMatching() throws Exception {
        createBatch("2026-03-01");

        mockMvc.perform(get("/api/production/batches")
                        .param("status", "PLANNED")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].status").value("PLANNED"));
    }

    @Test
    @DisplayName("Ungültiger Status-Parameter → 400")
    void listBatches_invalidStatus_returns400() throws Exception {
        mockMvc.perform(get("/api/production/batches")
                        .param("status", "INVALID")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("BATCH_VALIDATION_ERROR"));
    }

    @Test
    @DisplayName("Batches nach Produktionsdatum filtern → 200")
    void listBatches_filteredByProductionDate_returnsOnlyMatching() throws Exception {
        createBatch("2026-03-01");
        createBatch("2026-03-02");

        mockMvc.perform(get("/api/production/batches")
                        .param("productionDate", "2026-03-01")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].productionDate").value("2026-03-01"));
    }

    @Test
    @DisplayName("Batches nach articleId filtern → 200")
    void listBatches_filteredByArticleId_returnsOnlyMatching() throws Exception {
        createBatch("2026-03-01");

        mockMvc.perform(get("/api/production/batches")
                        .param("articleId", "article-123")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)));
    }

    @Test
    @DisplayName("Unbekannte articleId → 200 leeres Array")
    void listBatches_unknownArticleId_returnsEmptyArray() throws Exception {
        mockMvc.perform(get("/api/production/batches")
                        .param("articleId", "unknown-article")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
    }

    @Test
    @DisplayName("Mehrere Filter gleichzeitig → 400")
    void listBatches_multipleFilters_returns400() throws Exception {
        mockMvc.perform(get("/api/production/batches")
                        .param("status", "PLANNED")
                        .param("articleId", "article-123")
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("BATCH_VALIDATION_ERROR"));
    }

    @Test
    @DisplayName("Batches auflisten ohne BATCH_READ → 403")
    void listBatches_withoutPermission_returns403() throws Exception {
        mockMvc.perform(get("/api/production/batches")
                        .header("Authorization", "Bearer " + viewerToken))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("Batches auflisten ohne Token → 401")
    void listBatches_withoutToken_returns401() throws Exception {
        mockMvc.perform(get("/api/production/batches"))
                .andExpect(status().isUnauthorized());
    }

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

    private void createBatch(String productionDate) throws Exception {
        String recipeId = createActiveRecipe();

        String json = """
                {
                    "recipeId": "%s",
                    "plannedQuantity": "100",
                    "plannedQuantityUnit": "KILOGRAM",
                    "productionDate": "%s",
                    "bestBeforeDate": "2026-12-01"
                }
                """.formatted(recipeId, productionDate);

        mockMvc.perform(post("/api/production/batches")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(json))
                .andExpect(status().isCreated());
    }

    private String createActiveRecipe() throws Exception {
        String recipeJson = """
                {
                    "name": "Test-Rezept-%s",
                    "version": 1,
                    "type": "FINISHED_PRODUCT",
                    "description": "Testrezept",
                    "yieldPercentage": 85,
                    "shelfLifeDays": 14,
                    "outputQuantity": "100",
                    "outputUom": "KILOGRAM",
                    "articleId": "article-123"
                }
                """.formatted(UUID.randomUUID().toString().substring(0, 8));

        var result = mockMvc.perform(post("/api/recipes")
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(recipeJson))
                .andExpect(status().isCreated())
                .andReturn();

        String recipeId = objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText();

        String ingredientJson = """
                {"position": 1, "articleId": "%s", "quantity": "5.5", "uom": "KILOGRAM", "substitutable": false}
                """.formatted(UUID.randomUUID().toString());

        mockMvc.perform(post("/api/recipes/{id}/ingredients", recipeId)
                        .header("Authorization", "Bearer " + adminToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(ingredientJson))
                .andExpect(status().isCreated());

        mockMvc.perform(post("/api/recipes/{id}/activate", recipeId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        return recipeId;
    }
}
