package de.effigenix.infrastructure.production.web;

import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.production.web.dto.PlanBatchRequest;
import de.effigenix.infrastructure.production.web.dto.RecordConsumptionRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

import java.time.LocalDate;
import java.util.Set;
import java.util.UUID;

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("RecordConsumption Integration Tests")
class RecordConsumptionIntegrationTest extends AbstractIntegrationTest {

    private String adminToken;
    private String viewerToken;

    private static final LocalDate PRODUCTION_DATE = LocalDate.of(2026, 3, 1);
    private static final LocalDate BEST_BEFORE_DATE = LocalDate.of(2026, 6, 1);

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

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

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

    @Nested
    @DisplayName("POST /api/production/batches/{id}/consumptions – Verbrauch dokumentieren")
    class RecordConsumptionEndpoint {

        @Test
        @DisplayName("Verbrauch dokumentieren bei IN_PRODUCTION → 201")
        void recordConsumption_withInProductionBatch_returns201() throws Exception {
            String batchId = createInProductionBatch();
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "10.5", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("$.id").isNotEmpty())
                    .andExpect(jsonPath("$.inputBatchId").value(request.inputBatchId()))
                    .andExpect(jsonPath("$.articleId").value(request.articleId()))
                    .andExpect(jsonPath("$.quantityUsed").value("10.500000"))
                    .andExpect(jsonPath("$.quantityUsedUnit").value("KILOGRAM"))
                    .andExpect(jsonPath("$.consumedAt").isNotEmpty());
        }

        @Test
        @DisplayName("Verbrauch wird in BatchResponse angezeigt")
        void recordConsumption_visibleInBatchResponse() throws Exception {
            String batchId = createInProductionBatch();
            String inputBatchId = UUID.randomUUID().toString();
            var request = new RecordConsumptionRequest(inputBatchId, UUID.randomUUID().toString(), "5.0", "LITER");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isCreated());

            mockMvc.perform(get("/api/production/batches/{id}", batchId)
                            .header("Authorization", "Bearer " + adminToken))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.consumptions").isArray())
                    .andExpect(jsonPath("$.consumptions.length()").value(1))
                    .andExpect(jsonPath("$.consumptions[0].inputBatchId").value(inputBatchId));
        }

        @Test
        @DisplayName("Mehrere Verbrauchseinträge möglich")
        void recordConsumption_multipleConsumptions() throws Exception {
            String batchId = createInProductionBatch();

            var request1 = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "KILOGRAM");
            var request2 = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "3.0", "LITER");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request1)))
                    .andExpect(status().isCreated());

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request2)))
                    .andExpect(status().isCreated());

            mockMvc.perform(get("/api/production/batches/{id}", batchId)
                            .header("Authorization", "Bearer " + adminToken))
                    .andExpect(jsonPath("$.consumptions.length()").value(2));
        }

        @Test
        @DisplayName("Doppelte InputBatchId → 409")
        void recordConsumption_duplicateInputBatch_returns409() throws Exception {
            String batchId = createInProductionBatch();
            String inputBatchId = UUID.randomUUID().toString();

            var request = new RecordConsumptionRequest(inputBatchId, UUID.randomUUID().toString(), "5.0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isCreated());

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isConflict())
                    .andExpect(jsonPath("$.code").value("BATCH_DUPLICATE_INPUT_BATCH"));
        }

        @Test
        @DisplayName("Charge nicht IN_PRODUCTION (PLANNED) → 409")
        void recordConsumption_plannedBatch_returns409() throws Exception {
            String batchId = createPlannedBatch();
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isConflict())
                    .andExpect(jsonPath("$.code").value("BATCH_NOT_IN_PRODUCTION"));
        }

        @Test
        @DisplayName("Ungültige Menge (0) → 400")
        void recordConsumption_zeroQuantity_returns400() throws Exception {
            String batchId = createInProductionBatch();
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isBadRequest())
                    .andExpect(jsonPath("$.code").value("BATCH_INVALID_CONSUMPTION_QUANTITY"));
        }

        @Test
        @DisplayName("Ungültige Unit → 400")
        void recordConsumption_invalidUnit_returns400() throws Exception {
            String batchId = createInProductionBatch();
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "INVALID_UNIT");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", batchId)
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isBadRequest())
                    .andExpect(jsonPath("$.code").value("BATCH_INVALID_CONSUMPTION_QUANTITY"));
        }

        @Test
        @DisplayName("Charge nicht gefunden → 404")
        void recordConsumption_batchNotFound_returns404() throws Exception {
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", UUID.randomUUID().toString())
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isNotFound())
                    .andExpect(jsonPath("$.code").value("BATCH_NOT_FOUND"));
        }

        @Test
        @DisplayName("Leere Pflichtfelder → 400 (Bean Validation)")
        void recordConsumption_blankFields_returns400() throws Exception {
            var request = new RecordConsumptionRequest("", "", "", "");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", UUID.randomUUID().toString())
                            .header("Authorization", "Bearer " + adminToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isBadRequest());
        }
    }

    @Nested
    @DisplayName("Authorization")
    class AuthTests {

        @Test
        @DisplayName("Ohne BATCH_WRITE → 403")
        void recordConsumption_withViewerToken_returns403() throws Exception {
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", UUID.randomUUID().toString())
                            .header("Authorization", "Bearer " + viewerToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isForbidden());
        }

        @Test
        @DisplayName("Ohne Token → 401")
        void recordConsumption_withoutToken_returns401() throws Exception {
            var request = new RecordConsumptionRequest(
                    UUID.randomUUID().toString(), UUID.randomUUID().toString(), "5.0", "KILOGRAM");

            mockMvc.perform(post("/api/production/batches/{id}/consumptions", UUID.randomUUID().toString())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isUnauthorized());
        }
    }

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

    private String createPlannedBatch() throws Exception {
        String recipeId = createActiveRecipe();
        var request = new PlanBatchRequest(recipeId, "100", "KILOGRAM", PRODUCTION_DATE, BEST_BEFORE_DATE);

        var result = mockMvc.perform(post("/api/production/batches")
                        .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 createInProductionBatch() throws Exception {
        String batchId = createPlannedBatch();

        mockMvc.perform(post("/api/production/batches/{id}/start", batchId)
                        .header("Authorization", "Bearer " + adminToken))
                .andExpect(status().isOk());

        return batchId;
    }

    private String createActiveRecipe() throws Exception {
        String json = """
                {
                    "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(json))
                .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;
    }
}
