package de.effigenix.application.inventory;

import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("ListStorageLocations Use Case")
class ListStorageLocationsTest {

    @Mock private StorageLocationRepository storageLocationRepository;

    private ListStorageLocations listStorageLocations;

    @BeforeEach
    void setUp() {
        listStorageLocations = new ListStorageLocations(storageLocationRepository);
    }

    private StorageLocation activeLocation(String name, StorageType type) {
        return StorageLocation.reconstitute(
                StorageLocationId.generate(),
                new StorageLocationName(name),
                type, null, true
        );
    }

    private StorageLocation inactiveLocation(String name, StorageType type) {
        return StorageLocation.reconstitute(
                StorageLocationId.generate(),
                new StorageLocationName(name),
                type, null, false
        );
    }

    // ==================== Ohne Filter ====================

    @Nested
    @DisplayName("ohne Filter")
    class NoFilter {

        @Test
        @DisplayName("should return all storage locations")
        void shouldReturnAll() {
            var locations = List.of(
                    activeLocation("Lager A", StorageType.DRY_STORAGE),
                    inactiveLocation("Lager B", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findAll()).thenReturn(Result.success(locations));

            var result = listStorageLocations.execute(null, null);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(2);
        }

        @Test
        @DisplayName("should return empty list when none exist")
        void shouldReturnEmptyList() {
            when(storageLocationRepository.findAll()).thenReturn(Result.success(List.of()));

            var result = listStorageLocations.execute(null, null);

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

        @Test
        @DisplayName("should fail when repository fails")
        void shouldFailWhenRepositoryFails() {
            when(storageLocationRepository.findAll())
                    .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

            var result = listStorageLocations.execute(null, null);

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

    // ==================== Active-Filter ====================

    @Nested
    @DisplayName("active-Filter")
    class ActiveFilter {

        @Test
        @DisplayName("should return only active locations when active=true")
        void shouldReturnOnlyActive() {
            var locations = List.of(
                    activeLocation("Aktiv", StorageType.DRY_STORAGE),
                    inactiveLocation("Inaktiv", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findAll()).thenReturn(Result.success(locations));

            var result = listStorageLocations.execute(null, true);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(1);
            assertThat(result.unsafeGetValue().get(0).name().value()).isEqualTo("Aktiv");
        }

        @Test
        @DisplayName("should return only inactive locations when active=false")
        void shouldReturnOnlyInactive() {
            var locations = List.of(
                    activeLocation("Aktiv", StorageType.DRY_STORAGE),
                    inactiveLocation("Inaktiv", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findAll()).thenReturn(Result.success(locations));

            var result = listStorageLocations.execute(null, false);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(1);
            assertThat(result.unsafeGetValue().get(0).name().value()).isEqualTo("Inaktiv");
        }

        @Test
        @DisplayName("should return empty list when no locations match active filter")
        void shouldReturnEmptyWhenNoMatch() {
            var locations = List.of(
                    activeLocation("Aktiv", StorageType.DRY_STORAGE)
            );
            when(storageLocationRepository.findAll()).thenReturn(Result.success(locations));

            var result = listStorageLocations.execute(null, false);

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

    // ==================== StorageType-Filter ====================

    @Nested
    @DisplayName("storageType-Filter")
    class StorageTypeFilter {

        @Test
        @DisplayName("should return locations of given storage type")
        void shouldReturnByStorageType() {
            var coldRooms = List.of(
                    activeLocation("Kühlraum", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findByStorageType(StorageType.COLD_ROOM))
                    .thenReturn(Result.success(coldRooms));

            var result = listStorageLocations.execute("COLD_ROOM", null);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(1);
            assertThat(result.unsafeGetValue().get(0).storageType()).isEqualTo(StorageType.COLD_ROOM);
        }

        @Test
        @DisplayName("should fail with InvalidStorageType for unknown type")
        void shouldFailForInvalidStorageType() {
            var result = listStorageLocations.execute("INVALID", null);

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

    // ==================== Kombinierte Filter ====================

    @Nested
    @DisplayName("kombinierte Filter (storageType + active)")
    class CombinedFilter {

        @Test
        @DisplayName("should return only active locations of given type")
        void shouldReturnActiveOfType() {
            var coldRooms = List.of(
                    activeLocation("Kühl Aktiv", StorageType.COLD_ROOM),
                    inactiveLocation("Kühl Inaktiv", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findByStorageType(StorageType.COLD_ROOM))
                    .thenReturn(Result.success(coldRooms));

            var result = listStorageLocations.execute("COLD_ROOM", true);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(1);
            assertThat(result.unsafeGetValue().get(0).name().value()).isEqualTo("Kühl Aktiv");
        }

        @Test
        @DisplayName("should return only inactive locations of given type")
        void shouldReturnInactiveOfType() {
            var coldRooms = List.of(
                    activeLocation("Kühl Aktiv", StorageType.COLD_ROOM),
                    inactiveLocation("Kühl Inaktiv", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findByStorageType(StorageType.COLD_ROOM))
                    .thenReturn(Result.success(coldRooms));

            var result = listStorageLocations.execute("COLD_ROOM", false);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(1);
            assertThat(result.unsafeGetValue().get(0).name().value()).isEqualTo("Kühl Inaktiv");
        }

        @Test
        @DisplayName("should return empty list when no locations match combined filter")
        void shouldReturnEmptyWhenNoMatchCombined() {
            var coldRooms = List.of(
                    activeLocation("Kühl Aktiv", StorageType.COLD_ROOM)
            );
            when(storageLocationRepository.findByStorageType(StorageType.COLD_ROOM))
                    .thenReturn(Result.success(coldRooms));

            var result = listStorageLocations.execute("COLD_ROOM", false);

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