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

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ColorsEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.FileAttributesGeneralConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.LegalBasisEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.ColorsService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentMappingService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateCloneService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.LayoutParsingTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.WatermarkService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DateFormatsPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.EntryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;

import lombok.SneakyThrows;

@ExtendWith(SpringExtension.class)
public class DossierTemplateCloneServiceTest {

    @MockBean
    private DossierTemplateRepository dossierTemplateRepository;

    @MockBean
    private LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;

    @MockBean
    private RulesPersistenceService rulesPersistenceService;

    @MockBean
    private DateFormatsPersistenceService dateFormatsPersistenceService;

    @MockBean
    private DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;

    @MockBean
    private DictionaryPersistenceService dictionaryPersistenceService;

    @MockBean
    private EntryPersistenceService entryPersistenceService;

    @MockBean
    private FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;

    @MockBean
    private ReportTemplatePersistenceService reportTemplatePersistenceService;

    @MockBean
    private ColorsService colorsService;

    @MockBean
    private StorageService storageService;

    @MockBean
    private DossierStatusPersistenceService dossierStatusPersistenceService;

    @MockBean
    private WatermarkService watermarkService;

    @MockBean
    private FileManagementStorageService fileManagementStorageService;
    @MockBean
    private TypeRepository typeRepository;
    @MockBean
    private ComponentMappingService componentMappingService;

    @MockBean
    private ComponentDefinitionPersistenceService componentDefinitionPersistenceService;

    @MockBean
    private CurrentApplicationTypeProvider currentApplicationTypeProvider;

    @MockBean
    private LayoutParsingTypeProvider layoutParsingTypeProvider;

    private DossierTemplateCloneService dossierTemplateCloneService;

    private DossierTemplatePersistenceService dossierTemplatePersistenceService;

    private DossierTemplateEntity dummyTemplate;


    @BeforeEach
    @SneakyThrows
    public void setUp() {

        dossierTemplatePersistenceService = new DossierTemplatePersistenceService(dossierTemplateRepository,
                                                                                  legalBasisMappingPersistenceService,
                                                                                  rulesPersistenceService,
                                                                                  dictionaryPersistenceService,
                                                                                  currentApplicationTypeProvider,
                                                                                  layoutParsingTypeProvider,
                                                                                  typeRepository);

        when(currentApplicationTypeProvider.isDocuMine()).thenReturn(true);
        when(layoutParsingTypeProvider.deferFromCurrentApplicationType()).thenReturn(LayoutParsingType.DOCUMINE_OLD);

        dossierTemplateCloneService = new DossierTemplateCloneService(dossierTemplateRepository,
                                                                      legalBasisMappingPersistenceService,
                                                                      rulesPersistenceService,
                                                                      dateFormatsPersistenceService,
                                                                      dossierTemplatePersistenceService,
                                                                      dossierAttributeConfigPersistenceService,
                                                                      dictionaryPersistenceService,
                                                                      entryPersistenceService,
                                                                      fileAttributeConfigPersistenceService,
                                                                      reportTemplatePersistenceService,
                                                                      colorsService,
                                                                      storageService,
                                                                      dossierStatusPersistenceService,
                                                                      watermarkService,
                                                                      fileManagementStorageService,
                                                                      componentMappingService,
                                                                      componentDefinitionPersistenceService);

        dummyTemplate = new DossierTemplateEntity();
        setNonDefaultValues(dummyTemplate);
        dummyTemplate.setId("dummy-id");
        dummyTemplate.setName("dummy-name");
        dummyTemplate.setDescription("dummy-description");
        dummyTemplate.setDateAdded(OffsetDateTime.now());
        dummyTemplate.setDateModified(OffsetDateTime.now());
        dummyTemplate.setCreatedBy("dummy-user");
        dummyTemplate.setModifiedBy("dummy-user");
        dummyTemplate.setValidFrom(OffsetDateTime.now().minusYears(6));
        dummyTemplate.setValidTo(OffsetDateTime.now().plusYears(2));
        dummyTemplate.setDossierTemplateStatus(DossierTemplateStatus.INACTIVE);
        dummyTemplate.setLayoutParsingType(LayoutParsingType.REDACT_MANAGER_WITHOUT_DUPLICATE_PARAGRAPH);
        when(dossierTemplateRepository.findById(anyString())).thenReturn(Optional.of(dummyTemplate));

    }


