package com.iqser.red.service.peristence.v1.server.integration.tests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateExternalClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionUpdateRequest;

import feign.FeignException;

public class ComponentDefinitionTests extends AbstractPersistenceServerServiceTest {

    @Autowired
    private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;

    @Autowired
    private DossierTemplateExternalClient dossierTemplateExternalClient;

    @Autowired
    private ComponentDefinitionPersistenceService componentDefinitionPersistenceService;


    @Test
    public void testCreateComponentDefinition() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate);

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));

        assertEquals(response.size(), 1);
        assertEquals(response.get(0).getRank(), 1);
        assertEquals(response.get(0).getDescription(), componentDefinitionAddRequest.getDescription());
        assertEquals(response.get(0).getDisplayName(), componentDefinitionAddRequest.getDisplayName());
        assertEquals(response.get(0).getTechnicalName(), componentDefinitionAddRequest.getTechnicalName());
        assertNull(response.get(0).getSoftDeleteTime());

        var noDisplayNameComponent = ComponentDefinitionAddRequest.builder().technicalName("component_2").description("Description").build();
        var result1 = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(noDisplayNameComponent));
        assertTrue(result1.get(0).getDisplayName().equals("component_2"));

        var noTechnicalNameComponent = ComponentDefinitionAddRequest.builder().technicalName("").displayName("Component 1").description("Description").build();
        var result = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(noTechnicalNameComponent)));
        assertTrue(result.getMessage().contains("Technical name can not be empty for a component."));
    }


    private ComponentDefinitionAddRequest buildComponentDefinitionAddRequest(DossierTemplateModel dossierTemplate) {

        return ComponentDefinitionAddRequest.builder().technicalName("component_1").displayName("Component 1").description("Description").build();
    }


    @Test
    public void testGetComponentDefinition() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate);

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);

        var component = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId());
        assertEquals(component.getRank(), 1);
        assertEquals(component.getDescription(), componentDefinitionAddRequest.getDescription());
        assertEquals(component.getDisplayName(), componentDefinitionAddRequest.getDisplayName());
        assertEquals(component.getTechnicalName(), componentDefinitionAddRequest.getTechnicalName());
        assertNull(component.getSoftDeleteTime());

        var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder().technicalName("component_2").displayName("Component 2").description("Description").build();

        response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest2));
        assertEquals(response.size(), 1);

        var components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false);
        assertEquals(components.size(), 2);

        dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId()));
        component = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId());
        assertEquals(component.getRank(), 2);
        assertNotNull(component.getSoftDeleteTime());

        components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false);
        assertEquals(components.size(), 1);

        var componentsError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.getComponents("123", false));
        assertTrue(componentsError.getMessage().contains("DossierTemplate with Id 123 not found."));

        var componentError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.getComponent("123", "321"));
        assertTrue(componentError.getMessage().contains("DossierTemplate with Id 123 not found."));
    }


    @Test
    public void testUpdateComponentDefinition() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate);

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);

        var componentDefinitionUpdateRequest = ComponentDefinitionUpdateRequest.builder()
                .id(response.get(0).getId())
                .description("updated description")
                .displayName("updated display name")
                .build();
        var updatedComponents = dossierTemplateExternalClient.updateComponents(dossierTemplate.getId(), List.of(componentDefinitionUpdateRequest));
        assertEquals(updatedComponents.size(), 1);
        assertEquals(updatedComponents.get(0).getRank(), 1);
        assertEquals(updatedComponents.get(0).getDescription(), componentDefinitionUpdateRequest.getDescription());
        assertEquals(updatedComponents.get(0).getDisplayName(), componentDefinitionUpdateRequest.getDisplayName());
        assertEquals(updatedComponents.get(0).getTechnicalName(), componentDefinitionAddRequest.getTechnicalName());
        assertNull(updatedComponents.get(0).getSoftDeleteTime());

        var unknownDossierError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.updateComponents("123", List.of(componentDefinitionUpdateRequest)));
        assertTrue(unknownDossierError.getMessage().contains("DossierTemplate with Id 123 not found."));
    }


    @Test
    public void testSoftDeleteComponentDefinition() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate);

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);

        dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId()));

        var softDeletedComponent = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId());
        assertNotNull(softDeletedComponent.getSoftDeleteTime());

        var unknownDossierError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.deleteComponents("123", List.of(response.get(0).getId())));
        assertTrue(unknownDossierError.getMessage().contains("DossierTemplate with Id 123 not found."));
    }

    @Test
    public void testAddComponentDefinitionAfterSoftDeletion() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = buildComponentDefinitionAddRequest(dossierTemplate);

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);

        dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId()));

        var softDeletedComponent = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId());
        assertNotNull(softDeletedComponent.getSoftDeleteTime());
        List<ComponentDefinition> componentsIncludingSoftDeleted = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), true);
        List<ComponentDefinition> componentsExcludingSoftDeleted = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false);
        assertEquals(componentsIncludingSoftDeleted.size(), componentsExcludingSoftDeleted.size() + 1);

        String changedDescription = "changed description";
        componentDefinitionAddRequest.setDescription(changedDescription);
        int count = componentDefinitionPersistenceService.maxRankByDossierTemplateId(dossierTemplate.getId());
        response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);
        assertEquals(response.get(0).getDescription(), changedDescription);
        assertEquals(response.get(0).getRank(), count + 1);
    }


    @Test
    public void testRestoreDeletedComponentDefinitions() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder().technicalName("Component 1").displayName("Component 1").description("Description").build();
        var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder().technicalName("Component 2").displayName("Component 2").description("Description").build();
        var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder().technicalName("Component 3").displayName("Component 3").description("Description").build();

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(),
                                                                      List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3));
        assertEquals(response.size(), 3);

        dossierTemplateExternalClient.deleteComponents(dossierTemplate.getId(), List.of(response.get(0).getId()));

        var softDeletedComponent = dossierTemplateExternalClient.getComponent(dossierTemplate.getId(), response.get(0).getId());
        assertNotNull(softDeletedComponent.getSoftDeleteTime());

        var restoredComponents = dossierTemplateExternalClient.restoreComponents(dossierTemplate.getId(), List.of(response.get(0).getId()));
        assertEquals(restoredComponents.size(), 1);
        assertEquals(restoredComponents.get(0).getId(), response.get(0).getId());
        assertEquals(restoredComponents.get(0).getRank(), 4);
        assertNull(restoredComponents.get(0).getSoftDeleteTime());

        var unknownDossierError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.restoreComponents("123", List.of(response.get(0).getId())));
        assertTrue(unknownDossierError.getMessage().contains("DossierTemplate with Id 123 not found."));
    }


    @Test
    public void testSnakeCaseTechnicalName() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1");
        var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder().technicalName("Component 1").displayName("Component 1").description("Description").build();

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(), List.of(componentDefinitionAddRequest));
        assertEquals(response.size(), 1);
        assertEquals(response.get(0).getTechnicalName(), "component_1");
    }


    @Test
    public void testGetAllComponentsForADossierTemplate() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1");
        var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder().technicalName("Component 1").displayName("Component 1").description("Description").build();
        var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder().technicalName("Component 2").displayName("Component 2").description("Description").build();
        var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder().technicalName("Component 3").displayName("Component 3").description("Description").build();

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(),
                                                                      List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3));
        assertEquals(response.size(), 3);

        var components = dossierTemplateExternalClient.getComponents(dossierTemplate.getId(), false);
        assertEquals(components.size(), 3);
        assertEquals(components.get(0).getId(), response.get(0).getId());
        assertEquals(components.get(0).getRank(), response.get(0).getRank());
        assertEquals(components.get(1).getId(), response.get(1).getId());
        assertEquals(components.get(1).getRank(), response.get(1).getRank());
        assertEquals(components.get(2).getId(), response.get(2).getId());
        assertEquals(components.get(2).getRank(), response.get(2).getRank());
    }


    @Test
    public void testReorderComponentDefinitions() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1");
        var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder().technicalName("Component 1").displayName("Component 1").description("Description").build();
        var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder().technicalName("Component 2").displayName("Component 2").description("Description").build();
        var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder().technicalName("Component 3").displayName("Component 3").description("Description").build();

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(),
                                                                      List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3));
        assertEquals(response.size(), 3);
        String firstComponentId = response.get(0).getId();
        String secondComponentId = response.get(1).getId();
        String thirdComponentId = response.get(2).getId();

        var newOrder = dossierTemplateExternalClient.reorderComponents(dossierTemplate.getId(), List.of(secondComponentId, thirdComponentId, firstComponentId));
        assertEquals(newOrder.size(), 3);
        assertEquals(newOrder.get(0).getId(), secondComponentId);
        assertEquals(newOrder.get(0).getRank(), 1);
        assertEquals(newOrder.get(1).getId(), thirdComponentId);
        assertEquals(newOrder.get(1).getRank(), 2);
        assertEquals(newOrder.get(2).getId(), firstComponentId);
        assertEquals(newOrder.get(2).getRank(), 3);

        var unknownDossierError = assertThrows(FeignException.class, () -> dossierTemplateExternalClient.reorderComponents("123", List.of(secondComponentId, thirdComponentId, firstComponentId)));
        assertTrue(unknownDossierError.getMessage().contains("DossierTemplate with Id 123 not found."));
    }


    @Test
    public void testReorderComponentDefinitionsWithShiftingRanks() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate("dossier1");
        var componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder().technicalName("Component 1").displayName("Component 1").description("Description").build();
        var componentDefinitionAddRequest2 = ComponentDefinitionAddRequest.builder().technicalName("Component 2").displayName("Component 2").description("Description").build();
        var componentDefinitionAddRequest3 = ComponentDefinitionAddRequest.builder().technicalName("Component 3").displayName("Component 3").description("Description").build();

        var response = dossierTemplateExternalClient.createComponents(dossierTemplate.getId(),
                                                                      List.of(componentDefinitionAddRequest, componentDefinitionAddRequest2, componentDefinitionAddRequest3));
        assertEquals(response.size(), 3);
        String firstComponentId = response.get(0).getId();
        String secondComponentId = response.get(1).getId();
        String thirdComponentId = response.get(2).getId();

        var newOrder = dossierTemplateExternalClient.reorderComponents(dossierTemplate.getId(), List.of(secondComponentId));
        assertEquals(newOrder.size(), 3);
        assertEquals(newOrder.get(0).getId(), secondComponentId);
        assertEquals(newOrder.get(0).getRank(), 1);
        assertEquals(newOrder.get(1).getId(), firstComponentId);
        assertEquals(newOrder.get(1).getRank(), 2);
        assertEquals(newOrder.get(2).getId(), thirdComponentId);
        assertEquals(newOrder.get(2).getRank(), 3);
    }

}
