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

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

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

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
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.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.SneakyThrows;

public class EntityLogMongoServiceTest extends AbstractPersistenceServerServiceTest {

    @Autowired
    private EntityLogMongoService entityLogMongoService;

    private final String ENTITY_LOG1 = "files/entity-log/b2cbdd4dca0aa1aa0ebbfc5cc1462df0.ENTITY_LOG.json";
    private final String ENTITY_LOG2 = "files/entity-log/6f2a9d4b5d4e11211fc0d7732fd45b78.ENTITY_LOG.json";
    private final String ENTITY_LOG3_BEFORE = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-before.ENTITY_LOG.json";
    private final String ENTITY_LOG3_AFTER = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-after.ENTITY_LOG.json";
    private final String ENTITY_LOG_LOCAL_BULK = "files/entity-log/local-bulk-test.ENTITY_LOG.json";
    private final String ENTITY_LOG_WITH_CHANGES = "files/entity-log/entitylog-with-changes.json";

    private static final String TEST_DOSSIER_ID = "91ce8e90-9aec-473c-b8c3-cbe16443ad34";
    private static final String TEST_FILE1_ID = "b2cbdd4dca0aa1aa0ebbfc5cc1462df0";
    private static final String TEST_FILE2_ID = "6f2a9d4b5d4e11211fc0d7732fd45b78";
    private static final String TEST_FILE3_ID = "c3b23116f2d277170d303bfc6fb82e6a";


    @Test
    @SneakyThrows
    public void testMultiTenantInserts() {

        var file = new ClassPathResource(String.format(ENTITY_LOG1));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLogToStore);

        var optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
        assertTrue(optionalEntityLog.isPresent());

        EntityLog entityLogFromStorage = optionalEntityLog.get();

        assertEquals(entityLogFromStorage, entityLogToStore);

        TenantContext.setTenantId("redaction2");

        optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
        assertTrue(optionalEntityLog.isEmpty());

