/*
 * Copyright (c) 2025 Rostock University Library
 * Licensed under the MIT License. See LICENSE file in the project root.
 */
package de.uni.rostock.ub.wsrb.edition.webapp.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
import org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteSolrException;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.SolrPing;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SolrPingResponse;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.ContentStreamBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import de.uni.rostock.ub.wsrb.edition.webapp.util.SearchResult;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

@Service
public class SolrService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SolrService.class);
    private static final int DELAY_IN_S = 10;
    private static final int NUM_OF_RETRIES = 6;

    @Value("${wsrb.solr.url}")
    String solrURL;

    @Value("${wsrb.solr.core}")
    String solrCore;

    List<String> knownFilterKeys = List.of("expr", "illoc", "theme", "verse_count", "transcription");

    @Autowired
    XSLTEditionService xsltService;

    @Autowired
    ApplicationContext ctx;

    SolrClient solr;

    @PostConstruct
    private void init() {
        solr = new HttpJdkSolrClient.Builder(solrURL).withDefaultCollection(solrCore).build();
        waitForSolrStart();
        reloadWSRBSolrData();
    }

    @PreDestroy()
    private void stop() {
        try {
            solr.close();
        } catch (Exception e) {
            LOGGER.error("Solr Error", e);
        }
    }

    public List<String> retrieveAllEditionIDs() {
        List<String> result = new ArrayList<>();

        SolrQuery query = new SolrQuery();
        query.setQuery("doctype:edition_texteinheit");
        query.setFields("id");
        query.setRows(1000);

        QueryResponse response;
        try {
            response = solr.query(query);
            SolrDocumentList docList = response.getResults();
            for (SolrDocument doc : docList) {
                result.add(String.valueOf(doc.getFieldValue("id")));
            }
        } catch (SolrServerException | IOException e) {
            LOGGER.error("Solr error", e);
        }
        return result;
    }

    public void reloadWSRBSolrData() {
        String xml = xsltService.createSolr4Edition();
        ContentStreamUpdateRequest updateRequest = new ContentStreamUpdateRequest("/update");
        updateRequest.addContentStream(new ContentStreamBase.StringStream(xml, "application/xml"));

        try {
            solr.request(updateRequest);
            solr.commit();
        } catch (SolrServerException | IOException e) {
            LOGGER.error("Solr Error", e);
        }
    }

    //TODO fix "parameter injection"
    //TODO Verhalten bei ID als URL-Param -> Filter und Suchen ignorieren
    //--> dann muss aber das Template angepasst werden und kann nicht mehr mit den Query-Parametern arbeiten
    public SearchResult searchEdition(List<String> id, List<String> filter,
        String searchRegion, String searchTerm) {
        StringBuffer sbQuery = new StringBuffer();

        final SolrQuery query = buildSolrQuery(id, filter, searchRegion, searchTerm, sbQuery);
        String highlightField = query.getHighlightFields() != null && query.getHighlightFields().length > 0
            ? query.getHighlightFields()[0] : null;

        List<String> result = new ArrayList<>();
        Map<String, Long> facets = Map.of();
        Map<String, String> highlightResult = new ConcurrentHashMap<>();
        try {
            QueryResponse qresp = solr.query(query);
            SolrDocumentList docs = qresp.getResults();

            FacetField ff = qresp.getFacetField("filter");
            facets = ff.getValues().stream().collect(Collectors.toUnmodifiableMap(Count::getName, Count::getCount));

            //Highlighting - TODO "flatten" map with Java stream api
            Map<String, Map<String, List<String>>> highlighting = qresp.getHighlighting();
            if (highlighting != null) {
                for (String key : highlighting.keySet()) {
                    if (highlighting.get(key).get(highlightField) != null) {
                        highlightResult.put(key, highlighting.get(key).get(highlightField).get(0));
                    }
                }
            }
            for (SolrDocument d : docs) {
                result.add(String.valueOf(d.getFieldValue("id")));
            }
        } catch (IOException | SolrServerException e) {
            LOGGER.warn(e.getMessage());
        }
        return new SearchResult(result, facets, highlightResult);
    }

    private SolrQuery buildSolrQuery(List<String> id, List<String> filter, String searchRegion, String searchTerm,
        StringBuffer sbQuery) {
        String region = switch (searchRegion.trim()) {
            case "edition" -> "text";
            case "translation" -> "translation";
            case "apparat" -> "apparat";
            default -> "text";
        };

        if (searchTerm.isBlank()) {
            sbQuery.append('*');
        } else {
            sbQuery.append(searchTerm.trim().replace("ODER", "OR").replace("UND", "AND"));
        }

        final SolrQuery query = new SolrQuery(sbQuery.toString());
        String field = "e__" + region;
        query.set("df", field);
        query.set("q.op", "AND");

        addFilterAndHighlighting(query, id, filter, field);

        query.addField("id");
        query.setRows(1000);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(query.toQueryString());
        }
        return query;
    }

    private void addFilterAndHighlighting(final SolrQuery query,
        List<String> id, List<String> filter, String highlightField) {
        //filter:
        if (!id.isEmpty()) {
            query.addFilterQuery("id:(" + String.join(" OR ", id) + ")");
        }

        Map<String, List<String>> filterAsMap =
            filter.stream().collect(Collectors.groupingBy(x -> x.split("\\.").length > 2 ? x.split("\\.")[1] : x));
        List<String> validKeys = filterAsMap.keySet().stream().filter(knownFilterKeys::contains).toList();
        for (String key : validKeys) {
            String joinOp = "transcription".equals(key) ? " AND " : " OR ";
            query.addFilterQuery("filter:(" + String.join(joinOp, filterAsMap.get(key)) + ")");
        }

        query.addFacetField("filter");

        if (highlightField != null) {
            query.setHighlight(true);
            query.addHighlightField(highlightField);
            query.setHighlightFragsize(64);
        }
    }

    @SuppressWarnings("PMD.DoNotTerminateVM")
    private void waitForSolrStart() {
        for (int i = 0; i <= NUM_OF_RETRIES; i++) {
            try {
                TimeUnit.SECONDS.sleep(i * DELAY_IN_S);
                SolrPing ping = new SolrPing();
                SolrPingResponse rsp = ping.process(solr);
                int status = rsp.getStatus();
                if (status == 0) {
                    LOGGER.info("Solr is up and running.");
                    return;
                }
                LOGGER.info("Solr Ping Response Status: " + status);
            } catch (SolrServerException | RemoteSolrException | IOException e) {
                LOGGER.warn("Solr not running: " + e.getMessage());
            } catch (InterruptedException ie) {
                LOGGER.error("Sleep interrupted", ie);
            }
        }

        //could not access solr - stop the application
        LOGGER.error("SHUTTING DOWN the WSRB web appication --- Could not find the Solr server!");
        System.exit(SpringApplication.exit(ctx, new ExitCodeGenerator() {
            @Override
            public int getExitCode() {
                return 0;
            }
        }));
    }
}
