package de.effigenix.domain.production;

import de.effigenix.shared.common.UnitOfMeasure;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("Ingredient Entity")
class IngredientTest {

    @Nested
    @DisplayName("create()")
    class Create {

        @Test
        @DisplayName("should create ingredient with valid inputs")
        void should_CreateIngredient_When_ValidInputs() {
            var draft = new IngredientDraft(1, "article-123", "5.5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var ingredient = result.unsafeGetValue();
            assertThat(ingredient.id()).isNotNull();
            assertThat(ingredient.position()).isEqualTo(1);
            assertThat(ingredient.articleId()).isEqualTo("article-123");
            assertThat(ingredient.quantity().amount()).isEqualByComparingTo("5.5");
            assertThat(ingredient.quantity().uom()).isEqualTo(UnitOfMeasure.KILOGRAM);
            assertThat(ingredient.subRecipeId()).isNull();
            assertThat(ingredient.substitutable()).isFalse();
        }

        @Test
        @DisplayName("should fail when position is 0")
        void should_Fail_When_PositionIsZero() {
            var draft = new IngredientDraft(0, "article-123", "5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when position is negative")
        void should_Fail_When_PositionIsNegative() {
            var draft = new IngredientDraft(-1, "article-123", "5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when articleId is blank")
        void should_Fail_When_ArticleIdIsBlank() {
            var draft = new IngredientDraft(1, "", "5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when articleId is null")
        void should_Fail_When_ArticleIdIsNull() {
            var draft = new IngredientDraft(1, null, "5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when quantity is zero")
        void should_Fail_When_QuantityIsZero() {
            var draft = new IngredientDraft(1, "article-123", "0", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when quantity is negative")
        void should_Fail_When_QuantityIsNegative() {
            var draft = new IngredientDraft(1, "article-123", "-5", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when UoM is invalid")
        void should_Fail_When_UomIsInvalid() {
            var draft = new IngredientDraft(1, "article-123", "5", "INVALID", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should fail when quantity is not a number")
        void should_Fail_When_QuantityIsNotANumber() {
            var draft = new IngredientDraft(1, "article-123", "abc", "KILOGRAM", null, false);

            var result = Ingredient.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.ValidationFailure.class);
        }

        @Test
        @DisplayName("should create ingredient with subRecipeId")
        void should_CreateIngredient_WithSubRecipeId() {
            var draft = new IngredientDraft(1, "article-123", "10", "GRAM", "sub-recipe-456", true);

            var result = Ingredient.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var ingredient = result.unsafeGetValue();
            assertThat(ingredient.subRecipeId()).isEqualTo("sub-recipe-456");
            assertThat(ingredient.substitutable()).isTrue();
        }
    }

    @Nested
    @DisplayName("Equality")
    class Equality {

        @Test
        @DisplayName("should be equal when same ID")
        void should_BeEqual_When_SameId() {
            var ingredient = Ingredient.create(
                    new IngredientDraft(1, "article-123", "5", "KILOGRAM", null, false)
            ).unsafeGetValue();

            var reconstituted = Ingredient.reconstitute(
                    ingredient.id(), 2, "other-article", ingredient.quantity(), "sub", true
            );

            assertThat(ingredient).isEqualTo(reconstituted);
            assertThat(ingredient.hashCode()).isEqualTo(reconstituted.hashCode());
        }

        @Test
        @DisplayName("should not be equal when different ID")
        void should_NotBeEqual_When_DifferentId() {
            var ingredient1 = Ingredient.create(
                    new IngredientDraft(1, "article-123", "5", "KILOGRAM", null, false)
            ).unsafeGetValue();
            var ingredient2 = Ingredient.create(
                    new IngredientDraft(1, "article-123", "5", "KILOGRAM", null, false)
            ).unsafeGetValue();

            assertThat(ingredient1).isNotEqualTo(ingredient2);
        }
    }
}
