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.util.Optional;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("GetStorageLocation Use Case")
class GetStorageLocationTest {

    @Mock private StorageLocationRepository storageLocationRepository;
    @Mock private AuthorizationPort authPort;

    private GetStorageLocation getStorageLocation;
    private ActorId performedBy;

    @BeforeEach
    void setUp() {
        getStorageLocation = new GetStorageLocation(storageLocationRepository, authPort);
        performedBy = ActorId.of("admin-user");
    }

    private StorageLocation existingLocation(String id) {
        return StorageLocation.reconstitute(
                StorageLocationId.of(id),
                new StorageLocationName("Kühlraum 1"),
                StorageType.COLD_ROOM,
                null,
                true
        );
    }

    @Test
    @DisplayName("should return storage location when found")
    void shouldReturnStorageLocationWhenFound() {
        var locationId = "location-1";
        when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
        when(storageLocationRepository.findById(StorageLocationId.of(locationId)))
                .thenReturn(Result.success(Optional.of(existingLocation(locationId))));

        var result = getStorageLocation.execute(locationId, performedBy);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.unsafeGetValue().id().value()).isEqualTo(locationId);
    }

    @Test
    @DisplayName("should fail with StorageLocationNotFound when not found")
    void shouldFailWhenNotFound() {
        when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
        when(storageLocationRepository.findById(StorageLocationId.of("nonexistent")))
                .thenReturn(Result.success(Optional.empty()));

        var result = getStorageLocation.execute("nonexistent", performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
    }

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

        var result = getStorageLocation.execute("location-1", performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.Unauthorized.class);
        verify(storageLocationRepository, never()).findById(any());
    }

    @Test
    @DisplayName("should fail with RepositoryFailure when repository fails")
    void shouldFailWhenRepositoryFails() {
        when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);
        when(storageLocationRepository.findById(StorageLocationId.of("location-1")))
                .thenReturn(Result.failure(new RepositoryError.DatabaseError("connection lost")));

        var result = getStorageLocation.execute("location-1", performedBy);

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

    @Test
    @DisplayName("should fail with StorageLocationNotFound when id is null")
    void shouldFailWhenIdIsNull() {
        when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);

        var result = getStorageLocation.execute(null, performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
        verify(storageLocationRepository, never()).findById(any());
    }

    @Test
    @DisplayName("should fail with StorageLocationNotFound when id is blank")
    void shouldFailWhenIdIsBlank() {
        when(authPort.can(performedBy, InventoryAction.STOCK_READ)).thenReturn(true);

        var result = getStorageLocation.execute("  ", performedBy);

        assertThat(result.isFailure()).isTrue();
        assertThat(result.unsafeGetError()).isInstanceOf(StorageLocationError.StorageLocationNotFound.class);
        verify(storageLocationRepository, never()).findById(any());
    }
}
