package cz.inqool.dl4dh.krameriusplus.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cz.inqool.dl4dh.krameriusplus.SolrPreAnalyzedField;
import cz.inqool.dl4dh.krameriusplus.domain.entity.Publication;
import cz.inqool.dl4dh.krameriusplus.domain.entity.monograph.Monograph;
import cz.inqool.dl4dh.krameriusplus.domain.entity.monograph.MonographUnit;
import cz.inqool.dl4dh.krameriusplus.domain.entity.page.NameTagMetadata;
import cz.inqool.dl4dh.krameriusplus.domain.entity.page.NamedEntity;
import cz.inqool.dl4dh.krameriusplus.domain.entity.page.Page;
import cz.inqool.dl4dh.krameriusplus.domain.entity.page.Token;
import cz.inqool.dl4dh.krameriusplus.service.filler.PublicationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cz.inqool.dl4dh.krameriusplus.domain.enums.NamedEntityType.GEOGRAPHICAL_NAMES;
import static cz.inqool.dl4dh.krameriusplus.domain.enums.NamedEntityType.PERSONAL_NAMES;

/**
 * @author Norbert Bodnar
 *
 * TODO: only for Solr, delete later
 */
@Service
@Slf4j
@ConditionalOnProperty(prefix = "solr", value = "enabled", havingValue = "true")
public class SolrService {

    private final PublicationService publicationService;

    private final SolrClient solrClient;

    private final ObjectMapper mapper;

    @Autowired
    public SolrService(PublicationService publicationService, ObjectMapper objectMapper) {
        this.publicationService = publicationService;
        this.mapper = objectMapper;
        List<String> solrUrls = new ArrayList<>();

        solrUrls.add("http://localhost:8983/solr");

        this.solrClient = new CloudSolrClient.Builder(solrUrls).build();
    }

    @Async
    public void indexAsync(String publicationId) {
        index(publicationId);
    }

    public void index(String publicationId) {
        Publication publication = publicationService.findWithChildren(publicationId);
        log.info("Indexing publication with PID=" + publicationId + ", " + publication.getTitle());

        if (publication instanceof Monograph) {
            indexMonograph((Monograph) publication);
        } else if (publication instanceof MonographUnit) {
            indexMonographUnit((MonographUnit) publication);
        } else {
            log.error("Error when indexing publication " + publication.getTitle());
            throw new IllegalArgumentException("Publication of type " + publication.getModel() + " not supported yet.");
        }
        log.info("Indexing of publication " + publication.getTitle() + " finished.");
    }

    private void indexMonograph(Monograph monograph) {
        if (monograph.getPages() != null && !monograph.getPages().isEmpty()) {
            Map<String, String> attributes = new HashMap<>();
            attributes.put("root_id", monograph.getPid());
            attributes.put("root_title", monograph.getTitle());
            indexPages(monograph.getPages(), attributes);
        } else {
            for (MonographUnit monographUnit : monograph.getMonographUnits()) {
                indexMonographUnit(monographUnit);
            }
        }
    }

    private void indexMonographUnit(MonographUnit monographUnit) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put("root_id", monographUnit.getPid());
        attributes.put("root_title", monographUnit.getTitle());
        indexPages(monographUnit.getPages(), attributes);
    }

    private void indexPages(List<Page> pages, Map<String, String> rootAttributes) {
        List<SolrInputDocument> solrInputDocuments = new ArrayList<>();
        SolrInputDocument document;
        try {
            for (Page page : pages) {
                document = new SolrInputDocument();

                document.addField("id", page.getPid());
                document.addField("title", page.getTitle());
                document.addField("lemma", toString(toPreAnalyzedField(page.getTokens())));
                document.addField("root_id", rootAttributes.get("root_id"));
                document.addField("root_title", rootAttributes.get("root_title"));

                NameTagMetadata nameTagMetadata = page.getNameTagMetadata();
                if (nameTagMetadata != null) {
                    List<NamedEntity> personalNames = nameTagMetadata.getNamedEntities().get(PERSONAL_NAMES);
                    if (personalNames != null) {
                        List<SolrPreAnalyzedField> preAnalyzedFields =
                                personalNames
                                        .stream()
                                        .map(personalName -> toPreAnalyzedField(personalName.getTokens()))
                                        .collect(Collectors.toList());

                        document.addField("nameTag.personalNames.search",
                                preAnalyzedFields
                                        .stream()
                                        .map(this::toString)
                                        .collect(Collectors.toList()));

                        document.addField("nameTag.personalNames.facet",
                                preAnalyzedFields
                                        .stream()
                                        .map(SolrPreAnalyzedField::getStoredValue)
                                        .collect(Collectors.toList()));
                    }

                    List<NamedEntity> geographicalNames = nameTagMetadata.getNamedEntities().get(GEOGRAPHICAL_NAMES);
                    if (geographicalNames != null) {
                        List<SolrPreAnalyzedField> preAnalyzedFields =
                                geographicalNames
                                        .stream()
                                        .map(geoName -> toPreAnalyzedField(geoName.getTokens()))
                                        .collect(Collectors.toList());

                        document.addField("nameTag.geographicalNames.search",
                                preAnalyzedFields
                                        .stream()
                                        .map(this::toString)
                                        .collect(Collectors.toList()));

                        document.addField("nameTag.geographicalNames.facet",
                                preAnalyzedFields
                                        .stream()
                                        .map(SolrPreAnalyzedField::getStoredValue)
                                        .collect(Collectors.toList()));
                    }
                }
                solrInputDocuments.add(document);
            }

            solrClient.add("kramerius_plus", solrInputDocuments);
            solrClient.commit("kramerius_plus");
        } catch (JsonProcessingException e) {
            log.error("Error processing json", e);
        } catch (SolrServerException | IOException e) {
            log.error("Solr error", e);
        }
    }

    private SolrPreAnalyzedField toPreAnalyzedField(List<Token> tokens) {
        return new SolrPreAnalyzedField(tokens);
    }

    private String toString(SolrPreAnalyzedField preAnalyzedField) {
        try {
            return mapper.writeValueAsString(preAnalyzedField);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("This error should not happen according to the documentation of ObjectMapper");
        }
    }
}
