package de.effigenix.application.inventory;

import de.effigenix.domain.inventory.*;
import de.effigenix.domain.masterdata.article.ArticleId;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.shared.security.Action;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import de.effigenix.shared.security.ResourceId;
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 java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

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

class ListStocksBelowMinimumTest {

    private static final ActorId ACTOR = ActorId.of("test-user");

    private InMemoryStockRepository stockRepository;
    private AllowAllAuthorizationPort authPort;
    private ListStocksBelowMinimum useCase;

    @BeforeEach
    void setUp() {
        stockRepository = new InMemoryStockRepository();
        authPort = new AllowAllAuthorizationPort();
        useCase = new ListStocksBelowMinimum(stockRepository, authPort);
    }

    @Nested
    @DisplayName("execute()")
    class Execute {

        @Test
        @DisplayName("should return stocks from repository directly")
        void shouldReturnStocksFromRepository() {
            var stock1 = createStockWithMinimumLevel("5", "10");
            var stock2 = createStockWithMinimumLevel("3", "10");
            stockRepository.stocksBelowMinimumLevel = List.of(stock1, stock2);

            var result = useCase.execute(ACTOR);

            assertThat(result.isSuccess()).isTrue();
            assertThat(result.unsafeGetValue()).hasSize(2);
            assertThat(result.unsafeGetValue()).containsExactly(stock1, stock2);
        }

        @Test
        @DisplayName("should return empty list when repository returns empty")
        void shouldReturnEmptyWhenRepositoryReturnsEmpty() {
            stockRepository.stocksBelowMinimumLevel = List.of();

            var result = useCase.execute(ACTOR);

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

        @Test
        @DisplayName("should propagate repository failure")
        void shouldPropagateRepositoryFailure() {
            stockRepository.failOnFind = true;

            var result = useCase.execute(ACTOR);

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

        @Test
        @DisplayName("should return Unauthorized when actor lacks permission")
        void shouldReturnUnauthorizedWhenNotPermitted() {
            authPort.allowed = false;

            var result = useCase.execute(ACTOR);

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

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

    private Stock createStockWithMinimumLevel(String availableAmount, String minimumAmount) {
        var batch = StockBatch.reconstitute(
                StockBatchId.generate(),
                new BatchReference("BATCH-" + availableAmount, BatchType.PRODUCED),
                Quantity.reconstitute(new BigDecimal(availableAmount), UnitOfMeasure.KILOGRAM),
                LocalDate.of(2026, 12, 31), StockBatchStatus.AVAILABLE, Instant.now()
        );
        var minimumLevel = new MinimumLevel(Quantity.reconstitute(new BigDecimal(minimumAmount), UnitOfMeasure.KILOGRAM));
        return Stock.reconstitute(
                StockId.generate(), ArticleId.of("article-" + availableAmount), StorageLocationId.of("location-1"),
                minimumLevel, null, new ArrayList<>(List.of(batch)), List.of()
        );
    }

    // ==================== Authorization Test Double ====================

    private static class AllowAllAuthorizationPort implements AuthorizationPort {
        boolean allowed = true;

        @Override
        public boolean can(ActorId actor, Action action) {
            return allowed;
        }

        @Override
        public boolean can(ActorId actor, Action action, ResourceId resource) {
            return allowed;
        }
    }

    // ==================== In-Memory Test Double ====================

    private static class InMemoryStockRepository implements StockRepository {

        List<Stock> stocksBelowMinimumLevel = new ArrayList<>();
        boolean failOnFind = false;

        @Override
        public Result<RepositoryError, List<Stock>> findAllBelowMinimumLevel() {
            if (failOnFind) {
                return Result.failure(new RepositoryError.DatabaseError("Test DB error"));
            }
            return Result.success(stocksBelowMinimumLevel);
        }

        // Unused methods for this test
        @Override public Result<RepositoryError, Optional<Stock>> findById(StockId id) { return Result.success(Optional.empty()); }
        @Override public Result<RepositoryError, Optional<Stock>> findByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) { return Result.success(Optional.empty()); }
        @Override public Result<RepositoryError, Boolean> existsByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) { return Result.success(false); }
        @Override public Result<RepositoryError, List<Stock>> findAll() { return Result.success(List.of()); }
        @Override public Result<RepositoryError, List<Stock>> findAllByStorageLocationId(StorageLocationId storageLocationId) { return Result.success(List.of()); }
        @Override public Result<RepositoryError, List<Stock>> findAllByArticleId(ArticleId articleId) { return Result.success(List.of()); }
        @Override public Result<RepositoryError, List<Stock>> findAllWithExpiryRelevantBatches(LocalDate referenceDate) { return Result.success(List.of()); }
        @Override public Result<RepositoryError, Void> save(Stock stock) { return Result.success(null); }
    }
}
