package de.effigenix.domain.masterdata;

import de.effigenix.domain.masterdata.article.*;
import de.effigenix.domain.masterdata.productcategory.ProductCategoryId;
import de.effigenix.domain.masterdata.supplier.SupplierId;
import de.effigenix.shared.common.Money;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import java.util.UUID;

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

class ArticleTest {

    // ==================== Create ====================

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

        @Test
        @DisplayName("should create Article with valid data (PIECE_FIXED / FIXED)")
        void shouldCreateWithValidData() {
            var draft = validDraft();

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var article = result.unsafeGetValue();
            assertThat(article.id()).isNotNull();
            assertThat(article.name().value()).isEqualTo("Testwurst");
            assertThat(article.articleNumber().value()).isEqualTo("ART-001");
            assertThat(article.categoryId()).isNotNull();
            assertThat(article.status()).isEqualTo(ArticleStatus.ACTIVE);
            assertThat(article.salesUnits()).hasSize(1);
            assertThat(article.salesUnits().getFirst().unit()).isEqualTo(Unit.PIECE_FIXED);
            assertThat(article.salesUnits().getFirst().priceModel()).isEqualTo(PriceModel.FIXED);
            assertThat(article.salesUnits().getFirst().price().amount()).isEqualByComparingTo("9.99");
            assertThat(article.supplierReferences()).isEmpty();
            assertThat(article.createdAt()).isNotNull();
            assertThat(article.updatedAt()).isNotNull();
        }

        @Test
        @DisplayName("should create Article with KG / WEIGHT_BASED")
        void shouldCreateWithKgWeightBased() {
            var draft = new ArticleDraft("Hackfleisch", "ART-002", UUID.randomUUID().toString(),
                    Unit.KG, PriceModel.WEIGHT_BASED, new BigDecimal("12.50"));

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var su = result.unsafeGetValue().salesUnits().getFirst();
            assertThat(su.unit()).isEqualTo(Unit.KG);
            assertThat(su.priceModel()).isEqualTo(PriceModel.WEIGHT_BASED);
        }

        @Test
        @DisplayName("should create Article with HUNDRED_GRAM / WEIGHT_BASED")
        void shouldCreateWithHundredGramWeightBased() {
            var draft = new ArticleDraft("Aufschnitt", "ART-003", UUID.randomUUID().toString(),
                    Unit.HUNDRED_GRAM, PriceModel.WEIGHT_BASED, new BigDecimal("1.99"));

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
        }

        @Test
        @DisplayName("should create Article with PIECE_VARIABLE / WEIGHT_BASED")
        void shouldCreateWithPieceVariableWeightBased() {
            var draft = new ArticleDraft("Braten", "ART-004", UUID.randomUUID().toString(),
                    Unit.PIECE_VARIABLE, PriceModel.WEIGHT_BASED, new BigDecimal("15.00"));

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
        }

