/**
 * 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.base.service.solr;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
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.stereotype.Service;

import com.softcorporation.sandi.base.model.Client;
import com.softcorporation.sandi.base.model.Collection;
import com.softcorporation.sandi.base.service.ClientService;
import com.softcorporation.sandi.base.util.ClientException;
import com.softcorporation.sandi.base.util.CollectionException;
import com.softcorporation.sandi.base.util.Constants;
import com.softcorporation.util.StringUtil;

import jakarta.annotation.PostConstruct;

@Service
public class SolrClientService implements ClientService {

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

    /**
     * Create collections
     */
    @Value("${sandi.collections.create}")
    private boolean createColls; 
    
    @Autowired
    private SolrService solrService;

    @Autowired
    private SolrCollectionService collectionService;

    @PostConstruct
    public void init() {
        LOGGER.debug("Initializing ClientService");
        if (createColls) {
            try {
            	if (!collectionService.hasCollection(Constants.CLIENTS_COLLECTION)) {
            		Collection clientsCollection = new Collection(Constants.CLIENTS_COLLECTION, Constants.CLIENTS_CONFIGURATION);
            		collectionService.createCollection(clientsCollection);
            	}
            } catch (Exception e) {
                LOGGER.error("Failed to initialize ClientService: {}", e.getMessage(), e);
            }
        }
    }

    @Override
    public List<Client> listClients() throws ClientException {
        LOGGER.debug("Retrieving all clients from Solr collection: {}", Constants.CLIENTS_COLLECTION);
        
        try {
            SolrQuery query = new SolrQuery("*:*");
            query.setRows(1000); // Assuming max 1000 clients
            
            QueryResponse response = solrService.getSolrClient().query(Constants.CLIENTS_COLLECTION, query);
            SolrDocumentList results = response.getResults();
            
            List<Client> clients = new ArrayList<>();
            for (SolrDocument doc : results) {
                Client client = solrDocumentToClient(doc);
                if (client != null) {
                    clients.add(client);
                }
            }
            
            LOGGER.debug("Retrieved {} clients from Solr", clients.size());
            return clients;
            
        } catch (SolrServerException e) {
            LOGGER.error("Failed to retrieve clients from Solr: {}", e.getMessage(), e);
            solrService.reset();
            throw new ClientException("Failed to retrieve clients", e);
        } catch (IOException e) {
            LOGGER.error("Failed to retrieve clients from Solr: {}", e.getMessage(), e);
            throw new ClientException("Failed to retrieve clients", e);
		}
    }

    @Override
    public Client getClient(String clientId) throws ClientException {
        LOGGER.debug("Looking for client with ID: {} in Solr collection: {}", clientId, Constants.CLIENTS_COLLECTION);
        
        try {
            SolrQuery query = new SolrQuery();
            query.setQuery("clientId:\"" + clientId + "\"");
            query.setRows(1);
            
            QueryResponse response = solrService.getSolrClient().query(Constants.CLIENTS_COLLECTION, query);
            SolrDocumentList results = response.getResults();
            
            if (results.size() > 0) {
                Client client = solrDocumentToClient(results.get(0));
                if (client != null) {
                    LOGGER.debug("Found client: {} ({})", client.getName(), clientId);
                    return client;
                }
            }
            
            LOGGER.debug("Client not found with ID: {}", clientId);
            return null;
            
        } catch (SolrServerException e) {
            LOGGER.error("Failed to get client by ID {}: {}", clientId, e.getMessage(), e);
            solrService.reset();
            throw new ClientException("Failed to get client", e);
        } catch (IOException e) {
            LOGGER.error("Failed to get client by ID {}: {}", clientId, e.getMessage(), e);
            throw new ClientException("Failed to get client", e);
        }
    }
    
    @Override
    public void saveClient(final Client client) throws ClientException {
    	LOGGER.debug("Processing save client to Solr: {}", client);

    	validateClient(client);
        
        try {
            // Check if client exists
            Client existingClient = getClient(client.getClientId());
            if (existingClient == null) {

            	if (client.getCollection() == null) {
            		client.setCollection(Constants.DEFAULT_COLLECTION);
            	}
            	if (!collectionService.hasCollection(client.getCollection())) {
                    throw new ClientException("Collection " + client.getCollection() + " does not exist");
            	}
                LOGGER.info("Adding new client: {} ({})", client.getName(), client.getClientId());

            } else {
            	if (client.getCollection() == null) {
                	if (existingClient.getCollection() == null) {
                		client.setCollection(Constants.DEFAULT_COLLECTION);
                	} else {
                		client.setCollection(existingClient.getCollection());
                	}
            	} else {
                	if (!client.getCollection().equals(existingClient.getCollection())) {
                        LOGGER.info("Changed client {} collection from {} to {}", client.getClientId(),
                        		existingClient.getCollection(), client.getCollection());
                	}
            	}            	
            	if (!collectionService.hasCollection(client.getCollection())) {
                    throw new ClientException("Collection " + client.getCollection() + " does not exist");
            	}
                LOGGER.info("Updating existing client: {} ({})", client.getName(), client.getClientId());
            }
            SolrInputDocument doc = clientToSolrDocument(client);
            solrService.getSolrClient().add(Constants.CLIENTS_COLLECTION, doc);
            solrService.getSolrClient().commit(Constants.CLIENTS_COLLECTION);
        	
            LOGGER.info("Saved client to Solr: {} ({})", client.getName(), client.getClientId());

        } catch (Exception e) {
            LOGGER.error("Failed to save client to Solr: {} ({}), error: {}", 
                        client.getName(), client.getClientId(), e.getMessage(), e);
            throw new ClientException(e.getMessage(), e);
        }
    }

    @Override
    public void saveClient(final Client client, final String configuration) throws ClientException, CollectionException {
    	LOGGER.debug("Saving client to Solr: {} with new collection configuration: {}", 
                   client, configuration);
    	validateClient(client);
		Collection clientsCollection = new Collection(client.getCollection(), configuration);
		collectionService.createCollection(clientsCollection);
		saveClient(client);
    }

    @Override
    public boolean deleteClient(String clientId) throws ClientException {
        LOGGER.debug("Attempting to delete client with ID: {} from Solr collection: {}", clientId, Constants.CLIENTS_COLLECTION);
        
        try {
            // Check if client exists first
            Client existingClient = getClient(clientId);
            if (existingClient == null) {
                LOGGER.warn("Client not found for deletion with ID: {}", clientId);
                return false;
            }
            
            solrService.getSolrClient().deleteByQuery(Constants.CLIENTS_COLLECTION, "clientId:\"" + clientId + "\"");
            solrService.getSolrClient().commit(Constants.CLIENTS_COLLECTION);
            
            LOGGER.info("Successfully deleted client with ID: {} from Solr", clientId);
            return true;
            
        } catch (Exception e) {
            LOGGER.error("Failed to delete client with ID {}: {}", clientId, e.getMessage(), e);
            throw new ClientException("Failed to delete client", e);
        }
    }

    /**
     * Converts Solr document to Client object
     */
    private Client solrDocumentToClient(SolrDocument doc) {
        Client client = new Client();
        client.setClientId(solrService.getString(doc, Constants.FIELD_CLIENT_ID));
        client.setName(solrService.getString(doc, Constants.FIELD_NAME));
        client.setCollection(solrService.getString(doc, Constants.FIELD_COLLECTION));
        
        boolean legacy = false; // default
        Object legacyValue = doc.getFieldValue(Constants.FIELD_LEGACY);
        if (legacyValue != null) {
        	legacy = (Boolean) legacyValue;
        }
        client.setLegacy(legacy);

        boolean active = true; // default
        Object activeValue = doc.getFieldValue(Constants.FIELD_ACTIVE);
        if (activeValue != null) {
            active = (Boolean) activeValue;
        }
        client.setActive(active);

        @SuppressWarnings("unchecked")
		List<String> synonyms = (List<String>) doc.get(Constants.FIELD_SYNONYMS);
        if (synonyms != null) {
            client.setSynonyms(synonyms.toArray(new String[0]));
        }
        @SuppressWarnings("unchecked")
		List<String> listHigh = (List<String>) doc.get(Constants.FIELDS_HIGH);
        if (listHigh != null) {
            client.setFieldsHigh(listHigh.toArray(new String[0]));
        }
        @SuppressWarnings("unchecked")
		List<String> listLow = (List<String>) doc.get(Constants.FIELDS_LOW);
        if (listLow != null) {
            client.setFieldsLow(listLow.toArray(new String[0]));
        }
        @SuppressWarnings("unchecked")
		List<String> listContent = (List<String>) doc.get(Constants.FIELDS_CONTENT);
        if (listContent != null) {
            client.setFieldsContent(listContent.toArray(new String[0]));
        }
        client.setEmbServiceUrl(solrService.getString(doc, Constants.EMB_SERVICE_URL));
        client.setLlmServiceUrl(solrService.getString(doc, Constants.LLM_SERVICE_URL));
        client.setNlpServiceUrl(solrService.getString(doc, Constants.NLP_SERVICE_URL));
        client.setRerServiceUrl(solrService.getString(doc, Constants.RER_SERVICE_URL));
        client.setCliServiceUrl(solrService.getString(doc, Constants.CLI_SERVICE_URL));
        client.setClsServiceUrl(solrService.getString(doc, Constants.CLS_SERVICE_URL));
		Boolean clientSearchProcessing = (Boolean) doc.get(Constants.CLIENT_SEARCH_PROCESSING);
        if (clientSearchProcessing != null) {
            client.setClientSearchProcessing(clientSearchProcessing);
        }
		Boolean clientIndexProcessing = (Boolean) doc.get(Constants.CLIENT_INDEX_PROCESSING);
        if (clientIndexProcessing != null) {
            client.setClientIndexProcessing(clientIndexProcessing);
        }
        return client;
    }
    
    /**
     * Validates client
     * @param client the Client to validate
     * @throws ClientException 
     */
    private void validateClient(Client client) throws ClientException {
    	String clientId = client.getClientId();
        if (clientId == null || clientId.trim().isEmpty()) {
            LOGGER.warn("Missing clientId for client {} ", client.toString());
            throw new ClientException("Missing clientId for client " + client.toString());
        }
        if (StringUtil.containsNonWordChars(client.getClientId())) {
            LOGGER.warn("Invalid clientId for client {} ", client.toString());
            throw new ClientException("Invalid clientId for client " + client.toString());
        }
    }

    /**
     * Converts a Client object to a Solr input document
     */
    private SolrInputDocument clientToSolrDocument(Client client) {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField(Constants.FIELD_ID, client.getClientId()); // Use clientId as Solr document ID
        doc.addField(Constants.FIELD_CLIENT_ID, client.getClientId());
        doc.addField(Constants.FIELD_NAME, client.getName());
        doc.addField(Constants.FIELD_COLLECTION, client.getCollection());
        doc.addField(Constants.FIELD_SYNONYMS, client.getSynonyms());
        doc.addField(Constants.FIELDS_HIGH, client.getFieldsHigh());
        doc.addField(Constants.FIELDS_LOW, client.getFieldsLow());
        doc.addField(Constants.FIELDS_CONTENT, client.getFieldsContent());
        doc.addField(Constants.EMB_SERVICE_URL, client.getEmbServiceUrl());
        doc.addField(Constants.LLM_SERVICE_URL, client.getLlmServiceUrl());
        doc.addField(Constants.NLP_SERVICE_URL, client.getNlpServiceUrl());
        doc.addField(Constants.RER_SERVICE_URL, client.getRerServiceUrl());
        doc.addField(Constants.CLI_SERVICE_URL, client.getCliServiceUrl());
        doc.addField(Constants.CLS_SERVICE_URL, client.getClsServiceUrl());
        doc.addField(Constants.FIELD_LEGACY, client.isLegacy());
        doc.addField(Constants.FIELD_ACTIVE, client.isActive());
        doc.addField(Constants.CLIENT_SEARCH_PROCESSING, client.isClientSearchProcessing());
        doc.addField(Constants.CLIENT_INDEX_PROCESSING, client.isClientIndexProcessing());
        return doc;
    }

}