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

import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_DATABASE;
import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_PASSWORD;
import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_USERNAME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.TestPropertySourceUtils;

import com.iqser.red.commons.jackson.ObjectMapperFactory;
import com.iqser.red.service.peristence.v1.server.Application;
import com.iqser.red.service.peristence.v1.server.integration.client.ApplicationConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.utils.MetricsPrinterService;
import com.iqser.red.service.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.ReportTemplatePlaceholderClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.client.searchservice.SearchClient;
import com.iqser.red.service.persistence.management.v1.processor.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.SystemManagedTypesImport;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ApplicationConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ApplicationConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.AuditRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.CommentRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DigitalSignatureRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierAttributeConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierAttributeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierStatusRepository;
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.DownloadStatusRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributeConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributesGeneralConfigurationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.IndexInformationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.LegalBasisMappingRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationPreferencesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ReportTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.RuleSetRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ViewedPagesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.WatermarkRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ForceRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.LegalBasisChangeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ManualRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.RecategorizationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.RemoveRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ResizeRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalsePositiveEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalseRecommendationEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentLogEntryDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogEntryDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
import com.knecon.fforesight.databasetenantcommons.providers.TenantCreatedDatabaseEventHandler;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.keycloakcommons.security.TenantAuthenticationManagerResolver;
import com.knecon.fforesight.mongo.database.commons.liquibase.TenantCreatedMongoEventHandler;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantsClient;
import com.knecon.fforesight.tenantcommons.model.AuthDetails;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import com.knecon.fforesight.tenantcommons.model.TenantCreatedEvent;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;

import io.micrometer.prometheus.PrometheusMeterRegistry;
import lombok.extern.slf4j.Slf4j;

import org.assertj.core.util.Lists;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.quartz.Scheduler;

@Slf4j
@ExtendWith(SpringExtension.class)
@EnableFeignClients(basePackageClasses = FileClient.class)
@Import(AbstractPersistenceServerServiceTest.TestConfiguration.class)
@ContextConfiguration(initializers = {AbstractPersistenceServerServiceTest.Initializer.class})
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = "spring-hibernate-query-utils.n-plus-one-queries-detection.error-level=INFO")
public abstract class AbstractPersistenceServerServiceTest {

    public static final String TENANT_1 = "redaction";
    public static final String TENANT_2 = "redaction2";

