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.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

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

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

@DisplayName("Recipe Lifecycle Integration Tests")
class RecipeLifecycleIntegrationTest extends AbstractIntegrationTest {

    private String adminToken;

    @BeforeEach
    void setUp() {
        String adminRoleId = createRole(RoleName.ADMIN, "Admin");
        String adminId = createUser("recipe.admin", "recipe.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
        adminToken = generateToken(adminId, "recipe.admin", "RECIPE_WRITE,RECIPE_READ");
    }

    // ==================== Aktivierung ====================

    @Nested
    @DisplayName("POST /api/recipes/{id}/activate – Rezept aktivieren")
    class ActivateRecipe {

        @Test
        @DisplayName("Rezept ohne Zutaten aktivieren → 400")
        void activateRecipe_withoutIngredients_returns400() throws Exception {
            String recipeId = createDraftRecipe();

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

    // ==================== Modifikation aktiver Rezepte ====================

    @Nested
    @DisplayName("Modifikation aktiver Rezepte")
    class ModifyActiveRecipe {

        @Test
        @DisplayName("Zutat zu aktivem Rezept hinzufügen → 409")
        void addIngredient_toActiveRecipe_returns409() throws Exception {
            String recipeId = createActiveRecipe();

            String ingredientJson = """
                    {"position": 2, "articleId": "%s", "quantity": "3.0", "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().isConflict());
        }

        @Test
        @DisplayName("Schritt zu aktivem Rezept hinzufügen → 409")
        void addStep_toActiveRecipe_returns409() throws Exception {
            String recipeId = createActiveRecipe();

            String stepJson = """
                    {"stepNumber": 1, "description": "Mischen", "durationMinutes": 15}
                    """;

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

    // ==================== Archivierung ====================

    @Nested
    @DisplayName("POST /api/recipes/{id}/archive – Rezept archivieren")
    class ArchiveRecipe {

        @Test
        @DisplayName("Aktives Rezept archivieren → 200, Status ARCHIVED")
        void archiveRecipe_fromActive_returns200() throws Exception {
            String recipeId = createActiveRecipe();

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

        @Test
        @DisplayName("Draft-Rezept archivieren → 409 (ungültiger Übergang)")
        void archiveRecipe_fromDraft_returns409() throws Exception {
            String recipeId = createDraftRecipe();

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

    // ==================== Duplikate ====================

    @Nested
    @DisplayName("Duplikat-Prüfungen")
    class DuplicateChecks {

        @Test
        @DisplayName("Zutat mit doppelter Position → 409")
        void addIngredient_duplicatePosition_returns409() throws Exception {
            String recipeId = createDraftRecipe();

            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());

            String duplicateIngredientJson = """
                    {"position": 1, "articleId": "%s", "quantity": "3.0", "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(duplicateIngredientJson))
                    .andExpect(status().isConflict());
        }

        @Test
        @DisplayName("Schritt mit doppelter Schrittnummer → 409")
        void addStep_duplicateStepNumber_returns409() throws Exception {
            String recipeId = createDraftRecipe();

            String stepJson = """
                    {"stepNumber": 1, "description": "Mischen", "durationMinutes": 15}
                    """;

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

            String duplicateStepJson = """
                    {"stepNumber": 1, "description": "Anderer Schritt", "durationMinutes": 10}
                    """;

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

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

    private String createDraftRecipe() 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();

        return objectMapper.readTree(result.getResponse().getContentAsString()).get("id").asText();
    }

    private String createActiveRecipe() throws Exception {
        String recipeId = createDraftRecipe();

        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;
    }
}