        file = new ClassPathResource(String.format(ENTITY_LOG2));
        entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE2_ID, entityLogToStore);

        optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE2_ID);
        assertTrue(optionalEntityLog.isPresent());

        entityLogFromStorage = optionalEntityLog.get();

        assertEquals(entityLogFromStorage, entityLogToStore);

        TenantContext.setTenantId("redaction");

        optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE2_ID);
        assertTrue(optionalEntityLog.isEmpty());
    }


    @Test
    @SneakyThrows
    public void testInsertAndDeleteEntityLogDocument() {

        var file = new ClassPathResource(String.format(ENTITY_LOG1));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLogToStore);

        var optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
        assertTrue(optionalEntityLog.isPresent());

        EntityLog entityLogFromStorage = optionalEntityLog.get();

        assertEquals(entityLogFromStorage, entityLogToStore);

        entityLogMongoService.deleteEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID);

        optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
        assertTrue(optionalEntityLog.isEmpty());
    }


    @Test
    @SneakyThrows
    public void testFindAndUpdateEntityLogDocument() {

        var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);

        Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1706);
        assertEquals(entityLogMongoService.findLatestAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID)
                             .orElse(-1), 8);

        var latest = entityLogMongoService.findLatestAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID)
                .orElseThrow();
        var latestChangedEntries = entityLogMongoService.findEntityLogEntriesByAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID, latest);
        assertEquals(latestChangedEntries.size(), 490);

        var manuallyChangedEntries = entityLogMongoService.findEntityLogEntriesWithManualChanges(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertEquals(manuallyChangedEntries.size(), 1014);

        String NEW_ENTITY_LOG_ENTRY = """
                {
                    "id": "05240998f2e657d739d59d220094702a",
                    "type": "CBI_author",
                    "entryType": "ENTITY",
                    "state": "REMOVED",
                    "value": "Amato",
                    "reason": "Author found",
                    "matchedRule": "CBI.0.0",
                    "legalBasis": "Article 39(e)(3) of Regulation (EC) No 178/2002",
                    "imported": false,
                    "containingNodeId": [
                        1,
                        0
                    ],
                    "closestHeadline": "",
                    "section": "[1, 0]: Paragraph: Almond R.H. Almond Richard",
                    "color": null,
                    "positions": [
                        {
                            "rectangle": [
                                56.8,
                                639.3,
                                32.59199,
                                12.642
                            ],
                            "pageNumber": 2
                        }
                    ],
                    "textBefore": "S. Amarasekare Amarasingham ",
                    "textAfter": " Amato S.L Amato",
                    "startOffset": 5137,
                    "endOffset": 5142,
                    "imageHasTransparency": false,
                    "dictionaryEntry": true,
                    "dossierDictionaryEntry": false,
                    "excluded": false,
                    "changes": [
                        {
                            "analysisNumber": 1,
                            "type": "ADDED",
                            "dateTime": "2024-03-13T08:44:54.811245839Z"
                        },
                        {
                            "analysisNumber": 3,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T08:47:11.339124572Z"
                        },
                        {
                            "analysisNumber": 4,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T08:47:19.694336707Z"
                        },
                        {
                            "analysisNumber": 5,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T08:48:21.670287664Z"
                        },
                        {
                            "analysisNumber": 6,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T08:58:41.082712591Z"
                        },
                        {
                            "analysisNumber": 7,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T08:59:31.444615299Z"
                        },
                        {
                            "analysisNumber": 8,
                            "type": "REMOVED",
                            "dateTime": "2024-03-13T09:05:22.006293189Z"
                        }
                    ],
                    "manualChanges": [],
                    "engines": [
                        "DICTIONARY"
                    ],
                    "reference": [],
                    "importedRedactionIntersections": [],
                    "numberOfComments": 0
                }
                """;

        EntityLogEntry entityLogEntry = objectMapper.readValue(NEW_ENTITY_LOG_ENTRY, EntityLogEntry.class);

        entityLogMongoService.saveEntityLogEntries(TEST_DOSSIER_ID, TEST_FILE3_ID, List.of(entityLogEntry));

        found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1707);

    }


    @Test
    @SneakyThrows
    public void testUpdateEntityLogDocumentByOverride() {

        var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);

        Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1706);

        file = new ClassPathResource(ENTITY_LOG3_AFTER);

        entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
        entityLog.setAnalysisNumber(entityLog.getAnalysisNumber() + 1);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);

        found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1707);
        assertEquals(found.get().getAnalysisNumber(), 9);

    }


    @Test
    @SneakyThrows
    public void testUpdateEntityLogWithoutEntries() {

        var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);

        Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1706);

        file = new ClassPathResource(ENTITY_LOG3_AFTER);

        entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
        entityLog.setAnalysisNumber(entityLog.getAnalysisNumber() + 1);

        entityLogMongoService.saveEntityLogWithoutEntries(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);

        found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
        assertTrue(found.isPresent());
        assertEquals(found.get().getEntityLogEntry().size(), 1706);
        assertEquals(found.get().getAnalysisNumber(), 9);

    }


    @Test
    @SneakyThrows
    public void testGetWithNullExcludedTypes() {

        var file = new ClassPathResource(String.format(ENTITY_LOG1));
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLogToStore);

        var optionalEntityLog = entityLogMongoService.findEntityLogWithoutExcludedEntryTypes(TEST_DOSSIER_ID, TEST_FILE1_ID, null);
        assertTrue(optionalEntityLog.isPresent());

    }


    @Test
    @SneakyThrows
    public void testGetEntityLogDeltaUpdate() {

        var file = new ClassPathResource(ENTITY_LOG_WITH_CHANGES);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLog);

        Optional<EntityLog> optionalEntityLog = entityLogMongoService.findEntityLogWithEntriesSinceAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE1_ID, 2);
        assertTrue(optionalEntityLog.isPresent());

        EntityLog response = optionalEntityLog.get();
        assertEquals(response.getEntityLogEntry().size(), 14);
    }


    @Test
    @SneakyThrows
    public void testEntityLogMongoServiceMethodsForLocalBulk() {

        var file = new ClassPathResource(ENTITY_LOG_LOCAL_BULK);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);

        entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLog);

        String akessonValue = "Akesson";
        float[] akessonRectangle = {322.63608f, 308.1f, 41.375977f, 12.642f};

        Set<String> akessonSetFilterQuery = entityLogMongoService.findEntryIdsByMatchingFullPositionWithFilters(TEST_DOSSIER_ID,
                                                                                                                TEST_FILE1_ID,
                                                                                                                akessonRectangle,
                                                                                                                Collections.emptySet(),
                                                                                                                Collections.emptySet(),
                                                                                                                Collections.emptySet());
        assertEquals(akessonSetFilterQuery.size(), 3);

        Set<String> akessonSetFilterQueryByPositions = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                                            TEST_FILE1_ID,
                                                                                                            akessonValue,
                                                                                                            false,
                                                                                                            Collections.emptySet(),
                                                                                                            Collections.emptySet(),
                                                                                                            Collections.emptySet());
        assertEquals(akessonSetFilterQueryByPositions.size(), 3);

        akessonSetFilterQuery = entityLogMongoService.findEntryIdsByMatchingFullPositionWithFilters(TEST_DOSSIER_ID,
                                                                                                    TEST_FILE1_ID,
                                                                                                    akessonRectangle,
                                                                                                    Collections.emptySet(),
                                                                                                    Collections.emptySet(),
                                                                                                    Set.of(1, 3));
        assertEquals(akessonSetFilterQuery.size(), 2);

        akessonSetFilterQueryByPositions = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                                TEST_FILE1_ID,
                                                                                                akessonValue,
                                                                                                false,
                                                                                                Collections.emptySet(),
                                                                                                Collections.emptySet(),
                                                                                                Set.of(1, 3));
        assertEquals(akessonSetFilterQueryByPositions.size(), 2);

        String baldridgeValue = "Baldridge";
        Set<String> baldridgeSet = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                        TEST_FILE1_ID,
                                                                                        baldridgeValue,
                                                                                        false,
                                                                                        Collections.emptySet(),
                                                                                        Collections.emptySet(),
                                                                                        Collections.emptySet());
        assertEquals(baldridgeSet.size(), 4);

        Set<String> baldridgeSetWithTypeFilter = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                                      TEST_FILE1_ID,
                                                                                                      baldridgeValue,
                                                                                                      false,
                                                                                                      Set.of("CBI_author", "Test"),
                                                                                                      Collections.emptySet(),
                                                                                                      Collections.emptySet());
        assertEquals(baldridgeSetWithTypeFilter.size(), 2);

        Set<String> baldridgeSetWithLegalBasesFilter = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                                            TEST_FILE1_ID,
                                                                                                            baldridgeValue,
                                                                                                            false,
                                                                                                            Set.of("CBI_author", "Test"),
                                                                                                            Set.of("Test"),
                                                                                                            Collections.emptySet());
        assertEquals(baldridgeSetWithLegalBasesFilter.size(), 1);

        String albaValueCapitalA = "Alba";
        Set<String> albaSet = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                                   TEST_FILE1_ID,
                                                                                   albaValueCapitalA,
                                                                                   true,
                                                                                   Collections.emptySet(),
                                                                                   Collections.emptySet(),
                                                                                   Collections.emptySet());
        assertEquals(albaSet.size(), 4);

        albaSet = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                       TEST_FILE1_ID,
                                                                       albaValueCapitalA,
                                                                       false,
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet());
        assertEquals(albaSet.size(), 4);

        String albaValue = "alba";
        albaSet = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                       TEST_FILE1_ID,
                                                                       albaValue,
                                                                       false,
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet());
        assertEquals(albaSet.size(), 4);

        albaSet = entityLogMongoService.findEntryIdsByValueWithFilters(TEST_DOSSIER_ID,
                                                                       TEST_FILE1_ID,
                                                                       albaValue,
                                                                       true,
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet(),
                                                                       Collections.emptySet());
        assertEquals(albaSet.size(), 0);

    }

}
