/**
 * 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;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import com.softcorporation.sandi.base.model.Client;
import com.softcorporation.sandi.base.service.TextEmbeddingService;
import com.softcorporation.sandi.base.util.Constants;
import com.softcorporation.sandi.index.util.IndexerException;

/**
 * Unit tests for IndexerService (abstract class testing through concrete implementation)
 */
@ExtendWith(MockitoExtension.class)
class IndexerServiceTest {

    @Mock
    private TextEmbeddingService textEmbeddingService;

    private TestIndexerService indexerService;
    private Client testClient;

    @BeforeEach
    void setUp() {
        indexerService = new TestIndexerService();
        ReflectionTestUtils.setField(indexerService, "textEmbeddingService", textEmbeddingService);
        ReflectionTestUtils.setField(indexerService, "batchDataLength", 1000000);

        testClient = new Client();
        testClient.setClientId("test-client");
        testClient.setName("Test Client");
        testClient.setCollection("test-collection");
        testClient.setActive(true);
        testClient.setLegacy(false);
        testClient.setFieldsHigh("title");
        testClient.setFieldsLow("tags,keywords,summary");
        testClient.setFieldsContent("abstract,content");
    }

    @Test
    void testProcessJsonDocument_SingleDocument() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put("id", "doc1");
        document.put("title", "Test Document");
        document.put("content", "This is test content");
        document.put("summary", "Test summary");
        document.put("tags", "tag1,tag2");

//        when(textEmbeddingService.getDocumentEmbeddings(anyString(), anyString(), anyString()))
//            .thenReturn(Arrays.asList(0.1, 0.2, 0.3, 0.4, 0.5));

        // Act
        int result = indexerService.processJsonDocument(testClient, document, true);

        // Assert
        assertEquals(1, result);
        
        // Verify document was processed correctly
        assertTrue(document.has(Constants.CLIENT_ID));
        assertTrue(document.has(Constants.UUID));
        assertTrue(document.has(Constants.TEXT_HIGH));
        assertTrue(document.has(Constants.TEXT_LOW));
        assertTrue(document.has(Constants.TEXT_CONTENT));
        assertTrue(document.has(Constants.EMBEDDING));
        assertTrue(document.has(Constants._ID));

        assertEquals("test-client", document.getString(Constants.CLIENT_ID));
        
        // Verify text fields
        JSONArray textHigh = document.getJSONArray(Constants.TEXT_HIGH);
        assertEquals(2, textHigh.length());
        assertTrue(textHigh.toString().contains("Test Document"));
        assertTrue(textHigh.toString().contains("This is test content"));

        // Verify embeddings
        JSONArray embeddings = document.getJSONArray(Constants.EMBEDDING);
        assertEquals(5, embeddings.length());
        assertEquals(0.1, embeddings.getDouble(0), 0.001);

