/**
 * 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.index.service.processors;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.softcorporation.sandi.base.model.Client;
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.index.model.IndexingJob;
import com.softcorporation.sandi.index.util.IndexerException;

@Service
public class TxtlProcessingService extends JobProcessingService {

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

    public void executeIndexingJob(IndexingJob job) throws IndexerException {
        String stopJobId = getStopIndexingJobId();
    	if (stopJobId != null && job.getJobId().equals(stopJobId)) {
    		stopIndexingJob(null);
    	}
        LOGGER.info("Starting execution of indexing job: {}", job);
    	
        try {
            // Get client
            Client client = clientService.getClient(job.getClientId());
            if (client == null) {
                throw new ClientException("Client not found: " + job.getClientId());
            }

            // Validate directory or url exists
            Path path = Paths.get(job.getLocation());
            
            URI uri = path.toUri();
            if ("file".equalsIgnoreCase(uri.getScheme())) {
                if (!Files.exists(path)) {
                    throw new IOException("Directory does not exist or is not a directory: " + job.getLocation());
                }
                if (Files.isDirectory(path)) {
            	    processTxtlDirectory(job, client, path);
                } else {
                	processTxtlFile(job, client, path);
                }
            } else {
                processTxtlUrl(job, client, path);
            }
        } catch (Exception e) {
        	String message = "Error executing indexing job: " + job.getJobId() + " " + e.getMessage();
            LOGGER.error(message);
            job.setMessage(message);
            throw new IndexerException(message);
        }
    }

    public void processTxtlDirectory(IndexingJob job, Client client, Path directoryPath)
    		throws IndexerException, IOException {
        String jobId = job.getJobId();
        LOGGER.info("Indexing job {}: Processing directory {}", jobId, directoryPath);

    	boolean cancelled = false;
        String fileExtensions = job.getFileExtensions();
        if (fileExtensions == null) {
        	fileExtensions = ".txtl";
        }
        List<String> desiredExtensions = Utils.stringToList(fileExtensions, false);
        
        // Process files recursively
        int totalFiles = 0;
        int totalDocuments = 0;
        int successfulDocuments = 0;
        int failedDocuments = 0;

        try (Stream<Path> paths = Files.walk(directoryPath)) {
        	List<Path> filteredFiles = paths
                .filter(Files::isRegularFile)
                .filter(path -> desiredExtensions.stream().anyMatch(ext -> path.getFileName().toString().endsWith(ext)))
                .collect(Collectors.toList());

            LOGGER.info("Found {} matching files in directory: {}", filteredFiles.size(), job.getLocation());
            job.setTotalFiles(filteredFiles.size());

            for (Path filePath : filteredFiles) {
                String stopJobId = getStopIndexingJobId();
            	if (stopJobId != null && jobId.equals(stopJobId)) {
            		cancelled = true;
            		stopIndexingJob(null);
            		break;
            	}
                totalFiles++;
                LOGGER.info("Client {} Processing file: {}", client.getClientId(), filePath);

                try {
                    int[] counts = processTxtlFile(jobId, client, filePath, job.getForceReindexing());
                    totalDocuments += counts[0];
                    successfulDocuments += counts[1];
                    failedDocuments += counts[2];

                    // Update job progress
                    job.setTotalDocuments(totalDocuments);
                    job.setSuccessfulDocuments(successfulDocuments);
                    job.setFailedDocuments(failedDocuments);

                } catch (Exception e) {
                	String message = e.getMessage();
                    LOGGER.error("Client{} Error processing file {}: {}", client.getClientId(), filePath, message, e);
                    failedDocuments++;
                    job.setFailedDocuments(failedDocuments);
                    if (message.contains("Collection not found")) {
                        throw new IOException(message);
                    }
                }
            }
        }

        // Update final job statistics
        job.setTotalFiles(totalFiles);
        job.setTotalDocuments(totalDocuments);
        job.setSuccessfulDocuments(successfulDocuments);
        job.setFailedDocuments(failedDocuments);

    	if (cancelled) {
            job.setMessage(String.format("Cancelled with %d successful documents out of %d indexed", 
            		successfulDocuments, totalDocuments));
    	} else {
            if (failedDocuments > 0) {
                job.setMessage(String.format("Completed with %d failed documents out of %d total", 
                    failedDocuments, totalDocuments));
            } else {
                job.setMessage("All documents processed successfully");
            }
    	}
        LOGGER.info("Completed indexing job {}: {} files, {} total documents, {} successful, {} failed", 
            jobId, totalFiles, totalDocuments, successfulDocuments, failedDocuments);

    }

    public void processTxtlFile(IndexingJob job, Client client, Path filePath)
    		throws IndexerException, IOException {
        String jobId = job.getJobId();
        LOGGER.info("Indexing job {}: Processing file {}", jobId, filePath);

    	boolean cancelled = false;

        int[] counts = processTxtlFile(jobId, client, filePath, job.getForceReindexing());
        int totalFiles = 1;
        int totalDocuments = counts[0];
        int successfulDocuments = counts[1];
        int failedDocuments = counts[2];

        // Job statistics
        job.setTotalFiles(totalFiles);
        job.setTotalDocuments(totalDocuments);
        job.setSuccessfulDocuments(successfulDocuments);
        job.setFailedDocuments(failedDocuments);

    	if (cancelled) {
            job.setMessage(String.format("Cancelled with %d successful documents out of %d indexed", 
            		successfulDocuments, totalDocuments));
    	} else {
            if (failedDocuments > 0) {
                job.setMessage(String.format("Completed with %d failed documents out of %d total", 
                    failedDocuments, totalDocuments));
            } else {
                job.setMessage("All documents processed successfully");
            }
    	}
        LOGGER.info("Completed indexing job {}: {} files, {} total documents, {} successful, {} failed", 
            jobId, totalFiles, totalDocuments, successfulDocuments, failedDocuments);
    }

    public void processTxtlUrl(IndexingJob job, Client client, Path urlPath)
    		throws IndexerException, IOException {
        String jobId = job.getJobId();
        LOGGER.info("Indexing job {}: Processing url {}", jobId, urlPath);

    	boolean cancelled = false;
    	
    	//TODO not implemented

        int[] counts = processTxtlFile(jobId, client, urlPath, job.getForceReindexing());
        int totalFiles = 1;
        int totalDocuments = counts[0];
        int successfulDocuments = counts[1];
        int failedDocuments = counts[2];

        // Job statistics
        job.setTotalFiles(totalFiles);
        job.setTotalDocuments(totalDocuments);
        job.setSuccessfulDocuments(successfulDocuments);
        job.setFailedDocuments(failedDocuments);

    	if (cancelled) {
            job.setMessage(String.format("Cancelled with %d successful documents out of %d indexed", 
            		successfulDocuments, totalDocuments));
    	} else {
            if (failedDocuments > 0) {
                job.setMessage(String.format("Completed with %d failed documents out of %d total", 
                    failedDocuments, totalDocuments));
            } else {
                job.setMessage("All documents processed successfully");
            }
    	}
        LOGGER.info("Completed indexing job {}: {} files, {} total documents, {} successful, {} failed", 
            jobId, totalFiles, totalDocuments, successfulDocuments, failedDocuments);
    }

    private int[] processTxtlFile(String jobId, Client client, Path txtlFile, boolean forceReindexing) 
    		throws IOException, IndexerException {
        int totalDocuments = 0;
        int successfulDocuments = 0;
        int failedDocuments = 0;

        try (BufferedReader reader = new BufferedReader(new FileReader(txtlFile.toFile()))) {
            String line;
            int lineNumber = 0;

            List<JSONObject> jsonDocuments = new ArrayList<>();
            int dataLength = 0;
            
            while ((line = reader.readLine()) != null) {
                lineNumber++;
                line = line.trim();                
                if (line.isEmpty()) {
                    continue; // Skip empty lines
                }
                dataLength += line.length();
                String stopJobId = getStopIndexingJobId();
            	if (stopJobId != null && jobId.equals(stopJobId)) {
            		break;
            	}

                totalDocuments++;
                try {
                    JSONObject jsonDocument = new JSONObject();
                    jsonDocument.put(Constants.CONTENT_FIELD, line);
                    jsonDocuments.add(jsonDocument);
                    
                    if (dataLength > indexerService.getBatchDataLength()) {
                    	int processedDocuments = indexerService.processJsonDocuments(client, jsonDocuments, forceReindexing);
                    	indexerService.commit(client.getCollection());
                        dataLength = 0;
                    	successfulDocuments += processedDocuments;
                    	failedDocuments += jsonDocuments.size() - processedDocuments;
                        jsonDocuments = new ArrayList<>();
                    }
                } catch (Exception e) {
                    failedDocuments++;
                    LOGGER.warn("Failed to index document at line {} in file {}: {}", 
                        lineNumber, txtlFile, e.getMessage());
                    jsonDocuments = new ArrayList<>();
                    dataLength = 0;
                }
                if (totalDocuments % 100 == 0) {
                    LOGGER.debug("Reading {} documents from file: {}", totalDocuments, txtlFile);
                }
            }
            if (jsonDocuments.size() > 0) {
            	int processedDocuments = indexerService.processJsonDocuments(client, jsonDocuments, forceReindexing);
            	successfulDocuments += processedDocuments;
            	failedDocuments += jsonDocuments.size() - processedDocuments;
            }
        }

        LOGGER.info("Processed file {}: {} total documents, {} successful, {} failed", 
            txtlFile, totalDocuments, successfulDocuments, failedDocuments);

        return new int[]{totalDocuments, successfulDocuments, failedDocuments};
    }

}