package com.knecon.fforesight.service.layoutparser.server;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ChangeType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;
import com.knecon.fforesight.service.layoutparser.processor.LayoutParsingPipeline;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.model.graph.nodes.SemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.table.TableServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.python_api.model.table.VisualLayoutParsingResponse;
import com.knecon.fforesight.service.layoutparser.processor.services.factory.DocumentGraphFactory;
import com.knecon.fforesight.tenantcommons.TenantsClient;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(HeadlinesGoldStandardIntegrationTest.RedactionIntegrationTestConfiguration.class)
public class HeadlinesGoldStandardIntegrationTest {

    @MockBean
    private TenantsClient tenantsClient;

    @MockBean
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private StorageService storageService;

    @Autowired
    private LayoutParsingPipeline layoutParsingPipeline;


    @Test
    public void testHeadlineDetection() {

        List<Metrics> metrics = new ArrayList<>();
        metrics.add(getMetrics("files/syngenta/CustomerFiles/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1).pdf",
                "files/headlineTest/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1)_REDACTION_LOG.json"));
        metrics.add(getMetrics("files/syngenta/CustomerFiles/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23.pdf",
                "files/headlineTest/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23_REDACTION_LOG.json"));
        metrics.add(getMetrics("files/syngenta/CustomerFiles/S-Metolachlor_RAR_01_Volume_1_2018-09-06.pdf", "files/headlineTest/S-Metolachlor_RAR_01_Volume_1_2018-09-06_REDACTION_LOG.json"));

        double precision = metrics.stream().mapToDouble(Metrics::getPrecision).average().orElse(1.0);
        double recall = metrics.stream().mapToDouble(Metrics::getRecall).average().orElse(1.0);

        System.out.println("Precision is: " + precision + " recall is: " + recall);

        Assertions.assertThat(precision).isGreaterThanOrEqualTo(0.44f);
        Assertions.assertThat(recall).isGreaterThanOrEqualTo(0.69f);
    }


    @SneakyThrows
    private Metrics getMetrics(String filePath, String redactionLogUrl) {

        ClassPathResource redactionLogResource = new ClassPathResource(redactionLogUrl);
        ClassPathResource pdfFileResource = new ClassPathResource(filePath);

        Set<Headline> goldStandardHeadlines = new HashSet<>();
        var goldStandardLog = objectMapper.readValue(redactionLogResource.getInputStream(), RedactionLog.class);
        goldStandardLog.getRedactionLogEntry().removeIf(r -> !r.isRedacted() || r.getChanges().get(r.getChanges().size() - 1).getType().equals(ChangeType.REMOVED));
        goldStandardLog.getRedactionLogEntry().forEach(e -> goldStandardHeadlines.add(new Headline(e.getPositions().get(0).getPage(), e.getValue())));

        Document documentGraph = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.DOCUMINE,
                layoutParsingPipeline.parseLayout(LayoutParsingType.DOCUMINE,
                        pdfFileResource.getFile(),
                        new ImageServiceResponse(),
                        new TableServiceResponse(),
                        new VisualLayoutParsingResponse(),
                        Map.of("file",filePath)));

        var foundHeadlines = documentGraph.streamAllSubNodes()
                .map(SemanticNode::getHeadline)
                .distinct()
                .map(headlineNode -> new Headline(headlineNode.getPages().stream().findFirst().get().getNumber(), headlineNode.getTextBlock().getSearchText().stripTrailing()))
                .toList();

        Set<Headline> correct = new HashSet<>();
        Set<Headline> missing;
        Set<Headline> falsePositive = new HashSet<>();
        for (Headline headline : foundHeadlines) {
            if (goldStandardHeadlines.contains(headline)) {
                correct.add(headline);
            } else {
                falsePositive.add(headline);
            }
        }

        missing = goldStandardHeadlines.stream().filter(h -> !correct.contains(h)).collect(Collectors.toSet());

        float precision = (float) correct.size() / (float) foundHeadlines.size();
        float recall = (float) correct.size() / ((float) correct.size() + (float) missing.size());

        return new Metrics(precision, recall);
    }


    @Configuration
    @EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class/*, StorageAutoConfiguration.class*/})
    @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})
    public static class RedactionIntegrationTestConfiguration {

    }

    @Data
    @EqualsAndHashCode
    @AllArgsConstructor
    @ToString
    private class Metrics {

        private float precision;
        private float recall;

    }

    @Data
    @EqualsAndHashCode
    @AllArgsConstructor
    @ToString
    @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName")
    private class Headline {

        private int page;
        private String headline;

    }

}