        // Verify indexJsonDocument was called
        assertTrue(indexerService.indexJsonDocumentCalled);
        assertEquals("test-collection", indexerService.lastCollection);
        assertEquals(document, indexerService.lastDocument);
    }

    @Test
    void testProcessJsonDocument_LegacyClient() throws Exception {
        // Arrange
        testClient.setLegacy(true);
        
        JSONObject document = new JSONObject();
        document.put("id", "doc1");
        document.put("title", "Test Document");

        // Act
        int result = indexerService.processJsonDocument(testClient, document, true);

        // Assert
        assertEquals(1, result);
        
        // Verify document was processed but without embeddings
        assertTrue(document.has(Constants.CLIENT_ID));
        assertTrue(document.has(Constants.UUID));
        assertFalse(document.has(Constants.EMBEDDING)); // No embeddings for legacy clients

        verify(textEmbeddingService, never()).getDocumentEmbeddings(any(), any(), any(), any());
    }

    @Test
    void testProcessJsonDocuments_MultipleDocuments() throws Exception {
        // Arrange
        JSONObject doc1 = new JSONObject();
        doc1.put("id", "doc1");
        doc1.put("title", "Document 1");
        doc1.put("content", "Content 1");

        JSONObject doc2 = new JSONObject();
        doc2.put("id", "doc2");
        doc2.put("title", "Document 2");
        doc2.put("content", "Content 2");

        List<JSONObject> documents = Arrays.asList(doc1, doc2);

//        when(textEmbeddingService.getDocumentEmbeddings(anyString(), anyString(), anyString()))
//            .thenReturn(Arrays.asList(0.1, 0.2, 0.3));

        // Act
        int result = indexerService.processJsonDocuments(testClient, documents, true);

        // Assert
        assertEquals(2, result);

        // Verify both documents were processed
        assertTrue(doc1.has(Constants.CLIENT_ID));
        assertTrue(doc2.has(Constants.CLIENT_ID));
        assertTrue(doc1.has(Constants.UUID));
        assertTrue(doc2.has(Constants.UUID));

        // Verify indexJsonDocuments was called with the documents
        assertTrue(indexerService.indexJsonDocumentsCalled);
        assertEquals("test-collection", indexerService.lastCollection);
        assertEquals(documents, indexerService.lastDocuments);
    }

    @Test
    void testProcessDocuments_MapToJsonConversion() throws Exception {
        // Arrange
        Map<String, Object> doc1Map = new HashMap<>();
        doc1Map.put("id", "doc1");
        doc1Map.put("title", "Document 1");
        doc1Map.put("tags", Arrays.asList("tag1", "tag2"));

        Map<String, Object> doc2Map = new HashMap<>();
        doc2Map.put("id", "doc2");
        doc2Map.put("title", "Document 2");

        List<Map<String, Object>> documents = Arrays.asList(doc1Map, doc2Map);

//        when(textEmbeddingService.getDocumentEmbeddings(anyString(), anyString(), anyString()))
//            .thenReturn(Arrays.asList(0.1, 0.2, 0.3));

        // Act
        int result = indexerService.processDocuments(testClient, documents, true);

        // Assert
        assertEquals(2, result);
        assertTrue(indexerService.indexJsonDocumentsCalled);
        assertEquals(2, indexerService.lastDocuments.size());
        
        // Verify conversion to JSONObject
        JSONObject convertedDoc1 = indexerService.lastDocuments.get(0);
        assertEquals("doc1", convertedDoc1.getString("id"));
        assertEquals("Document 1", convertedDoc1.getString("title"));
        assertTrue(convertedDoc1.has("tags"));
    }

    @Test
    void testSetId_WithExistingId() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put(Constants.ID, "existing-id");

        // Act
        String result = indexerService.testSetId(document, "test-client");

        // Assert
        assertEquals("existing-id", result);
        assertEquals("existing-id", document.getString(Constants.ID));
        assertEquals("test-client/existing-id", document.getString(Constants._ID));
    }

    @Test
    void testSetId_WithUri() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put(Constants.URI, "http://example.com/doc1");

        // Act
        String result = indexerService.testSetId(document, "test-client");

        // Assert
        assertEquals("http://example.com/doc1", result);
        assertEquals("http://example.com/doc1", document.getString(Constants.ID));
        assertEquals("test-client/http://example.com/doc1", document.getString(Constants._ID));
    }

    @Test
    void testSetId_GeneratedId() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put("content", "Some content for UUID generation");

        // Act
        String result = indexerService.testSetId(document, "test-client");

        // Assert
        assertNotNull(result);
        assertFalse(result.isEmpty());
        assertEquals(result, document.getString(Constants.ID));
        assertEquals("test-client/" + result, document.getString(Constants._ID));
        
        // Same content should generate same ID
        JSONObject document2 = new JSONObject();
        document2.put("content", "Some content for UUID generation");
        String result2 = indexerService.testSetId(document2, "test-client");
        assertEquals(result, result2);
    }

    @Test
    void testSetId_WithClientPrefix() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put(Constants.ID, "test-client/existing-id");

        // Act
        String result = indexerService.testSetId(document, "test-client");

        // Assert
        assertEquals("test-client/existing-id", result);
        assertEquals("test-client/existing-id", document.getString(Constants.ID));
        assertEquals("test-client/existing-id", document.getString(Constants._ID));
    }

    @Test
    void testGetBatchDataLength() {
        // Act
        int result = indexerService.getBatchDataLength();

        // Assert
        assertEquals(1000000, result);
    }

    @Test
    void testProcessJsonDocument_WithFieldProcessing() throws Exception {
        // Arrange
        JSONObject document = new JSONObject();
        document.put("id", "doc1");
        document.put("title", "Test Title");
        document.put("content", "Test Content");
        document.put("summary", "Test Summary");
        document.put("abstract", "Test Abstract");
        document.put("tags", "tag1");
        document.put("keywords", "keyword1");

//        when(textEmbeddingService.getDocumentEmbeddings(anyString(), anyString(), anyString()))
//            .thenReturn(Arrays.asList(0.1, 0.2, 0.3));

        // Act
        indexerService.processJsonDocument(testClient, document, true);

        // Assert
        JSONArray textHigh = document.getJSONArray(Constants.TEXT_HIGH);
        JSONArray textLow = document.getJSONArray(Constants.TEXT_LOW);
        JSONArray textContent = document.getJSONArray(Constants.TEXT_CONTENT);

        // High fields: title, content
        assertEquals(2, textHigh.length());
        assertTrue(textHigh.toString().contains("Test Title"));
        assertTrue(textHigh.toString().contains("Test Content"));

        // Low fields: tags, keywords + medium fields + high fields
        assertEquals(6, textLow.length());
        assertTrue(textLow.toString().contains("tag1"));
        assertTrue(textLow.toString().contains("keyword1"));
        assertTrue(textLow.toString().contains("Test Summary"));

        // Content fields: summary, abstract + high fields
        assertEquals(4, textContent.length());
        assertTrue(textContent.toString().contains("Test Content"));
    }

    // Test concrete implementation of IndexerService for testing purposes
    private static class TestIndexerService extends IndexerService {
        
        public boolean indexJsonDocumentCalled = false;
        public boolean indexJsonDocumentsCalled = false;
        public String lastCollection;
        public JSONObject lastDocument;
        public List<JSONObject> lastDocuments;

        @Override
        public void indexDocument(String collection, JSONObject document) throws IndexerException {
            indexJsonDocumentCalled = true;
            lastCollection = collection;
            lastDocument = document;
        }

        @Override
        public void indexDocuments(String collection, List<JSONObject> documents) throws IndexerException {
            indexJsonDocumentsCalled = true;
            lastCollection = collection;
            lastDocuments = documents;
        }

        @Override
        public JSONObject getDocumentById(String collection, String id, String fields) throws IndexerException {
            return null; // Not needed for these tests
        }

        @Override
        public JSONObject getDocumentById(String collection, String clientId, String id, String fields) throws IndexerException {
            return null; // Not needed for these tests
        }

        @Override
        public void commit(String collection) throws IndexerException {
            // Not needed for these tests
        }

        // Expose protected method for testing
        public String testSetId(JSONObject doc, String clientId) {
            return setUniqueId(doc, clientId);
        }

		@Override
		public void deleteDocument(String collection, String uniqueId) throws IndexerException {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void deleteDocument(String collection, String ClientId, String id) throws IndexerException {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void deleteDocuments(String collection, List<String> uniqueIds) throws IndexerException {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void deleteDocuments(String collection, String ClientId, List<String> Ids) throws IndexerException {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void deleteDocumentsByQuery(String collection, String ClientId, String query) throws IndexerException {
			// TODO Auto-generated method stub
			
		}
    }
}