package org.takiguchi.cerberus.cerberusapp.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.service.servicemanager.ServiceManagerProvider;
import org.takiguchi.cerberus.cerberusapp.service.validator.ApplicationValidator;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;

@ExtendWith(MockitoExtension.class)
class ApplicationServiceTest {
    private ApplicationService service;
    @Mock
    private ApplicationRepositoryPort applicationRepositoryPort;
    @Mock
    private ApplicationValidator applicationValidator;
    @Mock
    private ServiceManagerProvider serviceManagerProvider;
    @Captor
    private ArgumentCaptor<Application> applicationCaptor;

    @BeforeEach
    void setUp() {
        service = new ApplicationService(applicationValidator, applicationRepositoryPort, serviceManagerProvider);
    }

    @Test
    void getById_should_return_the_application() {
        // given
        UUID applicationId = UUID.randomUUID();

        Application application = anApplication().build();
        given(applicationRepositoryPort.getById(applicationId)).willReturn(Optional.of(application));

        // when
        Optional<Application> result = service.getById(applicationId);

        // then
        assertThat(result).contains(application);
    }

    @Test
    void getAll_should_return_all_applications() {
        // given
        Application application1 = anApplication().build();
        Application application2 = anApplication().build();
        given(applicationRepositoryPort.getAll()).willReturn(List.of(application1, application2));

        // when
        List<Application> result = service.getAll();

        // then
        assertThat(result).contains(application1, application2);
    }

    @Test
    void add_should_validate_the_application_then_add_it() {
        // given
        Application application = anApplication().build();

        InOrder inOrder = inOrder(applicationValidator, applicationRepositoryPort);

        Application addedApplication = anApplication().build();
        given(applicationRepositoryPort.add(application)).willReturn(addedApplication);

        // when
        Application result = service.add(application);

        // then
        assertThat(result).isEqualTo(addedApplication);
        inOrder.verify(applicationValidator).validate(application);
        inOrder.verify(applicationRepositoryPort).add(application);
    }

    @Test
    void update_should_retrieve_application_then_edit_some_fields_then_validate_it__then_update_it() {
        // given
        UUID newId = UUID.randomUUID();
        String newName = "new name";
        String newServiceName = "new service name";
        Application application = anApplication()
                .withId(newId)
                .withName(newName)
                .withServiceName(newServiceName)
                .build();

        InOrder inOrder = inOrder(applicationRepositoryPort, applicationValidator, applicationRepositoryPort);

        UUID oldId = UUID.randomUUID();
        Application existingApplication = anApplication()
                .withId(oldId)
                .build();
        given(applicationRepositoryPort.getById(newId)).willReturn(Optional.of(existingApplication));

        // when
        service.update(application);

        // then
        inOrder.verify(applicationRepositoryPort).getById(newId);
        inOrder.verify(applicationValidator).validate(applicationCaptor.capture());
        Application updatedApplication = applicationCaptor.getValue();
        inOrder.verify(applicationRepositoryPort).update(updatedApplication);

        assertThat(updatedApplication).isNotNull()
                .extracting(
                        Application::getId,
                        Application::getName,
                        Application::getServiceName
                )
                .contains(
                        oldId,
                        newName,
                        newServiceName
                );
    }
}