package de.effigenix.domain.production;

import de.effigenix.shared.common.QuantityError;
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.time.LocalDate;

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

@DisplayName("BatchNumber Value Object")
class BatchNumberTest {

    @Nested
    @DisplayName("of() factory")
    class OfFactory {

        @Test
        @DisplayName("should create BatchNumber with valid format")
        void should_Create_When_ValidFormat() {
            var result = BatchNumber.of("P-2026-02-19-001");

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue().value()).isEqualTo("P-2026-02-19-001");
        }

        @ParameterizedTest
        @ValueSource(strings = {
                "P-2026-01-15-999",
                "P-2025-12-31-042",
                "P-2024-06-01-001"
        })
        @DisplayName("should accept various valid batch numbers")
        void should_Accept_When_ValidFormats(String batchNumber) {
            var result = BatchNumber.of(batchNumber);
            assertThat(result.isSuccess()).isTrue();
        }

        @ParameterizedTest
        @ValueSource(strings = {
                "2026-02-19-001",
                "P-2026-02-19-01",
                "P-2026-02-19-1000",
                "X-2026-02-19-001",
                "P-26-02-19-001",
                "P2026-02-19-001",
                "",
                "random-string"
        })
        @DisplayName("should fail for invalid formats")
        void should_Fail_When_InvalidFormat(String batchNumber) {
            var result = BatchNumber.of(batchNumber);
            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(QuantityError.InvalidBatchNumber.class);
        }

        @Test
        @DisplayName("should fail for null value")
        void should_Fail_When_Null() {
            var result = BatchNumber.of(null);
            assertThat(result.isFailure()).isTrue();
        }
    }

    @Nested
    @DisplayName("generate()")
    class Generate {

        @Test
        @DisplayName("should generate batch number for date and sequence")
        void should_Generate_When_ValidDateAndSequence() {
            var bn = BatchNumber.generate(LocalDate.of(2026, 2, 19), 1);

            assertThat(bn.value()).isEqualTo("P-2026-02-19-001");
        }

        @Test
        @DisplayName("should generate with 3-digit sequence")
        void should_PadSequence_When_SmallNumber() {
            var bn = BatchNumber.generate(LocalDate.of(2026, 2, 19), 42);

            assertThat(bn.value()).isEqualTo("P-2026-02-19-042");
        }

        @Test
        @DisplayName("should throw when sequence out of range")
        void should_Throw_When_SequenceOutOfRange() {
            assertThatThrownBy(() -> BatchNumber.generate(LocalDate.of(2026, 1, 1), 0))
                    .isInstanceOf(IllegalArgumentException.class);

            assertThatThrownBy(() -> BatchNumber.generate(LocalDate.of(2026, 1, 1), 1000))
                    .isInstanceOf(IllegalArgumentException.class);
        }
    }

    @Nested
    @DisplayName("Extraction methods")
    class Extraction {

        @Test
        @DisplayName("should extract date from batch number")
        void should_ExtractDate() {
            var bn = new BatchNumber("P-2026-02-19-001");
            assertThat(bn.date()).isEqualTo(LocalDate.of(2026, 2, 19));
        }

        @Test
        @DisplayName("should extract sequence number from batch number")
        void should_ExtractSequence() {
            var bn = new BatchNumber("P-2026-02-19-042");
            assertThat(bn.sequenceNumber()).isEqualTo(42);
        }
    }

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

        @Test
        @DisplayName("should be equal when same value")
        void should_BeEqual_When_SameValue() {
            var bn1 = new BatchNumber("P-2026-02-19-001");
            var bn2 = new BatchNumber("P-2026-02-19-001");

            assertThat(bn1).isEqualTo(bn2);
            assertThat(bn1.hashCode()).isEqualTo(bn2.hashCode());
        }

        @Test
        @DisplayName("should not be equal when different value")
        void should_NotBeEqual_When_DifferentValue() {
            var bn1 = new BatchNumber("P-2026-02-19-001");
            var bn2 = new BatchNumber("P-2026-02-19-002");

            assertThat(bn1).isNotEqualTo(bn2);
        }
    }
}