    @MockBean
    protected Scheduler scheduler;
    @MockBean
    protected RabbitTemplate rabbitTemplate;
    @MockBean
    protected SearchClient searchClient;
    @MockBean
    protected EntityLogService entityLogService;
    @MockBean
    protected PDFTronClient pdfTronRedactionClient;
    @Autowired
    protected ApplicationConfigClient appConfigClient;
    @Autowired
    protected StorageService storageService;
    @Autowired
    protected EntityLogMongoService entityLogMongoService;
    @Autowired
    protected EntityLogDocumentRepository entityLogDocumentRepository;
    @Autowired
    protected EntityLogEntryDocumentRepository entityLogEntryDocumentRepository;
    @Autowired
    protected ComponentLogMongoService componentLogMongoService;
    @Autowired
    protected ComponentLogDocumentRepository componentLogDocumentRepository;
    @Autowired
    protected ComponentLogEntryDocumentRepository componentLogEntryDocumentRepository;
    @Autowired
    protected FileManagementStorageService fileManagementStorageService;
    @Autowired
    protected DossierTemplateRepository dossierTemplateRepository;
    @Autowired
    protected DossierRepository dossierRepository;
    @Autowired
    protected FileRepository fileRepository;
    @Autowired
    protected ViewedPagesRepository viewedPagesRepository;
    @Autowired
    protected NotificationRepository notificationRepository;
    @Autowired
    protected AuditRepository auditRepository;
    @Autowired
    protected TypeRepository typeRepository;
    @Autowired
    protected ManualRedactionRepository manualRedactionRepository;
    @Autowired
    protected ForceRedactionRepository forceRedactionRepository;
    @Autowired
    protected RemoveRedactionRepository removeRedactionRepository;
    @Autowired
    protected LegalBasisChangeRepository legalBasisChangeRepository;
    @Autowired
    protected RecategorizationRepository recategorizationRepository;
    @Autowired
    protected WatermarkRepository watermarkRepository;
    @Autowired
    protected RuleSetRepository ruleSetRepository;
    @Autowired
    protected LegalBasisMappingRepository legalBasisMappingRepository;
    @Autowired
    protected FileAttributeConfigRepository fileAttributeConfigRepository;
    @Autowired
    protected FileAttributesGeneralConfigurationRepository fileAttributesGeneralConfigurationRepository;
    @Autowired
    protected ReportTemplateRepository reportTemplateRepository;
    @Autowired
    protected DigitalSignatureRepository digitalSignatureRepository;
    @Autowired
    protected FileAttributesRepository fileAttributesRepository;
    @Autowired
    protected DownloadStatusRepository downloadStatusRepository;
    @Autowired
    protected DossierAttributeRepository dossierAttributeRepository;
    @Autowired
    protected DossierAttributeConfigRepository dossierAttributeConfigRepository;
    @Autowired
    protected NotificationPreferencesRepository notificationPreferencesRepository;
    @Autowired
    protected MetricsPrinterService metricsPrinterService;
    @Autowired
    protected EntryRepository entryRepository;
    @Autowired
    protected IndexInformationRepository indexInformationRepository;
    @Autowired
    protected FalsePositiveEntryRepository falsePositiveEntryRepository;
    @Autowired
    protected FalseRecommendationEntryRepository falseRecommendationEntryRepository;
    @Autowired
    protected ApplicationConfigRepository applicationConfigRepository;
    @MockBean
    protected RedactionClient redactionClient;
    @Autowired
    protected PrometheusMeterRegistry prometheusMeterRegistry;
    @MockBean
    private RabbitAdmin rabbitAdmin;
    @Autowired
    private DossierStatusRepository dossierStatusRepository;
    @Autowired
    private ApplicationConfigService applicationConfigService;
    @Autowired
    protected UserService userService;
    @Autowired
    private TokenService tokenService;
    @MockBean
    protected TenantsClient tenantsClient;
    @MockBean
    private UsersClient usersClient;
    @Autowired
    protected EncryptionDecryptionService encryptionDecryptionService;
    @Autowired
    protected TenantCreatedDatabaseEventHandler tenantCreatedDatabaseEventHandler;
    @Autowired
    protected TenantCreatedMongoEventHandler tenantCreatedMongoEventHandler;
    @Autowired
    protected MongoTestConfig mongoTestConfig;
    @MockBean
    protected ReportTemplatePlaceholderClient reportTemplatePlaceholderClient;
    @Autowired
    protected CommentRepository commentRepository;
    @Autowired
    protected ResizeRedactionRepository resizeRedactionRepository;
    @MockBean
    protected TenantAuthenticationManagerResolver tenantAuthenticationManagerResolver;
    @Autowired
    private SystemManagedTypesImport systemManagedTypesImport;
    @Autowired
    private DictionaryManagementService dictionaryManagementService;
    @MockBean
    private CurrentApplicationTypeProvider currentApplicationTypeProvider;


    private static String[] getAllRoles() {

        var allRoles = ApplicationRoles.ROLE_DATA.entrySet()
                .stream()
                .flatMap(entry -> entry.getValue()
                        .stream())
                .collect(Collectors.toSet());
        allRoles.addAll(ApplicationRoles.UNMAPPED_ACTION_ROLES);
        allRoles.addAll(ApplicationRoles.ROLE_DATA.keySet());
        var rolesArray = allRoles.toArray(new String[0]);
        return rolesArray;
    }


