package de.effigenix.domain.production;

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("ProductionStep Entity")
class ProductionStepTest {

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

        @Test
        @DisplayName("should create production step with valid inputs")
        void should_CreateStep_When_ValidInputs() {
            var draft = new ProductionStepDraft(1, "Fleisch wolfen", 15, 4);

            var result = ProductionStep.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var step = result.unsafeGetValue();
            assertThat(step.id()).isNotNull();
            assertThat(step.stepNumber()).isEqualTo(1);
            assertThat(step.description()).isEqualTo("Fleisch wolfen");
            assertThat(step.durationMinutes()).isEqualTo(15);
            assertThat(step.temperatureCelsius()).isEqualTo(4);
        }

        @Test
        @DisplayName("should create production step with nullable fields as null")
        void should_CreateStep_When_NullableFieldsAreNull() {
            var draft = new ProductionStepDraft(1, "Mischen", null, null);

            var result = ProductionStep.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var step = result.unsafeGetValue();
            assertThat(step.durationMinutes()).isNull();
            assertThat(step.temperatureCelsius()).isNull();
        }

        @Test
        @DisplayName("should fail when step number is 0")
        void should_Fail_When_StepNumberIsZero() {
            var draft = new ProductionStepDraft(0, "Mischen", null, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when step number is negative")
        void should_Fail_When_StepNumberIsNegative() {
            var draft = new ProductionStepDraft(-1, "Mischen", null, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when description is blank")
        void should_Fail_When_DescriptionIsBlank() {
            var draft = new ProductionStepDraft(1, "", null, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when description is null")
        void should_Fail_When_DescriptionIsNull() {
            var draft = new ProductionStepDraft(1, null, null, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when duration minutes is zero")
        void should_Fail_When_DurationMinutesIsZero() {
            var draft = new ProductionStepDraft(1, "Mischen", 0, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when duration minutes is negative")
        void should_Fail_When_DurationMinutesIsNegative() {
            var draft = new ProductionStepDraft(1, "Mischen", -5, null);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when temperature is below absolute zero")
        void should_Fail_When_TemperatureBelowAbsoluteZero() {
            var draft = new ProductionStepDraft(1, "Mischen", null, -274);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should fail when temperature is above 1000")
        void should_Fail_When_TemperatureAbove1000() {
            var draft = new ProductionStepDraft(1, "Mischen", null, 1001);

            var result = ProductionStep.create(draft);

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

        @Test
        @DisplayName("should allow negative temperature within valid range")
        void should_AllowNegativeTemperature_WhenInRange() {
            var draft = new ProductionStepDraft(1, "Tiefkühlen", null, -18);

            var result = ProductionStep.create(draft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue().temperatureCelsius()).isEqualTo(-18);
        }
    }

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

        @Test
        @DisplayName("should be equal when same ID")
        void should_BeEqual_When_SameId() {
            var step = ProductionStep.create(
                    new ProductionStepDraft(1, "Mischen", 10, null)
            ).unsafeGetValue();

            var reconstituted = ProductionStep.reconstitute(
                    step.id(), 2, "Other description", 20, 80
            );

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

        @Test
        @DisplayName("should not be equal when different ID")
        void should_NotBeEqual_When_DifferentId() {
            var step1 = ProductionStep.create(
                    new ProductionStepDraft(1, "Mischen", null, null)
            ).unsafeGetValue();
            var step2 = ProductionStep.create(
                    new ProductionStepDraft(1, "Mischen", null, null)
            ).unsafeGetValue();

            assertThat(step1).isNotEqualTo(step2);
        }
    }
}
