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.assertThrows;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

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

import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileProcessingClient;
import com.iqser.red.service.peristence.v1.server.integration.client.SupportClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatusFilter;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalysisSettings;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReanalyzeFilesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileErrorInfo;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;

import feign.FeignException;

public class SupportControllerTest extends AbstractPersistenceServerServiceTest {

    @Autowired
    private FileTesterAndProvider fileTesterAndProvider;

    @Autowired
    private DossierTesterAndProvider dossierTesterAndProvider;

    @Autowired
    private DossierClient dossierClient;

    @Autowired
    private SupportClient supportClient;

    @Autowired
    private FileClient fileClient;

    @Autowired
    private FileProcessingClient fileProcessingClient;

    @Autowired
    private FileStatusManagementService fileStatusManagementService;


    @Test
    public void testReanalysis() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier);

        var e = assertThrows(FeignException.class, () -> supportClient.reanalyzeErrorFilesBulkForDossier(dossier.getId(), List.of(file.getFileId()), true, false));
        assertThat(e.status()).isEqualTo(409);

        setProcessingStatusToError(dossier, file);

        supportClient.reanalyzeAllRelevantErrorFiles(true, false);
        var loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
        assertThat(loadedFile.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        setProcessingStatusToError(dossier, file);

        supportClient.reanalyzeErrorFilesBulkForDossier(dossier.getId(), List.of(), true, false);
        loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
        assertThat(loadedFile.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        setProcessingStatusToError(dossier, file);

        supportClient.reanalyzeErrorFilesBulkForDossier(dossier.getId(), List.of(file.getFileId()), true, false);
        loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
        assertThat(loadedFile.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        setProcessingStatusToError(dossier, file);

        setFileToStatusDeleted(file);
        e = assertThrows(FeignException.class, () -> supportClient.reanalyzeErrorFilesBulkForDossier(dossier.getId(), List.of(file.getFileId()), true, false));
        assertThat(e.status()).isEqualTo(400);

        setFileToStatusHardDeleted(file);
        e = assertThrows(FeignException.class, () -> supportClient.reanalyzeErrorFilesBulkForDossier(dossier.getId(), List.of(file.getFileId()), true, false));
        assertThat(e.status()).isEqualTo(400);

    }


    @Test
    public void testFileStatus() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier);

        setProcessingStatusToError(dossier, file);

        var fileStatuses = supportClient.getFileStatus(new FileStatusFilter());
        assertThat(fileStatuses.size()).isEqualTo(1);

        List<ProcessingStatus> errorProcessingStatusList = List.of(ProcessingStatus.ERROR);
        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(errorProcessingStatusList, new ArrayList<>()));
        assertThat(fileStatuses.size()).isEqualTo(1);

        List<ProcessingStatus> processedProcessingStatusList = List.of(ProcessingStatus.PROCESSED);
        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(processedProcessingStatusList, new ArrayList<>()));
        assertThat(fileStatuses.size()).isEqualTo(0);

        List<WorkflowStatus> newWorkflowStatusList = List.of(WorkflowStatus.NEW);
        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(new ArrayList<>(), newWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(1);

        List<WorkflowStatus> approvedWorkflowStatusList = List.of(WorkflowStatus.APPROVED);
        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(new ArrayList<>(), approvedWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(0);

        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(errorProcessingStatusList, approvedWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(0);

        fileStatuses = supportClient.getFileStatus(new FileStatusFilter(processedProcessingStatusList, newWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(0);

        fileStatuses = supportClient.exportFiles(file.getDossierId(), new FileStatusFilter(errorProcessingStatusList, newWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(1);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(errorProcessingStatusList, newWorkflowStatusList));
        assertThat(fileStatuses.size()).isEqualTo(1);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(new ArrayList<>(), new ArrayList<>()));
        assertThat(fileStatuses.size()).isEqualTo(1);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(null, null));
        assertThat(fileStatuses.size()).isEqualTo(1);

        setFileToStatusDeleted(file);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(null, null, false, true));
        assertThat(fileStatuses.size()).isEqualTo(0);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(null, null, true, false));
        assertThat(fileStatuses.size()).isEqualTo(1);

        setFileToStatusHardDeleted(file);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(null, null, true, false));
        assertThat(fileStatuses.size()).isEqualTo(0);

        fileStatuses = supportClient.getFileStatusForDossierTemplate(file.getDossierTemplateId(), new FileStatusFilter(null, null, false, true));
        assertThat(fileStatuses.size()).isEqualTo(1);

    }


    @Test
    public void testReanalysisWhenDossierDeleted() {

        var dossiers = dossierTesterAndProvider.provideTestDossiers(List.of("dossier1", "dossier2"));
        var dossier1 = dossiers.get(0);
        var dossier2 = dossiers.get(1);
        var file1 = fileTesterAndProvider.testAndProvideFile(dossier1, "file1");
        var file2 = fileTesterAndProvider.testAndProvideFile(dossier2, "file2");

        setProcessingStatusToError(dossier1, file1);
        setProcessingStatusToError(dossier2, file2);

        dossierClient.deleteDossier(dossier1.getId());

        supportClient.reanalyzeAllRelevantErrorFiles(false, false);
        FeignException feignException = assertThrows(FeignException.class, () -> fileClient.getFileStatus(dossier1.getId(), file1.getId()));
        assertEquals(feignException.status(), 404);
        FileStatus fileStatus = FileStatusMapper.toFileStatus(fileStatusManagementService.getFileStatus(file1.getId()));
        assertThat(fileStatus.getProcessingStatus()).isEqualTo(ProcessingStatus.PROCESSED);
        var loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        setProcessingStatusToError(dossier2, file2);

        supportClient.reanalyzeErrorFilesBulkForDossier(dossier2.getId(), List.of(), true, false);
        loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        feignException = assertThrows(FeignException.class, () -> supportClient.reanalyzeErrorFilesBulkForDossier(dossier1.getId(), List.of(), true, false));
        assertEquals(feignException.status(), 404);

        setProcessingStatusToError(dossier2, file2);

        ReanalyzeFilesResponse reanalyzeFilesResponse = supportClient.reanalyzeFiles(dossier1.getDossierTemplateId(),
                                                                                     new ReanalysisSettings(Set.of(dossier1.getId(), dossier2.getId()),
                                                                                                            Collections.emptySet(),
                                                                                                            true,
                                                                                                            false,
                                                                                                            new FileStatusFilter(null, null, true, true)));
        loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);
        assertEquals(reanalyzeFilesResponse.getRejectedFiles().size(), 1);
        assertEquals(reanalyzeFilesResponse.getRejectedFiles()
                             .get(0).getId(), file1.getId());

    }


    @Test
    public void testReanalysisWhenDossierArchived() {

        var dossiers = dossierTesterAndProvider.provideTestDossiers(List.of("dossier1", "dossier2"));
        var dossier1 = dossiers.get(0);
        var dossier2 = dossiers.get(1);
        var file1 = fileTesterAndProvider.testAndProvideFile(dossier1, "file1");
        var file2 = fileTesterAndProvider.testAndProvideFile(dossier2, "file2");

        setProcessingStatusToError(dossier1, file1);
        setProcessingStatusToError(dossier2, file2);

        dossierClient.archiveDossiers(Collections.singleton(dossier1.getId()));

        supportClient.reanalyzeAllRelevantErrorFiles(false, false);
        FileStatus fileStatus = fileClient.getFileStatus(dossier1.getId(), file1.getId());
        assertThat(fileStatus.getProcessingStatus()).isEqualTo(ProcessingStatus.ERROR);
        var loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        setProcessingStatusToError(dossier2, file2);

        supportClient.reanalyzeErrorFilesBulkForDossier(dossier2.getId(), List.of(), true, false);
        loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);

        FeignException feignException = assertThrows(FeignException.class, () -> supportClient.reanalyzeErrorFilesBulkForDossier(dossier1.getId(), List.of(), true, false));
        assertEquals(feignException.status(), 404);

        setProcessingStatusToError(dossier2, file2);

        ReanalyzeFilesResponse reanalyzeFilesResponse = supportClient.reanalyzeFiles(dossier1.getDossierTemplateId(),
                                                                                     new ReanalysisSettings(Set.of(dossier1.getId(), dossier2.getId()),
                                                                                                            Collections.emptySet(),
                                                                                                            true,
                                                                                                            false,
                                                                                                            null));
        loadedFile2 = fileClient.getFileStatus(dossier2.getId(), file2.getId());
        assertThat(loadedFile2.getProcessingStatus()).isEqualTo(ProcessingStatus.FULL_PROCESSING);
        assertEquals(reanalyzeFilesResponse.getRejectedFiles().size(), 1);
        assertEquals(reanalyzeFilesResponse.getRejectedFiles()
                             .get(0).getId(), file1.getId());

    }


    private void setFileToStatusHardDeleted(FileStatus file) {

        fileRepository.findById(file.getId())
                .ifPresent((f) -> {
                    f.setDeleted(null);
                    f.setHardDeletedTime(OffsetDateTime.ofInstant(Instant.now(), ZoneId.of("Z")));
                    fileRepository.saveAndFlush(f);
                });
    }


    private void setFileToStatusDeleted(FileStatus file) {

        fileRepository.findById(file.getId())
                .ifPresent((f) -> {
                    f.setDeleted(OffsetDateTime.ofInstant(Instant.now(), ZoneId.of("Z")));
                    f.setHardDeletedTime(null);
                    fileRepository.saveAndFlush(f);
                });
    }


    private void setProcessingStatusToError(Dossier dossier, FileStatus file) {

        fileProcessingClient.analysisFailed(dossier.getId(), file.getId(), new FileErrorInfo());
        var loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
        assertThat(loadedFile.getProcessingStatus()).isEqualTo(ProcessingStatus.ERROR);
    }

}
