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 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("ListStocks Use Case")
class ListStocksTest {

    @Mock private StockRepository stockRepository;

    private ListStocks listStocks;
    private Stock stock1;
    private Stock stock2;

    @BeforeEach
    void setUp() {
        listStocks = new ListStocks(stockRepository);

        stock1 = Stock.reconstitute(
                StockId.of("stock-1"),
                ArticleId.of("article-1"),
                StorageLocationId.of("location-1"),
                null, null, List.of(), List.of()
        );

        stock2 = Stock.reconstitute(
                StockId.of("stock-2"),
                ArticleId.of("article-2"),
                StorageLocationId.of("location-1"),
                null, null, List.of(), List.of()
        );
    }

    @Test
    @DisplayName("should return all stocks when no filter provided")
    void shouldReturnAllStocksWhenNoFilter() {
        when(stockRepository.findAll()).thenReturn(Result.success(List.of(stock1, stock2)));

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

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

    @Test
    @DisplayName("should filter by storageLocationId")
    void shouldFilterByStorageLocationId() {
        when(stockRepository.findAllByStorageLocationId(StorageLocationId.of("location-1")))
                .thenReturn(Result.success(List.of(stock1, stock2)));

        var result = listStocks.execute("location-1", null);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue()).hasSize(2);
        verify(stockRepository).findAllByStorageLocationId(StorageLocationId.of("location-1"));
        verify(stockRepository, never()).findAll();
    }

    @Test
    @DisplayName("should filter by articleId")
    void shouldFilterByArticleId() {
        when(stockRepository.findAllByArticleId(ArticleId.of("article-1")))
                .thenReturn(Result.success(List.of(stock1)));

        var result = listStocks.execute(null, "article-1");

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue()).hasSize(1);
        verify(stockRepository).findAllByArticleId(ArticleId.of("article-1"));
        verify(stockRepository, never()).findAll();
    }

    @Test
    @DisplayName("should fail when both filters are provided")
    void shouldFailWhenBothFiltersProvided() {
        var result = listStocks.execute("location-1", "article-1");

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StockError.InvalidFilterCombination.class);
        verifyNoInteractions(stockRepository);
    }

    @Test
    @DisplayName("should fail with RepositoryFailure when repository fails for findAll")
    void shouldFailWhenRepositoryFailsForFindAll() {
        when(stockRepository.findAll())
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

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

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

    @Test
    @DisplayName("should fail with RepositoryFailure when repository fails for storageLocationId filter")
    void shouldFailWhenRepositoryFailsForStorageLocationFilter() {
        when(stockRepository.findAllByStorageLocationId(StorageLocationId.of("location-1")))
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = listStocks.execute("location-1", null);

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

    @Test
    @DisplayName("should fail with RepositoryFailure when repository fails for articleId filter")
    void shouldFailWhenRepositoryFailsForArticleIdFilter() {
        when(stockRepository.findAllByArticleId(ArticleId.of("article-1")))
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = listStocks.execute(null, "article-1");

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

    @Test
    @DisplayName("should return empty list when no stocks match")
    void shouldReturnEmptyListWhenNoStocksMatch() {
        when(stockRepository.findAllByStorageLocationId(StorageLocationId.of("unknown")))
                .thenReturn(Result.success(List.of()));

        var result = listStocks.execute("unknown", null);

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