package de.effigenix.application.inventory;

import de.effigenix.application.inventory.command.RecordStockMovementCommand;
import de.effigenix.domain.inventory.*;
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 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class RecordStockMovementTest {

    private StockMovementRepository repository;
    private AuthorizationPort authPort;
    private UnitOfWork unitOfWork;
    private RecordStockMovement useCase;
    private final ActorId actor = ActorId.of("user-1");

    @BeforeEach
    void setUp() {
        repository = mock(StockMovementRepository.class);
        authPort = mock(AuthorizationPort.class);
        unitOfWork = mock(UnitOfWork.class);
        useCase = new RecordStockMovement(repository, authPort, unitOfWork);
        lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
        when(authPort.can(any(ActorId.class), any())).thenReturn(true);
    }

    private RecordStockMovementCommand validCommand() {
        return new RecordStockMovementCommand(
                "stock-1", "article-1", "batch-id-1",
                "CHARGE-001", "PRODUCED",
                "GOODS_RECEIPT", null,
                "10.0", "KILOGRAM",
                null, null, "user-1"
        );
    }

    @Test
    @DisplayName("should record stock movement successfully")
    void shouldRecordSuccessfully() {
        when(repository.save(any())).thenReturn(Result.success(null));

        var result = useCase.execute(validCommand(), actor);

        assertThat(result.isSuccess()).isTrue();
        var movement = result.unsafeGetValue();
        assertThat(movement.movementType()).isEqualTo(MovementType.GOODS_RECEIPT);
        assertThat(movement.direction()).isEqualTo(MovementDirection.IN);

        var captor = org.mockito.ArgumentCaptor.forClass(StockMovement.class);
        verify(repository).save(captor.capture());
        assertThat(captor.getValue().id()).isEqualTo(movement.id());
    }

    @Test
    @DisplayName("should return domain error when validation fails")
    void shouldReturnDomainErrorOnValidationFailure() {
        var cmd = new RecordStockMovementCommand(
                null, "article-1", "batch-id-1",
                "CHARGE-001", "PRODUCED",
                "GOODS_RECEIPT", null,
                "10.0", "KILOGRAM",
                null, null, "user-1"
        );

        var result = useCase.execute(cmd, actor);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StockMovementError.InvalidStockId.class);
        verify(repository, never()).save(any());
    }

    @Test
    @DisplayName("should return repository failure on save error")
    void shouldReturnRepositoryFailureOnSaveError() {
        when(repository.save(any())).thenReturn(
                Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = useCase.execute(validCommand(), actor);

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

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

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

            var result = useCase.execute(validCommand(), actor);

            assertThat(result.isFailure()).isTrue();
            assertThat(result.unsafeGetError()).isInstanceOf(StockMovementError.Unauthorized.class);
            verify(repository, never()).save(any());
        }
    }
}
