package de.effigenix.application.production;

import de.effigenix.domain.production.*;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
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.math.BigDecimal;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("ListRecipes Use Case")
class ListRecipesTest {

    @Mock private RecipeRepository recipeRepository;
    @Mock private AuthorizationPort authPort;

    private ListRecipes listRecipes;
    private ActorId performedBy;

    @BeforeEach
    void setUp() {
        listRecipes = new ListRecipes(recipeRepository, authPort);
        performedBy = ActorId.of("admin-user");
    }

    private Recipe recipeWithStatus(String id, RecipeStatus status) {
        return Recipe.reconstitute(
                RecipeId.of(id), new RecipeName("Rezept-" + id), 1, RecipeType.FINISHED_PRODUCT,
                null, new YieldPercentage(85), 14,
                Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(),
                "article-123", status, List.of(), List.of(),
                OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC)
        );
    }

    @Test
    @DisplayName("should_ReturnAllRecipes_When_Authorized")
    void should_ReturnAllRecipes_When_Authorized() {
        var recipes = List.of(
                recipeWithStatus("r1", RecipeStatus.DRAFT),
                recipeWithStatus("r2", RecipeStatus.ACTIVE)
        );
        when(authPort.can(performedBy, ProductionAction.RECIPE_READ)).thenReturn(true);
        when(recipeRepository.findAll()).thenReturn(Result.success(recipes));

        var result = listRecipes.execute(performedBy);

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

    @Test
    @DisplayName("should_ReturnFilteredRecipes_When_FilteredByStatus")
    void should_ReturnFilteredRecipes_When_FilteredByStatus() {
        var activeRecipes = List.of(recipeWithStatus("r2", RecipeStatus.ACTIVE));
        when(authPort.can(performedBy, ProductionAction.RECIPE_READ)).thenReturn(true);
        when(recipeRepository.findByStatus(RecipeStatus.ACTIVE)).thenReturn(Result.success(activeRecipes));

        var result = listRecipes.executeByStatus(RecipeStatus.ACTIVE, performedBy);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue()).hasSize(1);
        assertThat(result.unsafeGetValue().getFirst().status()).isEqualTo(RecipeStatus.ACTIVE);
    }

    @Test
    @DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
    void should_FailWithUnauthorized_When_ActorLacksPermission() {
        when(authPort.can(performedBy, ProductionAction.RECIPE_READ)).thenReturn(false);

        var result = listRecipes.execute(performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.Unauthorized.class);
        verify(recipeRepository, never()).findAll();
    }

    @Test
    @DisplayName("should_FailWithUnauthorized_When_ActorLacksPermissionForStatusFilter")
    void should_FailWithUnauthorized_When_ActorLacksPermissionForStatusFilter() {
        when(authPort.can(performedBy, ProductionAction.RECIPE_READ)).thenReturn(false);

        var result = listRecipes.executeByStatus(RecipeStatus.ACTIVE, performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(RecipeError.Unauthorized.class);
        verify(recipeRepository, never()).findByStatus(any());
    }
}