        @Test
        @DisplayName("should fail when name is blank")
        void shouldFailWhenNameBlank() {
            var draft = new ArticleDraft("", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should fail when name is null")
        void shouldFailWhenNameNull() {
            var draft = new ArticleDraft(null, "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should fail when name exceeds 200 characters")
        void shouldFailWhenNameTooLong() {
            var longName = "A".repeat(201);
            var draft = new ArticleDraft(longName, "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should accept name with exactly 200 characters")
        void shouldAcceptNameWith200Chars() {
            var name = "A".repeat(200);
            var draft = new ArticleDraft(name, "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
        }

        @Test
        @DisplayName("should fail when articleNumber is blank")
        void shouldFailWhenArticleNumberBlank() {
            var draft = new ArticleDraft("Testwurst", "", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should fail when articleNumber is null")
        void shouldFailWhenArticleNumberNull() {
            var draft = new ArticleDraft("Testwurst", null, UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should fail when articleNumber exceeds 50 characters")
        void shouldFailWhenArticleNumberTooLong() {
            var longNumber = "A".repeat(51);
            var draft = new ArticleDraft("Testwurst", longNumber, UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

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

        @Test
        @DisplayName("should accept articleNumber with exactly 50 characters")
        void shouldAcceptArticleNumberWith50Chars() {
            var number = "A".repeat(50);
            var draft = new ArticleDraft("Testwurst", number, UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isSuccess()).isTrue();
        }

        @Test
        @DisplayName("should throw when price is null (Money.tryEuro does not guard null)")
        void shouldThrowWhenPriceNull() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, null);

            org.junit.jupiter.api.Assertions.assertThrows(NullPointerException.class,
                    () -> Article.create(draft));
        }

        @Test
        @DisplayName("should fail when price is zero")
        void shouldFailWhenPriceZero() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, BigDecimal.ZERO);

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPrice.class);
        }

        @Test
        @DisplayName("should fail when price is negative")
        void shouldFailWhenPriceNegative() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.FIXED, new BigDecimal("-1.00"));

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
        }

        @Test
        @DisplayName("should fail when Unit/PriceModel combination is invalid (PIECE_FIXED + WEIGHT_BASED)")
        void shouldFailWhenInvalidCombinationPieceFixedWeightBased() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, PriceModel.WEIGHT_BASED, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPriceModelCombination.class);
        }

        @Test
        @DisplayName("should fail when Unit/PriceModel combination is invalid (KG + FIXED)")
        void shouldFailWhenInvalidCombinationKgFixed() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.KG, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPriceModelCombination.class);
        }

