package de.effigenix.domain.inventory;

import de.effigenix.shared.common.Result;
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;

class StorageLocationTest {

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

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

        @Test
        @DisplayName("should create StorageLocation with valid data")
        void shouldCreateWithValidData() {
            var draft = new StorageLocationDraft("Kühlraum 1", "COLD_ROOM", "-2", "8");

            var result = StorageLocation.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var location = result.unsafeGetValue();
            assertThat(location.id()).isNotNull();
            assertThat(location.name().value()).isEqualTo("Kühlraum 1");
            assertThat(location.storageType()).isEqualTo(StorageType.COLD_ROOM);
            assertThat(location.temperatureRange()).isNotNull();
            assertThat(location.temperatureRange().minTemperature().intValue()).isEqualTo(-2);
            assertThat(location.temperatureRange().maxTemperature().intValue()).isEqualTo(8);
            assertThat(location.active()).isTrue();
        }

        @Test
        @DisplayName("should create StorageLocation without temperature range")
        void shouldCreateWithoutTemperatureRange() {
            var draft = new StorageLocationDraft("Trockenlager", "DRY_STORAGE", null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isSuccess()).isTrue();
            var location = result.unsafeGetValue();
            assertThat(location.temperatureRange()).isNull();
            assertThat(location.isTemperatureControlled()).isFalse();
        }

