/**
 * Copyright (c) 2025 SoftCorporation LLC. All rights reserved.
 *
 * The Software License, Version 1.0
 *
 * SoftCorporation LLC. grants you ("Licensee") a non-exclusive, royalty free,
 * license to use, modify and redistribute this software in source and binary
 * code form, provided that the following conditions are met:
 *
 * 1. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        SoftCorporation LLC. (http://www.softcorporation.com)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 2. This software and SoftCorporation name must not be used to
 *    promote products derived from the software without prior
 *    written permission. For written permission, please contact
 *    info@softcorporation.com.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 * IN NO EVENT SHALL THE SOFTCORPORATION BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION).
 *
 */
package com.softcorporation.sandi.search.service.solr;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest.METHOD;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.softcorporation.sandi.base.model.Client;
import com.softcorporation.sandi.base.service.solr.SolrService;
import com.softcorporation.sandi.base.util.ClientException;
import com.softcorporation.sandi.base.util.Constants;
import com.softcorporation.sandi.base.util.Utils;
import com.softcorporation.sandi.search.model.TypeAheadRequest;
import com.softcorporation.sandi.search.model.TypeAheadResponse;
import com.softcorporation.sandi.search.service.TypeAheadService;
import com.softcorporation.sandi.search.util.SearchException;

@Service
public class SolrTypeAheadService extends TypeAheadService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SolrTypeAheadService.class);

    private static final int MIN_RESULTS = 5;
    private static final int MAX_WORDS = 4;
    private static final int MAX_ROWS = 10;

    @Autowired
    private SolrService solrService;

    @Override
    public TypeAheadResponse search(TypeAheadRequest request) throws SearchException {
        long timeStart = System.currentTimeMillis();
        String requestId = request.getRequestId();
        String clientId = request.getClientId();
        String query = request.getQuery();

        if (request.isDebug()) {
            LOGGER.debug("[{}] Type ahead request: '{}' for client: {}", requestId, query, clientId);
        }

        try {
            String searchCollections = getCollections(request, clientId);

            TypeAheadResponse typeAheadResponse = new TypeAheadResponse();
            typeAheadResponse.setRequestId(requestId);
            typeAheadResponse.setStatus("OK");

            if (query != null && !query.isEmpty()) {
                String cleanQuery = clean(query).toLowerCase();
                String[] words = cleanQuery.trim().split("\\s+");

                int numWords = Math.min(MAX_WORDS, words.length);
                List<String> results = Collections.emptyList();

                // Try from max words down to 1, stopping as soon as we have enough results
                while (numWords >= 1) {
                    int fromIndex = words.length - numWords;
                    String[] slice = Arrays.copyOfRange(words, fromIndex, words.length);
                    String term = String.join("_", slice);
                    String solrQueryStr = buildEdgeQuery(term);

                    if (request.isDebug()) {
                        LOGGER.debug("[{}] TypeAhead Solr query ({} word(s)): {}", requestId, numWords, solrQueryStr);
                    }

                    SolrQuery solrQuery = new SolrQuery(solrQueryStr);
                    solrQuery.setRows(MAX_ROWS);
                    solrQuery.addSort(Constants.SCORE, SolrQuery.ORDER.desc);
                    solrQuery.setFields(Constants._ID, "count");

                    QueryResponse response = solrService.getSolrClient().query(searchCollections, solrQuery, METHOD.GET);
                    SolrDocumentList solrDocs = response.getResults();
                    List<String> hits = extractTexts(solrDocs);

                    // Prepend dropped prefix words so suggestions always reflect the full query
                    if (fromIndex > 0) {
                        String prefix = String.join(" ", Arrays.copyOfRange(words, 0, fromIndex)) + " ";
                        List<String> prefixed = new ArrayList<>(hits.size());
                        for (String hit : hits) {
                            prefixed.add(prefix + hit);
                        }
                        results = prefixed;
                    } else {
                        results = hits;
                    }

                    if (results.size() >= MIN_RESULTS) {
                        break;
                    }
                    numWords--;
                }

                typeAheadResponse.setResults(results);
            } else {
                typeAheadResponse.setResults(Collections.emptyList());
            }

            typeAheadResponse.setTook((int) (System.currentTimeMillis() - timeStart));
            return typeAheadResponse;

        } catch (Exception e) {
            LOGGER.error("[{}] Type Ahead search failed for query '{}': {}", requestId, query, e.getMessage());
            throw new SearchException(e.getMessage());
        }
    }

    /**
     * Builds a Solr edge-ngram query with fuzzy matching based on term length.
     * Fuzzy: ~2 if length > 10, ~1 if length > 5, none if length <= 5.
     */
    public String buildEdgeQuery(String term) {
        StringBuilder sb = new StringBuilder("text_edge:");
        sb.append(term);
        if (term.length() > 10) {
            sb.append("~2");
        } else if (term.length() > 5) {
            sb.append("~1");
        }
        sb.append(" AND _val_:count");
        return sb.toString();
    }

    public String getCollections(TypeAheadRequest request, String clientId) throws ClientException {
        StringBuilder sb = new StringBuilder();
        List<String> collections = Utils.stringToList(request.getSearchCollections(), false);
        if (collections == null) {
            Client client = request.getClient();
            if (client == null) {
                throw new ClientException("Unknown client " + clientId);
            }
            sb.append(Constants.TA_PREFIX);
            sb.append(client.getCollection());
        } else {
            for (String collection : collections) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append(Constants.TA_PREFIX);
                sb.append(collection);
            }
        }
        return sb.toString();
    }

    private List<String> extractTexts(SolrDocumentList docs) {
        List<String> texts = new ArrayList<>(docs.size());
        for (SolrDocument doc : docs) {
            Object text = doc.getFieldValue(Constants._ID);
            if (text != null) {
                texts.add(text.toString());
            }
        }
        return texts;
    }
}
