/**
 * 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.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.common.util.SimpleOrderedMap;
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.Collection;
import com.softcorporation.sandi.base.service.CollectionService;
import com.softcorporation.sandi.base.util.CollectionException;
import com.softcorporation.sandi.base.util.ConfigurationException;
import com.softcorporation.sandi.base.util.Constants;

import jakarta.annotation.PostConstruct;

@Service
public class SolrCollectionService implements CollectionService {

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

    /**
     * Create collections
     */
    @Value("${sandi.collections.create}")
    private boolean createColls;
    
    /**
     * Collections shards
     */
    @Value("${sandi.collections.config.shards:1}")
    private int collsConfigShards;
    
    /**
     * Collections replicas
     */
    @Value("${sandi.collections.config.replicas:1}")
    private int collsConfigReplicas;
    
    @Autowired
    private SolrService solrService;
    
    @Autowired
    private SolrConfigurationService configurationService;
    
    @PostConstruct
    public void init() {
        LOGGER.debug("Initializing CollectionService");
        if (createColls) {
            try {
            	if (!hasCollection(Constants.DEFAULT_COLLECTION)) {
            		Collection defaultCollection = new Collection(Constants.DEFAULT_COLLECTION, Constants.DEFAULT_CONFIGURATION);
            		createCollection(defaultCollection);
            	}
            } catch (Exception e) {
                LOGGER.error("Failed to initialize CollectionService: {}", e.getMessage(), e);
            }
        }
    }
    
    @Override
    public List<String> listCollections() throws CollectionException {
        LOGGER.debug("Listing all collections from Solr");
        
        try {
            CollectionAdminRequest.List listRequest = new CollectionAdminRequest.List();
            CollectionAdminResponse response = listRequest.process(solrService.getSolrClient());
            
            @SuppressWarnings("unchecked")
            List<String> collections = (List<String>) response.getResponse().get("collections");
            if (collections == null) {
                collections = new java.util.ArrayList<>();
            }
            
            LOGGER.info("Found {} collections: {}", collections.size(), collections);
            
            return collections;
            
        } catch (SolrServerException e) {
            LOGGER.error("Failed to list collections: {}", e.getMessage(), e);
            solrService.reset();
            throw new CollectionException("Failed to list collections: " + e.getMessage());
        } catch (IOException e) {
            LOGGER.error("Failed to list collections: {}", e.getMessage(), e);
            throw new CollectionException("Failed to list collections: " + e.getMessage());
		}
    }

    @Override
    public boolean hasCollection(String name) throws CollectionException {
    	List<String> collections = listCollections();
    	if (collections.contains(name)) {
    		return true;
    	}
    	return false;
    }
    
    @Override
    public Collection getCollection(String name) throws CollectionException {
        if (name == null || name.trim().isEmpty()) {
            throw new CollectionException("Collection name cannot be null or empty");
        }
        
        if (!hasCollection(name)) {
        	return null;
        }
        LOGGER.debug("Getting collection details for: {}", name);
        
        try {
            // Get cluster state to retrieve collection configuration
            CollectionAdminRequest.ClusterStatus clusterStatusRequest = new CollectionAdminRequest.ClusterStatus();
            clusterStatusRequest.setCollectionName(name);
            CollectionAdminResponse response = clusterStatusRequest.process(solrService.getSolrClient());
            
            // Extract configuration name from cluster state
            SimpleOrderedMap<?> cluster = (SimpleOrderedMap<?>) response.getResponse().get("cluster");
            if (cluster != null) {
                SimpleOrderedMap<?> collections = (SimpleOrderedMap<?>) cluster.get("collections");
                if (collections != null) {
                    LinkedHashMap<?, ?> collectionData = (LinkedHashMap<?, ?>) collections.get(name);
                    if (collectionData != null) {
                        String configName = (String) collectionData.get("configName");
                        Collection collection = new Collection(name, configName);
                        LinkedHashMap<?, ?> shardMap = (LinkedHashMap<?, ?>) collectionData.get("shards");
                        int shards = shardMap.size();
                        collection.setShards(shards);
                        long replicas = (Long) collectionData.get("replicationFactor");
                        collection.setReplicas((int) replicas);
                        LOGGER.info("Found collection '{}' with configuration '{}'", name, configName);
                        return collection;
                    }
                }
            }
            
            LOGGER.warn("Could not retrieve configuration details for collection '{}'", name);
            return null;
            
        } catch (SolrServerException | IOException e) {
            LOGGER.error("Failed to get collection '{}': {}", name, e.getMessage(), e);
            throw new CollectionException("Failed to get collection details: " + e.getMessage());
        }
    }
    
    @Override
    public void createCollection(final Collection collection) throws CollectionException {
        String name = collection.getName();
    	if (name == null || name.trim().isEmpty()) {
            throw new CollectionException("Collection name cannot be null or empty");
        }
        
        if (hasCollection(name)) {
            throw new CollectionException("Collection already exists");
        }
        
        int shards = collection.getShards();
    	if (shards == 0) {
    		shards = collsConfigShards;
    	} else if (shards < 0) {
            throw new CollectionException("Invalid collection shards");
        }
        
        int replicas = collection.getReplicas();
    	if (replicas == 0) {
    		replicas = collsConfigReplicas;
    	} else if (replicas < 0) {
            throw new CollectionException("Invalid collection replicas");
        }
        
        String configuration = collection.getConfiguration();
    	if (configuration == null || configuration.length() == 0) {
        	Path path = Path.of(configurationService.getCollectionsDirectory(), name);
            if (Files.exists(path)) {
            	configuration = name;
            } else if (Constants.CLIENTS_COLLECTION.equals(name)) {
            	configuration = Constants.CLIENTS_CONFIGURATION;
            } else if (Constants.DEFAULT_COLLECTION.equals(name)) {
            	configuration = Constants.DEFAULT_CONFIGURATION;
        	} else {
            	configuration = Constants.DEFAULT_CONFIGURATION;
            }
        }        
        if (configuration == null || configuration.trim().isEmpty()) {
            throw new CollectionException("Invalid configuration name");
        }
        
        try {
        	if (!configurationService.hasConfiguration(configuration)) {
        		configurationService.uploadConfiguration(configuration, Path.of(configurationService.getCollectionsDirectory(), configuration));
        	}

        	LOGGER.debug("Creating collection: '{}' with configuration: '{}' shards: {} replicas: {}", name, configuration, shards, replicas);            
        	
        	// create new collection
            CollectionAdminRequest.Create createRequest = CollectionAdminRequest.createCollection(name, configuration, shards, replicas);
            createRequest.process(solrService.getSolrClient());
            // fix caching issue
            CollectionAdminRequest.Reload reloadRequest = CollectionAdminRequest.reloadCollection(name);
            reloadRequest.process(solrService.getSolrClient());
            
            LOGGER.info("Successfully created collection '{}' with configuration '{}'", name, configuration);
            
        } catch (SolrServerException | IOException | ConfigurationException e) {
            LOGGER.error("Failed to create collection '{}' with config '{}': {}", name, configuration, e.getMessage(), e);
            throw new CollectionException("Failed to create collection: " + e.getMessage());
        }
    }

    @Override
    public boolean deleteCollection(String name) throws CollectionException {
        if (name == null || name.trim().isEmpty()) {
            throw new CollectionException("Collection name cannot be null or empty");
        }
        
        LOGGER.debug("Deleting collection '{}'", name);
        try {
            CollectionAdminRequest.Delete deleteRequest = CollectionAdminRequest.deleteCollection(name);
            deleteRequest.process(solrService.getSolrClient());
            
            LOGGER.info("Successfully deleted collection '{}'", name);
            return true;
        } catch (SolrServerException | IOException e) {
            LOGGER.error("Failed to delete collection '{}': {}", name, e.getMessage(), e);
            throw new CollectionException("Failed to delete collection: " + e.getMessage());
        }
    }

    @Override
    public void updateCollection(final Collection collection) throws CollectionException {
        String name = collection.getName();
    	if (name == null || name.trim().isEmpty()) {
            throw new CollectionException("Collection name cannot be null or empty");
        }
        
        String config = collection.getConfiguration();
        if (config == null || config.trim().isEmpty()) {
            throw new CollectionException("Configuration cannot be null or empty");
        }
        
        int shards = collection.getShards();
        if (shards < 0) {
            throw new CollectionException("Invalid collection shards");
        }
        int replicas = collection.getReplicas();
    	if (replicas < 0) {
            throw new CollectionException("Invalid collection replicas");
        }

        if (!hasCollection(name)) {
            throw new CollectionException("Collection " + name + " does not exist");
        }
        
        LOGGER.info("Updating collection '{}' to use configuration '{}'", name, config);
        
        Map<String, Object> propertiesToModify = new HashMap<>();
        if (shards > 0) {
//            propertiesToModify.put("maxShardsPerNode", shards);
        }
        if (replicas > 0) {
//            propertiesToModify.put("maxReplicasPerNode", replicas);
        }
        
        try {
            CollectionAdminRequest.Modify modifyRequest = CollectionAdminRequest.modifyCollection(name, propertiesToModify);
            modifyRequest.setAttribute("collection.configName", config);
            modifyRequest.process(solrService.getSolrClient());
            
            CollectionAdminRequest.Reload reloadRequest = CollectionAdminRequest.reloadCollection(name);
            reloadRequest.process(solrService.getSolrClient());

            LOGGER.info("Successfully updated collection '{}' to use configuration '{}'", name, config);
            
        } catch (SolrServerException | IOException e) {
            LOGGER.error("Failed to update collection '{}' with config '{}': {}", name, config, e.getMessage(), e);
            throw new CollectionException("Failed to update collection configuration: " + e.getMessage());
        }
    }

}