    @BeforeEach
    public void setupTenantContext() {

        TenantContext.setTenantId(TENANT_1);
        when(currentApplicationTypeProvider.get()).thenReturn(getTenantApplicationType());
        when(currentApplicationTypeProvider.isDocuMine()).thenReturn(getTenantApplicationType().equals(TenantApplicationType.DocuMine));
        when(currentApplicationTypeProvider.isRedactManager()).thenReturn(getTenantApplicationType().equals(TenantApplicationType.RedactManager));
    }


    protected TenantApplicationType getTenantApplicationType() {

        return TenantApplicationType.RedactManager;
    }


    @AfterEach
    public void clearTenantContext() {

        TenantContext.clear();
    }


    @BeforeEach
    public void setupOptimize() {

        createTenants();

        TenantContext.setTenantId(TENANT_1);

        ApplicationConfig appConfig = ApplicationConfig.builder()
                .downloadCleanupDownloadFilesHours(8)
                .downloadCleanupNotDownloadFilesHours(72)
                .softDeleteCleanupTime(96)
                .hardDeleteCleanupRetryTime(72)
                .build();
        applicationConfigService.saveApplicationConfiguration(MagicConverter.convert(appConfig, ApplicationConfigurationEntity.class));

        tokenService.setUser("manageradmin1@test.com", "secret");

        var allUsers = new ArrayList<com.iqser.red.service.persistence.management.v1.processor.service.users.model.User>();
        allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
                             .userId("manageradmin1@test.com")
                             .email("manageradmin1@test.com")
                             .isActive(true)
                             .roles(Set.of(getAllRoles()))
                             .build());
        allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
                             .userId("manageradmin2@test.com")
                             .email("manageradmin2@test.com")
                             .isActive(true)
                             .roles(Set.of(getAllRoles()))
                             .build());
        allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
                             .userId("manageradmin3@test.com")
                             .email("manageradmin3@test.com")
                             .isActive(true)
                             .roles(Set.of(getAllRoles()))
                             .build());
        Set<String> allRolesWithoutValid = Arrays.stream(getAllRoles())
                .collect(Collectors.toSet());
        allRolesWithoutValid.remove("RED_USER");
        allRolesWithoutValid.remove("RED_MANAGER");
        allUsers.add(com.iqser.red.service.persistence.management.v1.processor.service.users.model.User.builder()
                             .userId("manageradmin4@test.com")
                             .email("manageradmin4@test.com")
                             .isActive(true)
                             .roles(allRolesWithoutValid)
                             .build());
        when(usersClient.getAllUsers(false)).thenReturn(allUsers);
        when(usersClient.getAllUsers(true)).thenReturn(allUsers);
        // doNothing().when(pdfTronRedactionClient).testDigitalCurrentSignature(Mockito.any());

        when(rabbitAdmin.getQueueInfo(any())).thenReturn(null);

        when(entityLogService.getEntityLog(any(), any())).thenReturn(new EntityLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0));
        when(entityLogService.getEntityLog(any(), any(), any(), Mockito.anyBoolean())).thenReturn(new EntityLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0));
        when(redactionClient.testRules(any())).thenReturn(DroolsValidation.builder()
                                                                  .syntaxErrorMessages(Collections.emptyList())
                                                                  .deprecatedWarnings(Collections.emptyList())
                                                                  .build());
    }


    private void createTenants() {

        var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance().withDatabaseName("integration-tests-db-master").withUsername("sa").withPassword("sa");
        var mongoDbContainer = MongoDBTestContainer.getInstance();

        var port = postgreSQLContainerMaster.getJdbcUrl().substring(0, postgreSQLContainerMaster.getJdbcUrl().lastIndexOf('/')).split(":")[3];

        if (tenantsClient.getTenants() == null || tenantsClient.getTenants().isEmpty()) {

            var redactionTenant = new TenantResponse();
            redactionTenant.setTenantId(TENANT_1);
            redactionTenant.setGuid(TENANT_1);
            redactionTenant.setDisplayName(TENANT_1);
            redactionTenant.setAuthDetails(new AuthDetails());
            redactionTenant.setDatabaseConnection(DatabaseConnection.builder()
                                                          .driver("postgresql")
                                                          .host(postgreSQLContainerMaster.getHost())
                                                          .port(port)
                                                          .database("integration-tests-db-master")
                                                          .schema("public")
                                                          .username("sa")
                                                          .password(encryptionDecryptionService.encrypt("sa"))
                                                          .build());

            redactionTenant.setSearchConnection(SearchConnection.builder()
                                                        .hosts(Set.of("elasticsearchHost"))
                                                        .port(9200)
                                                        .scheme("https")
                                                        .username("elastic")
                                                        .numberOfShards("1")
                                                        .numberOfReplicas("5")
                                                        .build());

            redactionTenant.setS3StorageConnection(S3StorageConnection.builder()
                                                           .key("key")
                                                           .secret("secret")
                                                           .signerType("signerType")
                                                           .bucketName("bucketName")
                                                           .region("eu")
                                                           .endpoint("endpoint")
                                                           .build());

            redactionTenant.setMongoDBConnection(MongoDBConnection.builder()
                                                         .prefix("mongodb")
                                                         .username(MONGO_USERNAME)
                                                         .password(encryptionDecryptionService.encrypt(MONGO_PASSWORD))
                                                         .address(mongoDbContainer.getHost() + ":" + mongoDbContainer.getFirstMappedPort())
                                                         .database(MONGO_DATABASE)
                                                         .options("")
                                                         .build());

            var redactionTenant2 = new TenantResponse();
            redactionTenant2.setTenantId(TENANT_2);
            redactionTenant2.setGuid(TENANT_2);
            redactionTenant2.setDisplayName(TENANT_2);
            redactionTenant2.setAuthDetails(new AuthDetails());
            redactionTenant2.setDatabaseConnection(DatabaseConnection.builder()
                                                           .driver("postgresql")
                                                           .host(postgreSQLContainerMaster.getHost())
                                                           .port(port)
                                                           .database("integration-tests-db-master")
                                                           .schema("public")
                                                           .username("sa")
                                                           .password(encryptionDecryptionService.encrypt("sa"))
                                                           .build());

            redactionTenant2.setSearchConnection(SearchConnection.builder()
                                                         .hosts(Set.of("elasticsearchHost"))
                                                         .port(9200)
                                                         .scheme("https")
                                                         .username("elastic")
                                                         .numberOfShards("1")
                                                         .numberOfReplicas("5")
                                                         .build());

            redactionTenant2.setS3StorageConnection(S3StorageConnection.builder()
                                                            .key("key")
                                                            .secret("secret")
                                                            .signerType("signerType")
                                                            .bucketName("bucketName")
                                                            .region("eu")
                                                            .endpoint("endpoint")
                                                            .build());

            redactionTenant2.setMongoDBConnection(MongoDBConnection.builder()
                                                          .prefix("mongodb")
                                                          .username(MONGO_USERNAME)
                                                          .password(encryptionDecryptionService.encrypt(MONGO_PASSWORD))
                                                          .address(mongoDbContainer.getHost() + ":" + mongoDbContainer.getFirstMappedPort())
                                                          .database(TENANT_2)
                                                          .options("")
                                                          .build());

            when(tenantsClient.getTenant(TENANT_1)).thenReturn(redactionTenant);
            when(tenantsClient.getTenant(TENANT_2)).thenReturn(redactionTenant2);
            when(tenantsClient.getTenants()).thenReturn(List.of(redactionTenant, redactionTenant2));

            try {
                tenantCreatedDatabaseEventHandler.handle(new TenantCreatedEvent(TENANT_1));
                tenantCreatedDatabaseEventHandler.handle(new TenantCreatedEvent(TENANT_2));
                tenantCreatedMongoEventHandler.handle(new TenantCreatedEvent(TENANT_1));
                tenantCreatedMongoEventHandler.handle(new TenantCreatedEvent(TENANT_2));
            } catch (Exception e) {

                e.printStackTrace();
            }
        }

    }


    private void createDatabase(String db, String jdbcUrl, String password) {

        var ds = new SingleConnectionDataSource();
        ds.setUsername("sa");
        ds.setPassword("sa");
        ds.setUrl(jdbcUrl);
        ds.setDriverClassName("org.postgresql.Driver");
        var jdbcTemplate = new JdbcTemplate(ds);
        jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE DATABASE " + db));
        jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE USER " + db + " WITH ENCRYPTED PASSWORD '" + password + "'"));
        jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("GRANT ALL PRIVILEGES ON DATABASE " + db + " TO " + db));
    }


    @AfterEach
    public void cleanupStorage() {

        ((FileSystemBackedStorageService) this.storageService).clearStorage();
    }


    @AfterEach
    public void printMetrics() {

        // this.metricsPrinterService.printMetrics();
    }


    @AfterEach
    public void afterTests() {

        tenantsClient.getTenants()
                .forEach(tenant -> {

                    TenantContext.setTenantId(tenant.getTenantId());

                    commentRepository.deleteAll();
                    resizeRedactionRepository.deleteAll();
                    falsePositiveEntryRepository.deleteAll();
                    falseRecommendationEntryRepository.deleteAll();
                    entryRepository.deleteAll();
                    dossierAttributeRepository.deleteAll();
                    dossierAttributeConfigRepository.deleteAll();
                    downloadStatusRepository.deleteAll();
                    fileAttributesRepository.deleteAll();
                    digitalSignatureRepository.deleteAll();
                    fileAttributesGeneralConfigurationRepository.deleteAll();
                    fileAttributeConfigRepository.deleteAll();
                    reportTemplateRepository.deleteAll();
                    typeRepository.deleteAll();
                    viewedPagesRepository.deleteAll();
                    notificationRepository.deleteAll();
                    auditRepository.deleteAll();
                    manualRedactionRepository.deleteAll();
                    forceRedactionRepository.deleteAll();
                    removeRedactionRepository.deleteAll();
                    legalBasisChangeRepository.deleteAll();
                    recategorizationRepository.deleteAll();
                    legalBasisMappingRepository.deleteAll();
                    ruleSetRepository.deleteAll();
                    fileRepository.deleteAll();
                    dossierRepository.deleteAll();
                    dossierStatusRepository.deleteAll();
                    watermarkRepository.deleteAll();
                    dossierTemplateRepository.deleteAll();
                    notificationPreferencesRepository.deleteAll();
                    indexInformationRepository.deleteAll();
                    applicationConfigRepository.deleteAll();
                    entityLogEntryDocumentRepository.deleteAll();
                    entityLogDocumentRepository.deleteAll();
                    componentLogDocumentRepository.deleteAll();
                    componentLogEntryDocumentRepository.deleteAll();

                });
    }


    @Slf4j
    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        static AtomicInteger UniquePortFactory = new AtomicInteger(28081);


        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

            // any time the WebEnvironment is re-instantiated use a new port, as it sometimes leads to PortBindExceptions
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
            int port = UniquePortFactory.getAndIncrement();
            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, "server.port=" + port);

            var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance().withDatabaseName("integration-tests-db-master").withUsername("sa").withPassword("sa");

            postgreSQLContainerMaster.start();

            var redisContainer = RedisTestContainer.getInstance();
            redisContainer.start();

            var mongoInstance = MongoDBTestContainer.getInstance();
            mongoInstance.start();
            createMongoDBDatabase(mongoInstance, TENANT_1);
            createMongoDBDatabase(mongoInstance, TENANT_2);

            log.info("Hosts are -  Redis: {}, Postgres: {}, MongoDB: {}", redisContainer.getHost(), postgreSQLContainerMaster.getHost(), mongoInstance.getHost());

            TestPropertyValues.of("REDIS_PORT=" + redisContainer.getFirstMappedPort(),
                                  "REDIS_HOST=" + redisContainer.getHost(),
                                  "MONGODB_HOST=" + mongoInstance.getHost(),
                                  "MONGODB_PORT=" + mongoInstance.getFirstMappedPort(),
                                  "MONGODB_USER=" + MONGO_USERNAME,
                                  "MONGODB_PASSWORD=" + MONGO_PASSWORD,
                                  "fforesight.jobs.enabled=false",
                                  "fforesight.keycloak.enabled=false",
                                  "POD_NAME=persistence-service").applyTo(configurableApplicationContext.getEnvironment());

        }


        private static void createMongoDBDatabase(MongoDBTestContainer mongoDBTestContainer, String databaseName) {

            try (MongoClient mongoClient = MongoClients.create(String.format("mongodb://%s:%s@%s:%s/",
                                                                             MONGO_USERNAME,
                                                                             MONGO_PASSWORD,
                                                                             mongoDBTestContainer.getHost(),
                                                                             mongoDBTestContainer.getFirstMappedPort()))) {
                MongoDatabase database = mongoClient.getDatabase(databaseName);
                BsonDocument createUserCommand = new BsonDocument();
                createUserCommand.append("createUser", new BsonString(MONGO_USERNAME));
                createUserCommand.append("pwd", new BsonString(MONGO_PASSWORD));
                BsonArray roles = new BsonArray();
                roles.add(new BsonDocument("role", new BsonString("dbOwner")).append("db", new BsonString(databaseName)));
                createUserCommand.append("roles", roles);

                try {
                    database.runCommand(createUserCommand);
                } catch (MongoCommandException mongoCommandException) {
                    // ignore user already exists
                    if (mongoCommandException.getErrorCode() != 51003) {
                        throw mongoCommandException;
                    }

                }

            }
        }

    }

    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity
    @EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
    @ComponentScan("com.iqser.red.service.persistence")
    public static class TestConfiguration {

        @Bean
        public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {

            var mock = mock(RabbitListenerEndpointRegistry.class);
            when(mock.getListenerContainer(any())).thenReturn(mock(MessageListenerContainer.class));

            return mock;
        }


        @Bean
        public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {

            var allRoles = getAllRoles();
            UserDetails user1 = User.withUsername("manageradmin1@test.com")
                    .password(passwordEncoder.encode("secret")).roles(allRoles).authorities(getAllRoles()).build();
            UserDetails user2 = User.withUsername("manageradmin2@test.com")
                    .password(passwordEncoder.encode("secret")).roles(allRoles).authorities(getAllRoles()).build();
            var allRolesWithoutRedUserOrRedManager = Arrays.stream(allRoles)
                    .filter(s -> !(s.equalsIgnoreCase(ApplicationRoles.RED_USER_ROLE) || s.equalsIgnoreCase(ApplicationRoles.RED_MANAGER_ROLE)))
                    .collect(Collectors.toList());
            UserDetails userWithoutRedUserOrRedManager = User.withUsername("manageradmin4@test.com")
                    .password(passwordEncoder.encode("secret"))
                    .roles(allRolesWithoutRedUserOrRedManager.toArray(new String[0]))
                    .authorities(getAllRoles())
                    .build();
            return new InMemoryUserDetailsManager(user1,user2, userWithoutRedUserOrRedManager);
        }


        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

            http.authorizeHttpRequests().anyRequest().authenticated();
            http.httpBasic();
            http.csrf().disable();
            http.cors();
            return http.build();
        }


        @Bean
        public BCryptPasswordEncoder passwordEncoder() {

            return new BCryptPasswordEncoder();
        }


        @Bean
        @Primary
        public StorageService inmemoryStorage() {

            return new FileSystemBackedStorageService(ObjectMapperFactory.create());
        }

    }

}