        @Test
        @DisplayName("should fail when name is blank")
        void shouldFailWhenNameBlank() {
            var draft = new StorageLocationDraft("", "COLD_ROOM", null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
        }

        @Test
        @DisplayName("should fail when name is null")
        void shouldFailWhenNameNull() {
            var draft = new StorageLocationDraft(null, "COLD_ROOM", null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
        }

        @Test
        @DisplayName("should fail when name exceeds 100 chars")
        void shouldFailWhenNameTooLong() {
            var longName = "A".repeat(101);
            var draft = new StorageLocationDraft(longName, "COLD_ROOM", null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
        }

        @Test
        @DisplayName("should accept name with exactly 100 chars")
        void shouldAcceptNameWith100Chars() {
            var name = "A".repeat(100);
            var draft = new StorageLocationDraft(name, "DRY_STORAGE", null, null);

            var result = StorageLocation.create(draft);

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

        @Test
        @DisplayName("should fail when storageType is invalid")
        void shouldFailWhenStorageTypeInvalid() {
            var draft = new StorageLocationDraft("Test", "INVALID_TYPE", null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidStorageType.class);
        }

        @Test
        @DisplayName("should fail when storageType is null")
        void shouldFailWhenStorageTypeNull() {
            var draft = new StorageLocationDraft("Test", null, null, null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidStorageType.class);
        }

        @Test
        @DisplayName("should fail when min >= max temperature")
        void shouldFailWhenMinGreaterThanMax() {
            var draft = new StorageLocationDraft("Test", "COLD_ROOM", "10", "5");

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should fail when min == max temperature")
        void shouldFailWhenMinEqualsMax() {
            var draft = new StorageLocationDraft("Test", "COLD_ROOM", "5", "5");

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should fail when temperature below -50")
        void shouldFailWhenTemperatureBelowMinus50() {
            var draft = new StorageLocationDraft("Test", "FREEZER", "-51", "-20");

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should fail when temperature above 80")
        void shouldFailWhenTemperatureAbove80() {
            var draft = new StorageLocationDraft("Test", "DRY_STORAGE", "20", "81");

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should accept boundary temperatures -50 and +80")
        void shouldAcceptBoundaryTemperatures() {
            var draft = new StorageLocationDraft("Test", "DRY_STORAGE", "-50", "80");

            var result = StorageLocation.create(draft);

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

        @Test
        @DisplayName("should fail when temperature is not a number")
        void shouldFailWhenTemperatureNotNumber() {
            var draft = new StorageLocationDraft("Test", "COLD_ROOM", "abc", "10");

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should fail when only min temperature provided")
        void shouldFailWhenOnlyMinTemperature() {
            var draft = new StorageLocationDraft("Test", "COLD_ROOM", "5", null);

            var result = StorageLocation.create(draft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidTemperatureRange.class);
        }

        @Test
        @DisplayName("should create all storage types")
        void shouldCreateAllStorageTypes() {
            for (StorageType type : StorageType.values()) {
                var draft = new StorageLocationDraft("Test " + type, type.name(), null, null);
                var result = StorageLocation.create(draft);
                assertThat(result.isSuccess()).as("StorageType %s should be valid", type).isTrue();
            }
        }
    }

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

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

        @Test
        @DisplayName("should update name")
        void shouldUpdateName() {
            var location = createValidLocation();
            var updateDraft = new StorageLocationUpdateDraft("Neuer Name", null, null);

            var result = location.update(updateDraft);

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

        @Test
        @DisplayName("should update temperature range")
        void shouldUpdateTemperatureRange() {
            var location = createValidLocation();
            var updateDraft = new StorageLocationUpdateDraft(null, "-5", "12");

            var result = location.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(location.temperatureRange().minTemperature().intValue()).isEqualTo(-5);
            assertThat(location.temperatureRange().maxTemperature().intValue()).isEqualTo(12);
        }

        @Test
        @DisplayName("should fail update when name is blank")
        void shouldFailUpdateWhenNameBlank() {
            var location = createValidLocation();
            var updateDraft = new StorageLocationUpdateDraft("", null, null);

            var result = location.update(updateDraft);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.InvalidName.class);
        }

        @Test
        @DisplayName("should not change name when null in draft")
        void shouldNotChangeNameWhenNull() {
            var location = createValidLocation();
            var originalName = location.name().value();
            var updateDraft = new StorageLocationUpdateDraft(null, null, null);

            var result = location.update(updateDraft);

            assertThat(result.isSuccess()).isTrue();
            assertThat(location.name().value()).isEqualTo(originalName);
        }
    }

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

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

        @Test
        @DisplayName("should deactivate active location")
        void shouldDeactivate() {
            var location = createValidLocation();

            var result = location.deactivate();

            assertThat(result.isSuccess()).isTrue();
            assertThat(location.active()).isFalse();
        }

        @Test
        @DisplayName("should fail deactivating already inactive location")
        void shouldFailDeactivatingInactive() {
            var location = createValidLocation();
            location.deactivate();

            var result = location.deactivate();

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.AlreadyInactive.class);
        }

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

            var result = location.activate();

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

        @Test
        @DisplayName("should fail activating already active location")
        void shouldFailActivatingActive() {
            var location = createValidLocation();

            var result = location.activate();

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.AlreadyActive.class);
        }
    }

    // ==================== Query Methods ====================

    @Nested
    @DisplayName("query methods")
    class QueryMethods {

        @Test
        @DisplayName("isTemperatureControlled should return true when range is set")
        void shouldReturnTrueWhenRangeSet() {
            var draft = new StorageLocationDraft("Kühlraum", "COLD_ROOM", "-2", "8");
            var location = StorageLocation.create(draft).unsafeGetValue();

            assertThat(location.isTemperatureControlled()).isTrue();
        }

        @Test
        @DisplayName("isTemperatureControlled should return false when range is null")
        void shouldReturnFalseWhenRangeNull() {
            var draft = new StorageLocationDraft("Trockenlager", "DRY_STORAGE", null, null);
            var location = StorageLocation.create(draft).unsafeGetValue();

            assertThat(location.isTemperatureControlled()).isFalse();
        }
    }

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

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

        @Test
        @DisplayName("should be equal if same ID")
        void shouldBeEqualBySameId() {
            var id = StorageLocationId.generate();
            var loc1 = StorageLocation.reconstitute(id, new StorageLocationName("A"), StorageType.COLD_ROOM, null, true);
            var loc2 = StorageLocation.reconstitute(id, new StorageLocationName("B"), StorageType.DRY_STORAGE, null, false);

            assertThat(loc1).isEqualTo(loc2);
            assertThat(loc1.hashCode()).isEqualTo(loc2.hashCode());
        }

        @Test
        @DisplayName("should not be equal if different ID")
        void shouldNotBeEqualByDifferentId() {
            var loc1 = createValidLocation();
            var loc2 = createValidLocation();

            assertThat(loc1).isNotEqualTo(loc2);
        }
    }

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

    private StorageLocation createValidLocation() {
        var draft = new StorageLocationDraft("Kühlraum 1", "COLD_ROOM", "-2", "8");
        return StorageLocation.create(draft).unsafeGetValue();
    }
}
