package de.effigenix.domain.production;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.math.BigDecimal;

import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.QuantityError;

import static de.effigenix.shared.common.UnitOfMeasure.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@DisplayName("YieldPercentage Value Object")
class YieldPercentageTest {

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

        @Test
        @DisplayName("should create YieldPercentage with valid value")
        void should_Create_When_ValidValue() {
            var result = YieldPercentage.of(80);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue().value()).isEqualTo(80);
        }

        @ParameterizedTest
        @ValueSource(ints = {1, 100, 200})
        @DisplayName("should accept boundary values")
        void should_Accept_When_BoundaryValues(int value) {
            var result = YieldPercentage.of(value);
            assertThat(result.isSuccess()).isTrue();
        }

        @Test
        @DisplayName("should fail when value is 0")
        void should_Fail_When_ValueIsZero() {
            var result = YieldPercentage.of(0);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(QuantityError.InvalidYieldPercentage.class);
        }

        @Test
        @DisplayName("should fail when value is 201")
        void should_Fail_When_ValueIs201() {
            var result = YieldPercentage.of(201);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(QuantityError.InvalidYieldPercentage.class);
        }

        @ParameterizedTest
        @ValueSource(ints = {-1, 0, 201, 300})
        @DisplayName("should fail for out-of-range values")
        void should_Fail_When_OutOfRange(int value) {
            var result = YieldPercentage.of(value);
            assertThat(result.isFailure()).isTrue();
        }

        @Test
        @DisplayName("should throw from constructor for invalid value")
        void should_ThrowFromConstructor_When_InvalidValue() {
            assertThatThrownBy(() -> new YieldPercentage(0))
                    .isInstanceOf(IllegalArgumentException.class);
        }
    }

    @Nested
    @DisplayName("calculateRequiredInput()")
    class CalculateRequiredInput {

        @Test
        @DisplayName("80% yield: 100kg output requires 125kg input")
        void should_Calculate125kg_When_80PercentYieldAnd100kg() {
            var yield80 = new YieldPercentage(80);
            var output = Quantity.of(new BigDecimal("100"), KILOGRAM).unsafeGetValue();

            var input = yield80.calculateRequiredInput(output);

            assertThat(input.amount()).isEqualByComparingTo(new BigDecimal("125"));
            assertThat(input.uom()).isEqualTo(KILOGRAM);
        }

        @Test
        @DisplayName("100% yield: input equals output")
        void should_ReturnSameAmount_When_100PercentYield() {
            var yield100 = new YieldPercentage(100);
            var output = Quantity.of(new BigDecimal("50"), LITER).unsafeGetValue();

            var input = yield100.calculateRequiredInput(output);

            assertThat(input.amount()).isEqualByComparingTo(new BigDecimal("50"));
            assertThat(input.uom()).isEqualTo(LITER);
        }

        @Test
        @DisplayName("50% yield: 100kg output requires 200kg input")
        void should_Calculate200kg_When_50PercentYieldAnd100kg() {
            var yield50 = new YieldPercentage(50);
            var output = Quantity.of(new BigDecimal("100"), KILOGRAM).unsafeGetValue();

            var input = yield50.calculateRequiredInput(output);

            assertThat(input.amount()).isEqualByComparingTo(new BigDecimal("200"));
        }

        @Test
        @DisplayName("should handle dual quantities")
        void should_HandleDualQuantity() {
            var yield80 = new YieldPercentage(80);
            var output = Quantity.dual(
                    new BigDecimal("10"), PIECE,
                    new BigDecimal("20"), KILOGRAM).unsafeGetValue();

            var input = yield80.calculateRequiredInput(output);

            assertThat(input.amount()).isEqualByComparingTo(new BigDecimal("12.5"));
            assertThat(input.secondaryAmount()).isEqualByComparingTo(new BigDecimal("25"));
        }
    }

    @Nested
    @DisplayName("asFactor()")
    class AsFactor {

        @Test
        @DisplayName("should return decimal factor")
        void should_ReturnDecimalFactor() {
            var yield80 = new YieldPercentage(80);
            assertThat(yield80.asFactor()).isEqualByComparingTo(new BigDecimal("0.8"));
        }
    }
}