        @Test
        @DisplayName("should fail when unit is null")
        void shouldFailWhenUnitNull() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    null, PriceModel.FIXED, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPriceModelCombination.class);
        }

        @Test
        @DisplayName("should fail when priceModel is null")
        void shouldFailWhenPriceModelNull() {
            var draft = new ArticleDraft("Testwurst", "ART-001", UUID.randomUUID().toString(),
                    Unit.PIECE_FIXED, null, new BigDecimal("9.99"));

            var result = Article.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPriceModelCombination.class);
        }
    }

    // ==================== Update ====================

    @Nested
    @DisplayName("update()")
    class Update {

        @Test
        @DisplayName("should update name")
        void shouldUpdateName() {
            var article = createValidArticle();
            var updateDraft = new ArticleUpdateDraft("Neuer Name", null);

            var result = article.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.name().value()).isEqualTo("Neuer Name");
        }

        @Test
        @DisplayName("should update categoryId")
        void shouldUpdateCategoryId() {
            var article = createValidArticle();
            var newCategoryId = UUID.randomUUID().toString();
            var updateDraft = new ArticleUpdateDraft(null, newCategoryId);

            var result = article.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.categoryId().value()).isEqualTo(newCategoryId);
        }

        @Test
        @DisplayName("should update both name and categoryId")
        void shouldUpdateBothFields() {
            var article = createValidArticle();
            var newCategoryId = UUID.randomUUID().toString();
            var updateDraft = new ArticleUpdateDraft("Neuer Name", newCategoryId);

            var result = article.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.name().value()).isEqualTo("Neuer Name");
            assertThat(article.categoryId().value()).isEqualTo(newCategoryId);
        }

        @Test
        @DisplayName("should not change fields when all null in draft")
        void shouldNotChangeWhenAllNull() {
            var article = createValidArticle();
            var originalName = article.name().value();
            var originalCategoryId = article.categoryId().value();
            var updateDraft = new ArticleUpdateDraft(null, null);

            var result = article.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.name().value()).isEqualTo(originalName);
            assertThat(article.categoryId().value()).isEqualTo(originalCategoryId);
        }

        @Test
        @DisplayName("should fail when updated name is blank")
        void shouldFailWhenUpdatedNameBlank() {
            var article = createValidArticle();
            var updateDraft = new ArticleUpdateDraft("", null);

            var result = article.update(updateDraft);

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

        @Test
        @DisplayName("should fail when updated name exceeds 200 characters")
        void shouldFailWhenUpdatedNameTooLong() {
            var article = createValidArticle();
            var updateDraft = new ArticleUpdateDraft("A".repeat(201), null);

            var result = article.update(updateDraft);

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

        @Test
        @DisplayName("should update updatedAt timestamp")
        void shouldUpdateTimestamp() {
            var article = createValidArticle();
            var originalUpdatedAt = article.updatedAt();

            article.update(new ArticleUpdateDraft("Anderer Name", null));

            assertThat(article.updatedAt()).isAfterOrEqualTo(originalUpdatedAt);
        }
    }

    // ==================== Activate / Deactivate ====================

    @Nested
    @DisplayName("activate() / deactivate()")
    class ActivateDeactivate {

        @Test
        @DisplayName("should deactivate active article")
        void shouldDeactivate() {
            var article = createValidArticle();
            assertThat(article.status()).isEqualTo(ArticleStatus.ACTIVE);

            article.deactivate();

            assertThat(article.status()).isEqualTo(ArticleStatus.INACTIVE);
        }

        @Test
        @DisplayName("should activate inactive article")
        void shouldActivate() {
            var article = createValidArticle();
            article.deactivate();

            article.activate();

            assertThat(article.status()).isEqualTo(ArticleStatus.ACTIVE);
        }

        @Test
        @DisplayName("should update updatedAt on deactivate")
        void shouldUpdateTimestampOnDeactivate() {
            var article = createValidArticle();
            var originalUpdatedAt = article.updatedAt();

            article.deactivate();

            assertThat(article.updatedAt()).isAfterOrEqualTo(originalUpdatedAt);
        }

        @Test
        @DisplayName("should update updatedAt on activate")
        void shouldUpdateTimestampOnActivate() {
            var article = createValidArticle();
            article.deactivate();
            var afterDeactivate = article.updatedAt();

            article.activate();

            assertThat(article.updatedAt()).isAfterOrEqualTo(afterDeactivate);
        }
    }

    // ==================== Add Sales Unit ====================

    @Nested
    @DisplayName("addSalesUnit()")
    class AddSalesUnit {

        @Test
        @DisplayName("should add a second SalesUnit with different unit type")
        void shouldAddSecondSalesUnit() {
            var article = createValidArticle();
            var salesUnit = SalesUnit.create(Unit.KG, PriceModel.WEIGHT_BASED, Money.euro(new BigDecimal("12.50")))
                    .unsafeGetValue();

            var result = article.addSalesUnit(salesUnit);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.salesUnits()).hasSize(2);
        }

        @Test
        @DisplayName("should fail when adding SalesUnit with duplicate unit type")
        void shouldFailWhenDuplicateUnitType() {
            var article = createValidArticle();
            var duplicate = SalesUnit.create(Unit.PIECE_FIXED, PriceModel.FIXED, Money.euro(new BigDecimal("5.00")))
                    .unsafeGetValue();

            var result = article.addSalesUnit(duplicate);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.DuplicateSalesUnitType.class);
            assertThat(article.salesUnits()).hasSize(1);
        }

        @Test
        @DisplayName("should update updatedAt on add")
        void shouldUpdateTimestampOnAdd() {
            var article = createValidArticle();
            var originalUpdatedAt = article.updatedAt();
            var salesUnit = SalesUnit.create(Unit.KG, PriceModel.WEIGHT_BASED, Money.euro(new BigDecimal("12.50")))
                    .unsafeGetValue();

            article.addSalesUnit(salesUnit);

            assertThat(article.updatedAt()).isAfterOrEqualTo(originalUpdatedAt);
        }
    }

    // ==================== Remove Sales Unit ====================

    @Nested
    @DisplayName("removeSalesUnit()")
    class RemoveSalesUnit {

        @Test
        @DisplayName("should remove SalesUnit when more than one exists")
        void shouldRemoveWhenMultiple() {
            var article = createValidArticle();
            var secondUnit = SalesUnit.create(Unit.KG, PriceModel.WEIGHT_BASED, Money.euro(new BigDecimal("12.50")))
                    .unsafeGetValue();
            article.addSalesUnit(secondUnit);

            var result = article.removeSalesUnit(secondUnit.id());

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.salesUnits()).hasSize(1);
        }

        @Test
        @DisplayName("should fail when removing the last SalesUnit")
        void shouldFailWhenRemovingLastSalesUnit() {
            var article = createValidArticle();
            var onlySalesUnitId = article.salesUnits().getFirst().id();

            var result = article.removeSalesUnit(onlySalesUnitId);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.MinimumSalesUnitRequired.class);
            assertThat(article.salesUnits()).hasSize(1);
        }

        @Test
        @DisplayName("should fail when SalesUnit ID does not exist")
        void shouldFailWhenSalesUnitNotFound() {
            var article = createValidArticle();
            var secondUnit = SalesUnit.create(Unit.KG, PriceModel.WEIGHT_BASED, Money.euro(new BigDecimal("12.50")))
                    .unsafeGetValue();
            article.addSalesUnit(secondUnit);

            var nonExistentId = SalesUnitId.generate();
            var result = article.removeSalesUnit(nonExistentId);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.SalesUnitNotFound.class);
        }

        @Test
        @DisplayName("should update updatedAt on remove")
        void shouldUpdateTimestampOnRemove() {
            var article = createValidArticle();
            var secondUnit = SalesUnit.create(Unit.KG, PriceModel.WEIGHT_BASED, Money.euro(new BigDecimal("12.50")))
                    .unsafeGetValue();
            article.addSalesUnit(secondUnit);
            var afterAdd = article.updatedAt();

            article.removeSalesUnit(secondUnit.id());

            assertThat(article.updatedAt()).isAfterOrEqualTo(afterAdd);
        }
    }

    // ==================== Update Sales Unit Price ====================

    @Nested
    @DisplayName("updateSalesUnitPrice()")
    class UpdateSalesUnitPrice {

        @Test
        @DisplayName("should update price of existing SalesUnit")
        void shouldUpdatePrice() {
            var article = createValidArticle();
            var salesUnitId = article.salesUnits().getFirst().id();
            var newPrice = Money.euro(new BigDecimal("19.99"));

            var result = article.updateSalesUnitPrice(salesUnitId, newPrice);

            assertThat(result.isSuccess()).isTrue();
            assertThat(article.salesUnits().getFirst().price().amount()).isEqualByComparingTo("19.99");
        }

        @Test
        @DisplayName("should fail when SalesUnit not found")
        void shouldFailWhenNotFound() {
            var article = createValidArticle();
            var nonExistentId = SalesUnitId.generate();

            var result = article.updateSalesUnitPrice(nonExistentId, Money.euro(new BigDecimal("5.00")));

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.SalesUnitNotFound.class);
        }

        @Test
        @DisplayName("should fail when new price is zero")
        void shouldFailWhenPriceZero() {
            var article = createValidArticle();
            var salesUnitId = article.salesUnits().getFirst().id();

            var result = article.updateSalesUnitPrice(salesUnitId, Money.zero());

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(ArticleError.InvalidPrice.class);
        }
    }

    // ==================== Supplier References ====================

    @Nested
    @DisplayName("assignSupplier / removeSupplier")
    class SupplierReferences {

        @Test
        @DisplayName("should add supplier reference")
        void shouldAddSupplierReference() {
            var article = createValidArticle();
            var supplierId = SupplierId.generate();

            article.addSupplierReference(supplierId);

            assertThat(article.supplierReferences()).containsExactly(supplierId);
        }

        @Test
        @DisplayName("should add multiple supplier references")
        void shouldAddMultipleSuppliers() {
            var article = createValidArticle();
            var supplier1 = SupplierId.generate();
            var supplier2 = SupplierId.generate();

            article.addSupplierReference(supplier1);
            article.addSupplierReference(supplier2);

            assertThat(article.supplierReferences()).containsExactlyInAnyOrder(supplier1, supplier2);
        }

        @Test
        @DisplayName("should not duplicate supplier reference")
        void shouldNotDuplicateSupplier() {
            var article = createValidArticle();
            var supplierId = SupplierId.generate();

            article.addSupplierReference(supplierId);
            article.addSupplierReference(supplierId);

            assertThat(article.supplierReferences()).hasSize(1);
        }

        @Test
        @DisplayName("should remove supplier reference")
        void shouldRemoveSupplierReference() {
            var article = createValidArticle();
            var supplierId = SupplierId.generate();
            article.addSupplierReference(supplierId);

            article.removeSupplierReference(supplierId);

            assertThat(article.supplierReferences()).isEmpty();
        }

        @Test
        @DisplayName("should silently ignore removing non-existent supplier")
        void shouldIgnoreRemovingNonExistentSupplier() {
            var article = createValidArticle();
            var supplierId = SupplierId.generate();

            article.removeSupplierReference(supplierId);

            assertThat(article.supplierReferences()).isEmpty();
        }

        @Test
        @DisplayName("should update updatedAt on add supplier")
        void shouldUpdateTimestampOnAddSupplier() {
            var article = createValidArticle();
            var originalUpdatedAt = article.updatedAt();

            article.addSupplierReference(SupplierId.generate());

            assertThat(article.updatedAt()).isAfterOrEqualTo(originalUpdatedAt);
        }

        @Test
        @DisplayName("should update updatedAt on remove supplier")
        void shouldUpdateTimestampOnRemoveSupplier() {
            var article = createValidArticle();
            var supplierId = SupplierId.generate();
            article.addSupplierReference(supplierId);
            var afterAdd = article.updatedAt();

            article.removeSupplierReference(supplierId);

            assertThat(article.updatedAt()).isAfterOrEqualTo(afterAdd);
        }
    }

    // ==================== Equality ====================

    @Nested
    @DisplayName("equals / hashCode")
    class Equality {

        @Test
        @DisplayName("should be equal if same ID")
        void shouldBeEqualBySameId() {
            var id = ArticleId.generate();
            var name = new ArticleName("Testwurst");
            var number = new ArticleNumber("ART-001");
            var categoryId = ProductCategoryId.generate();
            var salesUnit = SalesUnit.reconstitute(
                    SalesUnitId.generate(), Unit.PIECE_FIXED, PriceModel.FIXED, Money.euro(new BigDecimal("9.99")));
            var now = java.time.OffsetDateTime.now();

            var article1 = Article.reconstitute(id, name, number, categoryId, List.of(salesUnit),
                    ArticleStatus.ACTIVE, Set.of(), now, now);
            var article2 = Article.reconstitute(id, new ArticleName("Anderer Name"), new ArticleNumber("ART-999"),
                    ProductCategoryId.generate(), List.of(salesUnit), ArticleStatus.INACTIVE, Set.of(), now, now);

            assertThat(article1).isEqualTo(article2);
            assertThat(article1.hashCode()).isEqualTo(article2.hashCode());
        }

        @Test
        @DisplayName("should not be equal if different ID")
        void shouldNotBeEqualByDifferentId() {
            var article1 = createValidArticle();
            var article2 = createValidArticle();

            assertThat(article1).isNotEqualTo(article2);
        }

        @Test
        @DisplayName("should not be equal to null")
        void shouldNotBeEqualToNull() {
            var article = createValidArticle();

            assertThat(article).isNotEqualTo(null);
        }

        @Test
        @DisplayName("should not be equal to different type")
        void shouldNotBeEqualToDifferentType() {
            var article = createValidArticle();

            assertThat(article).isNotEqualTo("not an article");
        }

        @Test
        @DisplayName("should be equal to itself")
        void shouldBeEqualToItself() {
            var article = createValidArticle();

            assertThat(article).isEqualTo(article);
        }
    }

    // ==================== Helpers ====================

    private static ArticleDraft validDraft() {
        return new ArticleDraft(
                "Testwurst",
                "ART-001",
                UUID.randomUUID().toString(),
                Unit.PIECE_FIXED,
                PriceModel.FIXED,
                new BigDecimal("9.99")
        );
    }

    private static Article createValidArticle() {
        return Article.create(validDraft()).unsafeGetValue();
    }
}