    @SneakyThrows
    private void setNonDefaultValues(Object obj) {

        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            Class<?> type = field.getType();
            setBasicFieldToOtherValue(obj, field.getName(), type);

        }
    }


    public static void setBasicFieldToOtherValue(Object obj, String fieldName, Class<?> type) throws Exception {

        String suffix = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
        String setterName = "set" + suffix;
        String getterName = "get" + suffix;
        String alternativeGetterName = "is" + suffix;
        var getterMethod = findGetterMethod(obj.getClass(), getterName);
        var alternativeGetterMethod = findGetterMethod(obj.getClass(), alternativeGetterName);
        Object value;

        if (getterMethod != null) {
            value = getterMethod.invoke(obj);
        } else if (alternativeGetterMethod != null) {
            value = alternativeGetterMethod.invoke(obj);
        } else {
            return;
        }

        if (type.equals(Boolean.class) || type.equals(boolean.class)) {
            value = !(Boolean) value;
        } else if (type.equals(Integer.class) || type.equals(int.class)) {
            value = (Integer) value + 1;
        } else if (type.equals(Long.class) || type.equals(long.class)) {
            value = (Long) value + 1;
        } else if (type.equals(Float.class) || type.equals(float.class)) {
            value = (Float) value + 1;
        } else if (type.equals(Double.class) || type.equals(double.class)) {
            value = (Double) value + 1;
        } else if (type.equals(String.class)) {
            value = "Modified:" + value;
        } else {
            return;
        }

        var setterMethod = findSetterMethod(obj.getClass(), setterName, type);

        if (setterMethod != null) {
            setterMethod.invoke(obj, value);
        }
    }


    private static Method findSetterMethod(Class<?> clazz, String setterName, Class<?> parameterType) {

        var methods = clazz.getMethods();
        for (var method : methods) {
            if (method.getName().equals(setterName) && method.getParameterCount() == 1) {
                if (method.getParameterTypes()[0].isAssignableFrom(parameterType)) {
                    return method;
                }
            }
        }
        return null;
    }


    private static Method findGetterMethod(Class<?> clazz, String getterName) {

        var methods = clazz.getMethods();
        for (var method : methods) {
            if (method.getName().equals(getterName) && method.getParameterCount() == 0) {
                return method;
            }
        }
        return null;
    }


    @Test
    public void testCloneDossierTemplate() {

        CloneDossierTemplateRequest request = new CloneDossierTemplateRequest();
        request.setName("cloned-name");
        request.setDescription("cloned-description");
        request.setCloningUserId("cloning-user");
        OffsetDateTime validFrom = OffsetDateTime.now();
        dummyTemplate.setValidFrom(validFrom);
        OffsetDateTime validTo = OffsetDateTime.now().plusYears(4);
        dummyTemplate.setValidTo(validTo);

        when(rulesPersistenceService.getRules(anyString(), any())).thenReturn(Optional.of(new RuleSetEntity()));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(anyString())).thenReturn(List.of(LegalBasisEntity.builder()
                                                                                                               .name("dummy-name")
                                                                                                               .description("dummy-description")
                                                                                                               .reason("dummy-reason")
                                                                                                               .build()));
        when(dictionaryPersistenceService.getAllTypesForDossierTemplate(anyString(), anyBoolean())).thenReturn(Collections.emptyList());
        when(dossierAttributeConfigPersistenceService.getDossierAttributes(anyString())).thenReturn(Collections.emptyList());
        when(fileAttributeConfigPersistenceService.getFileAttributesGeneralConfiguration(anyString())).thenReturn(new FileAttributesGeneralConfigurationEntity());
        when(fileAttributeConfigPersistenceService.getFileAttributes(anyString())).thenReturn(Collections.emptyList());
        when(reportTemplatePersistenceService.findByDossierTemplateId(anyString())).thenReturn(Collections.emptyList());
        when(watermarkService.getWatermarksForDossierTemplateId(anyString())).thenReturn(Collections.emptyList());
        when(dossierStatusPersistenceService.getAllDossierStatusForTemplate(anyString())).thenReturn(Collections.emptyList());
        when(colorsService.getColors(anyString())).thenReturn(new ColorsEntity());
        when(componentDefinitionPersistenceService.findComponentsByDossierTemplateId(anyString())).thenReturn(Collections.emptyList());

        DossierTemplateEntity clonedTemplate = dossierTemplateCloneService.cloneDossierTemplate("dummy-id", request);

        assertNotNull(clonedTemplate);
        assertEquals("cloned-name", clonedTemplate.getName());
        assertEquals("cloned-description", clonedTemplate.getDescription());
        assertEquals("cloning-user", clonedTemplate.getCreatedBy());
        assertEquals("cloning-user", clonedTemplate.getModifiedBy());
        assertEquals(validFrom, clonedTemplate.getValidFrom());
        assertEquals(validTo, clonedTemplate.getValidTo());
        assertEquals(DossierTemplateStatus.ACTIVE, clonedTemplate.getDossierTemplateStatus());
        assertThat(clonedTemplate).usingRecursiveComparison()
                .ignoringFields("id", "name", "description", "dateAdded", "dateModified", "createdBy", "modifiedBy", "validFrom", "validTo", "cloningUser", "dossierTemplateStatus")
                .isEqualTo(dummyTemplate);

    }

}

