package de.effigenix.application.inventory;

import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
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.time.Instant;
import java.time.LocalDate;
import java.util.List;
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("GetInventoryCount Use Case")
class GetInventoryCountTest {

    @Mock private InventoryCountRepository inventoryCountRepository;
    @Mock private AuthorizationPort authPort;

    private GetInventoryCount getInventoryCount;
    private InventoryCount existingCount;
    private final ActorId actorId = ActorId.of("user-1");

    @BeforeEach
    void setUp() {
        getInventoryCount = new GetInventoryCount(inventoryCountRepository, authPort);
        when(authPort.can(any(ActorId.class), any())).thenReturn(true);

        existingCount = InventoryCount.reconstitute(
                InventoryCountId.of("count-1"),
                StorageLocationId.of("location-1"),
                LocalDate.now(),
                "user-1",
                null,
                InventoryCountStatus.OPEN,
                null,
                Instant.now(),
                List.of()
        );
    }

    @Test
    @DisplayName("should return inventory count when found")
    void shouldReturnCountWhenFound() {
        when(inventoryCountRepository.findById(InventoryCountId.of("count-1")))
                .thenReturn(Result.success(Optional.of(existingCount)));

        var result = getInventoryCount.execute("count-1", actorId);

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

    @Test
    @DisplayName("should fail with InventoryCountNotFound when not found")
    void shouldFailWhenNotFound() {
        when(inventoryCountRepository.findById(InventoryCountId.of("count-1")))
                .thenReturn(Result.success(Optional.empty()));

        var result = getInventoryCount.execute("count-1", actorId);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(InventoryCountError.InventoryCountNotFound.class);
    }

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

        var result = getInventoryCount.execute("count-1", actorId);

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

    @Test
    @DisplayName("should fail with InvalidInventoryCountId when id is null")
    void shouldFailWhenIdIsNull() {
        var result = getInventoryCount.execute(null, actorId);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(InventoryCountError.InvalidInventoryCountId.class);
    }

    @Test
    @DisplayName("should fail with InvalidInventoryCountId when id is blank")
    void shouldFailWhenIdIsBlank() {
        var result = getInventoryCount.execute("  ", actorId);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(InventoryCountError.InvalidInventoryCountId.class);
    }

    @Test
    @DisplayName("should fail with InvalidInventoryCountId when id is empty string")
    void shouldFailWhenIdIsEmpty() {
        var result = getInventoryCount.execute("", actorId);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(InventoryCountError.InvalidInventoryCountId.class);
    }
}
