package de.effigenix.domain.inventory;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

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

class StockBatchTest {

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

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

        @Test
        @DisplayName("should create batch with valid data")
        void shouldCreateWithValidData() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "10.5", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var batch = result.unsafeGetValue();
            assertThat(batch.id()).isNotNull();
            assertThat(batch.batchReference().batchId()).isEqualTo("BATCH-001");
            assertThat(batch.batchReference().batchType()).isEqualTo(BatchType.PRODUCED);
            assertThat(batch.quantity().amount()).isEqualByComparingTo(new BigDecimal("10.5"));
            assertThat(batch.expiryDate().toString()).isEqualTo("2026-12-31");
            assertThat(batch.status()).isEqualTo(StockBatchStatus.AVAILABLE);
            assertThat(batch.receivedAt()).isNotNull();
        }

        @Test
        @DisplayName("should create batch with PURCHASED type")
        void shouldCreateWithPurchasedType() {
            var draft = new StockBatchDraft("PO-123", "PURCHASED", "5", "PIECE", "2027-01-15");

            var result = StockBatch.create(draft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue().batchReference().batchType()).isEqualTo(BatchType.PURCHASED);
        }

        @Test
        @DisplayName("should fail when batchId is blank")
        void shouldFailWhenBatchIdBlank() {
            var draft = new StockBatchDraft("", "PRODUCED", "10", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidBatchReference.class);
        }

        @Test
        @DisplayName("should fail when batchType is invalid")
        void shouldFailWhenBatchTypeInvalid() {
            var draft = new StockBatchDraft("BATCH-001", "INVALID", "10", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidBatchReference.class);
        }

        @Test
        @DisplayName("should fail when quantity is negative")
        void shouldFailWhenQuantityNegative() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "-1", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidQuantity.class);
        }

        @Test
        @DisplayName("should fail when quantity is zero")
        void shouldFailWhenQuantityZero() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "0", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidQuantity.class);
        }

        @Test
        @DisplayName("should fail when quantity is not a number")
        void shouldFailWhenQuantityNotNumber() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "abc", "KILOGRAM", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidQuantity.class);
        }

        @Test
        @DisplayName("should fail when unit is invalid")
        void shouldFailWhenUnitInvalid() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "10", "INVALID_UNIT", "2026-12-31");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidQuantity.class);
        }

        @Test
        @DisplayName("should fail when expiryDate is blank")
        void shouldFailWhenExpiryDateBlank() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "10", "KILOGRAM", "");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidExpiryDate.class);
        }

        @Test
        @DisplayName("should fail when expiryDate is null")
        void shouldFailWhenExpiryDateNull() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "10", "KILOGRAM", null);

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidExpiryDate.class);
        }

        @Test
        @DisplayName("should fail when expiryDate is not ISO format")
        void shouldFailWhenExpiryDateInvalidFormat() {
            var draft = new StockBatchDraft("BATCH-001", "PRODUCED", "10", "KILOGRAM", "31.12.2026");

            var result = StockBatch.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidExpiryDate.class);
        }
    }

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

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

        @Test
        @DisplayName("should be equal if same ID")
        void shouldBeEqualBySameId() {
            var id = StockBatchId.generate();
            var ref = new BatchReference("B1", BatchType.PRODUCED);
            var batch1 = StockBatch.reconstitute(id, ref, null, null, StockBatchStatus.AVAILABLE, null);
            var batch2 = StockBatch.reconstitute(id, ref, null, null, StockBatchStatus.BLOCKED, null);

            assertThat(batch1).isEqualTo(batch2);
            assertThat(batch1.hashCode()).isEqualTo(batch2.hashCode());
        }

        @Test
        @DisplayName("should not be equal if different ID")
        void shouldNotBeEqualByDifferentId() {
            var ref = new BatchReference("B1", BatchType.PRODUCED);
            var batch1 = StockBatch.reconstitute(StockBatchId.generate(), ref, null, null, StockBatchStatus.AVAILABLE, null);
            var batch2 = StockBatch.reconstitute(StockBatchId.generate(), ref, null, null, StockBatchStatus.AVAILABLE, null);

            assertThat(batch1).isNotEqualTo(batch2);
        }
    }
}
