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

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.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.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.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.*;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.*;
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.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.databasetenantcommons.providers.TenantCreatedListener;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantsClient;
import com.knecon.fforesight.tenantcommons.model.*;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
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;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
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.*;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.mockito.Mockito.when;

@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 {



    @MockBean
    protected Scheduler scheduler;
    @MockBean
    protected RabbitTemplate rabbitTemplate;
    @MockBean
    protected SearchClient searchClient;
    @MockBean
    protected RedactionLogService redactionLogService;
    @MockBean
    protected PDFTronClient pdfTronRedactionClient;
    @Autowired
    protected ApplicationConfigClient appConfigClient;
    @Autowired
    protected StorageService storageService;
    @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
    private RedactionClient redactionClient;
    @Autowired
    protected PrometheusMeterRegistry prometheusMeterRegistry;
    @MockBean
    private AmqpAdmin amqpAdmin;
    @Autowired
    private DossierStatusRepository dossierStatusRepository;
    @Autowired
    private ApplicationConfigService applicationConfigService;
    @Autowired
    protected UserService userService;
    @Autowired
    private TokenService tokenService;
    @MockBean
    private TenantsClient tenantsClient;
    @MockBean
    private UsersClient usersClient;
    @Autowired
    private EncryptionDecryptionService encryptionDecryptionService;
    @Autowired
    private TenantCreatedListener tenantCreatedListener;


    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("redaction");
    }


    @AfterEach
    public void clearTenantContext() {

        TenantContext.clear();
    }


    @BeforeEach
    public void setupOptimize() {

        createDefaultTenant();

        TenantContext.setTenantId("redaction");

        ApplicationConfig appConfig = ApplicationConfig.builder().downloadCleanupDownloadFilesHours(8).downloadCleanupNotDownloadFilesHours(72).softDeleteCleanupTime(96).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());
        when(usersClient.getAllUsers(false)).thenReturn(allUsers);
        when(usersClient.getAllUsers(true)).thenReturn(allUsers);
        // doNothing().when(pdfTronRedactionClient).testDigitalCurrentSignature(Mockito.any());

        when(amqpAdmin.getQueueInfo(Mockito.any())).thenReturn(null);
        when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(new RedactionLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0));
        when(redactionClient.testRules(Mockito.any())).thenReturn(DroolsSyntaxValidation.builder().droolsSyntaxErrorMessages(Collections.emptyList()).build());
    }


    private void createDefaultTenant() {

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

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

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

            var redactionTenant = new TenantResponse();
            redactionTenant.setTenantId("redaction");
            redactionTenant.setGuid("redaction");
            redactionTenant.setDisplayName("redaction");
            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());
            when(tenantsClient.getTenant("redaction")).thenReturn(redactionTenant);
            when(tenantsClient.getTenants()).thenReturn(List.of(redactionTenant));

            try {
                tenantCreatedListener.createTenant(new TenantCreatedEvent("redaction"));
            } 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());

            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();

        });
    }


    @Slf4j
    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

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

            postgreSQLContainerMaster.start();

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

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

            TestPropertyValues.of("REDIS_PORT=" + redisContainer.getFirstMappedPort(),
                    "REDIS_HOST=" + redisContainer.getHost(),
                    "fforesight.jobs.enabled=false",
                    "fforesight.keycloak.enabled=false").applyTo(configurableApplicationContext.getEnvironment());

        }

    }

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

        @Bean
        public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {

            UserDetails user = User.withUsername("manageradmin1@test.com").password(passwordEncoder.encode("secret")).roles(getAllRoles()).authorities(getAllRoles()).build();
            return new InMemoryUserDetailsManager(user);
        }


        @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();
        }

    }

}
