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.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
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.math.BigDecimal;
import java.time.Instant;
import java.util.Optional;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("GetStockMovement Use Case")
class GetStockMovementTest {

    @Mock private StockMovementRepository repository;
    @Mock private AuthorizationPort authPort;

    private GetStockMovement useCase;
    private StockMovement existingMovement;
    private final ActorId actor = ActorId.of("user-1");

    @BeforeEach
    void setUp() {
        useCase = new GetStockMovement(repository, authPort);
        when(authPort.can(any(ActorId.class), any())).thenReturn(true);

        existingMovement = StockMovement.reconstitute(
                StockMovementId.of("movement-1"),
                StockId.of("stock-1"),
                ArticleId.of("article-1"),
                StockBatchId.of("batch-1"),
                new BatchReference("CHARGE-001", BatchType.PRODUCED),
                MovementType.GOODS_RECEIPT,
                MovementDirection.IN,
                Quantity.reconstitute(new BigDecimal("10"), UnitOfMeasure.KILOGRAM),
                null, null, "user-1", Instant.now()
        );
    }

    @Test
    @DisplayName("should return movement when found")
    void shouldReturnMovementWhenFound() {
        when(repository.findById(StockMovementId.of("movement-1")))
                .thenReturn(Result.success(Optional.of(existingMovement)));

        var result = useCase.execute("movement-1", actor);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue().id().value()).isEqualTo("movement-1");
    }

    @Test
    @DisplayName("should fail with StockMovementNotFound when not found")
    void shouldFailWhenNotFound() {
        when(repository.findById(StockMovementId.of("movement-1")))
                .thenReturn(Result.success(Optional.empty()));

        var result = useCase.execute("movement-1", actor);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StockMovementError.StockMovementNotFound.class);
    }

    @Test
    @DisplayName("should fail with RepositoryFailure when repository fails")
    void shouldFailWhenRepositoryFails() {
        when(repository.findById(StockMovementId.of("movement-1")))
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = useCase.execute("movement-1", actor);

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

    @Test
    @DisplayName("should fail with StockMovementNotFound when id is null")
    void shouldFailWhenIdIsNull() {
        var result = useCase.execute(null, actor);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StockMovementError.StockMovementNotFound.class);
    }

    @Test
    @DisplayName("should fail with StockMovementNotFound when id is blank")
    void shouldFailWhenIdIsBlank() {
        var result = useCase.execute("  ", actor);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StockMovementError.StockMovementNotFound.class);
    }

    @Nested
    @DisplayName("Authorization")
    class Authorization {

        @Test
        @DisplayName("should fail with Unauthorized when actor lacks STOCK_MOVEMENT_READ")
        void shouldFailWhenUnauthorized() {
            when(authPort.can(actor, InventoryAction.STOCK_MOVEMENT_READ)).thenReturn(false);

            var result = useCase.execute("movement-1", actor);

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