package de.effigenix.application.inventory;

import de.effigenix.domain.inventory.*;
import de.effigenix.domain.masterdata.article.ArticleId;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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 java.util.Optional;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("DeactivateStorageLocation Use Case")
class DeactivateStorageLocationTest {

    @Mock private StorageLocationRepository storageLocationRepository;
    @Mock private StockRepository stockRepository;
    @Mock private UnitOfWork unitOfWork;

    private DeactivateStorageLocation deactivateStorageLocation;
    private ActorId performedBy;

    @BeforeEach
    void setUp() {
        deactivateStorageLocation = new DeactivateStorageLocation(storageLocationRepository, stockRepository, unitOfWork);
        lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
        performedBy = ActorId.of("admin-user");
    }

    private StorageLocation activeLocation(String id) {
        return StorageLocation.reconstitute(
                StorageLocationId.of(id),
                new StorageLocationName("Kühlraum 1"),
                StorageType.COLD_ROOM,
                null,
                true
        );
    }

    private StorageLocation inactiveLocation(String id) {
        return StorageLocation.reconstitute(
                StorageLocationId.of(id),
                new StorageLocationName("Kühlraum 1"),
                StorageType.COLD_ROOM,
                null,
                false
        );
    }

    @Test
    @DisplayName("should deactivate active location without stock")
    void shouldDeactivateActiveLocationWithoutStock() {
        var locationId = StorageLocationId.of("loc-1");
        when(storageLocationRepository.findById(locationId))
                .thenReturn(Result.success(Optional.of(activeLocation("loc-1"))));
        when(stockRepository.findAllByStorageLocationId(locationId))
                .thenReturn(Result.success(List.of()));
        when(storageLocationRepository.save(any()))
                .thenReturn(Result.success(null));

        var result = deactivateStorageLocation.execute("loc-1", performedBy);

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

    @Test
    @DisplayName("should fail with StockExistsAtLocation when stock exists")
    void shouldFailWhenStockExistsAtLocation() {
        var locationId = StorageLocationId.of("loc-1");
        var stock = Stock.reconstitute(
                StockId.of("stock-1"), ArticleId.of("article-1"),
                locationId, null, null, List.of(), List.of()
        );
        when(storageLocationRepository.findById(locationId))
                .thenReturn(Result.success(Optional.of(activeLocation("loc-1"))));
        when(stockRepository.findAllByStorageLocationId(locationId))
                .thenReturn(Result.success(List.of(stock)));

        var result = deactivateStorageLocation.execute("loc-1", performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StockExistsAtLocation.class);
        verify(storageLocationRepository, never()).save(any());
    }

    @Test
    @DisplayName("should fail with StorageLocationNotFound when location does not exist")
    void shouldFailWhenLocationNotFound() {
        when(storageLocationRepository.findById(StorageLocationId.of("nonexistent")))
                .thenReturn(Result.success(Optional.empty()));

        var result = deactivateStorageLocation.execute("nonexistent", performedBy);

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

    @Test
    @DisplayName("should fail with AlreadyInactive when location is already inactive")
    void shouldFailWhenAlreadyInactive() {
        var locationId = StorageLocationId.of("loc-1");
        when(storageLocationRepository.findById(locationId))
                .thenReturn(Result.success(Optional.of(inactiveLocation("loc-1"))));
        when(stockRepository.findAllByStorageLocationId(locationId))
                .thenReturn(Result.success(List.of()));

        var result = deactivateStorageLocation.execute("loc-1", performedBy);

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

    @Test
    @DisplayName("should fail with RepositoryFailure when stock repository fails")
    void shouldFailWhenStockRepositoryFails() {
        var locationId = StorageLocationId.of("loc-1");
        when(storageLocationRepository.findById(locationId))
                .thenReturn(Result.success(Optional.of(activeLocation("loc-1"))));
        when(stockRepository.findAllByStorageLocationId(locationId))
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = deactivateStorageLocation.execute("loc-1", performedBy);

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