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

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;

import com.softcorporation.sandi.base.model.Client;

/**
 * Custom assertion utilities for SANDI tests
 */
public class TestAssertions {

    private TestAssertions() {
        // Utility class
    }

    /**
     * Assert that a client object has all required fields
     */
    public static void assertValidClient(Client client) {
        assertNotNull(client, "Client should not be null");
        assertNotNull(client.getClientId(), "Client ID should not be null");
        assertFalse(client.getClientId().trim().isEmpty(), "Client ID should not be empty");
        assertNotNull(client.getName(), "Client name should not be null");
        assertFalse(client.getName().trim().isEmpty(), "Client name should not be empty");
        assertNotNull(client.getCollection(), "Client collection should not be null");
        assertFalse(client.getCollection().trim().isEmpty(), "Client collection should not be empty");
    }

    /**
     * Assert that a client has specific values
     */
    public static void assertClientEquals(Client expected, Client actual) {
        assertValidClient(expected);
        assertValidClient(actual);
        
        assertEquals(expected.getClientId(), actual.getClientId(), "Client IDs should match");
        assertEquals(expected.getName(), actual.getName(), "Client names should match");
        assertEquals(expected.getCollection(), actual.getCollection(), "Client collections should match");
        assertEquals(expected.isActive(), actual.isActive(), "Client active status should match");
        assertEquals(expected.isLegacy(), actual.isLegacy(), "Client legacy status should match");
        assertEquals(expected.getFieldsHigh(), actual.getFieldsHigh(), "Text high fields should match");
        assertEquals(expected.getFieldsLow(), actual.getFieldsLow(), "Text low fields should match");
        assertEquals(expected.getFieldsContent(), actual.getFieldsContent(), "Text content fields should match");
    }

    /**
     * Assert that a JSONObject has required document fields
     */
    public static void assertValidDocument(JSONObject document) {
        assertNotNull(document, "Document should not be null");
        assertTrue(document.has("id"), "Document should have an id field");
        assertNotNull(document.optString("id", null), "Document id should not be null");
        assertFalse(document.optString("id", "").trim().isEmpty(), "Document id should not be empty");
    }

    /**
     * Assert that a JSONObject has all expected processed fields
     */
    public static void assertProcessedDocument(JSONObject document, String expectedClientId) {
        assertValidDocument(document);
        
        assertTrue(document.has(Constants.CLIENT_ID), "Document should have client ID");
        assertEquals(expectedClientId, document.getString(Constants.CLIENT_ID), "Client ID should match");
        
        assertTrue(document.has(Constants.UUID), "Document should have UUID");
        assertValidUUID(document.getString(Constants.UUID));
        
        assertTrue(document.has(Constants._ID), "Document should have unique ID");
        String uniqueId = document.getString(Constants._ID);
        assertTrue(uniqueId.startsWith(expectedClientId), "Unique ID should start with client ID");
        
        assertTrue(document.has(Constants.TEXT_HIGH), "Document should have text high field");
        assertTrue(document.has(Constants.TEXT_LOW), "Document should have text low field");
        assertTrue(document.has(Constants.TEXT_CONTENT), "Document should have text content field");
    }

    /**
     * Assert that a JSONObject has embeddings for non-legacy clients
     */
    public static void assertDocumentWithEmbeddings(JSONObject document, String expectedClientId) {
        assertProcessedDocument(document, expectedClientId);
        
        assertTrue(document.has(Constants.EMBEDDING), "Document should have embeddings");
        JSONArray embeddings = document.getJSONArray(Constants.EMBEDDING);
        assertTrue(embeddings.length() > 0, "Embeddings array should not be empty");
        
        for (int i = 0; i < embeddings.length(); i++) {
            Object embedding = embeddings.get(i);
            assertTrue(embedding instanceof Number, "Embedding should be a number at index " + i);
        }
    }

    /**
     * Assert that a JSONObject doesn't have embeddings for legacy clients
     */
    public static void assertLegacyDocument(JSONObject document, String expectedClientId) {
        assertProcessedDocument(document, expectedClientId);
        assertFalse(document.has(Constants.EMBEDDING), "Legacy document should not have embeddings");
    }

    /**
     * Assert that a UUID string is valid
     */
    public static void assertValidUUID(String uuid) {
        assertNotNull(uuid, "UUID should not be null");
        Pattern uuidPattern = Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
        assertTrue(uuidPattern.matcher(uuid).matches(), "UUID should be in valid format: " + uuid);
    }

    /**
     * Assert that a list contains all expected clients
     */
    public static void assertContainsClients(List<Client> clients, String... expectedClientIds) {
        assertNotNull(clients, "Client list should not be null");
        assertEquals(expectedClientIds.length, clients.size(), "Client list should have expected size");
        
        for (String expectedClientId : expectedClientIds) {
            boolean found = clients.stream()
                .anyMatch(client -> expectedClientId.equals(client.getClientId()));
            assertTrue(found, "Client list should contain client: " + expectedClientId);
        }
    }

    /**
     * Assert that a JSONArray contains expected number of items with specific fields
     */
    public static void assertJSONArraySize(JSONArray array, int expectedSize) {
        assertNotNull(array, "JSONArray should not be null");
        assertEquals(expectedSize, array.length(), "JSONArray should have expected size");
    }

    /**
     * Assert that a JSONArray contains objects with required fields
     */
    public static void assertJSONArrayContainsFields(JSONArray array, String... requiredFields) {
        assertNotNull(array, "JSONArray should not be null");
        assertTrue(array.length() > 0, "JSONArray should not be empty");
        
        for (int i = 0; i < array.length(); i++) {
            Object item = array.get(i);
            assertTrue(item instanceof JSONObject, "Array item should be JSONObject at index " + i);
            
            JSONObject obj = (JSONObject) item;
            for (String field : requiredFields) {
                assertTrue(obj.has(field), "Object at index " + i + " should have field: " + field);
            }
        }
    }

    /**
     * Assert that a JSONArray of strings contains expected values
     */
    public static void assertJSONArrayContainsStrings(JSONArray array, String... expectedValues) {
        assertNotNull(array, "JSONArray should not be null");
        assertEquals(expectedValues.length, array.length(), "JSONArray should have expected size");
        
        for (String expectedValue : expectedValues) {
            boolean found = false;
            for (int i = 0; i < array.length(); i++) {
                if (expectedValue.equals(array.getString(i))) {
                    found = true;
                    break;
                }
            }
            assertTrue(found, "JSONArray should contain: " + expectedValue);
        }
    }

    /**
     * Assert that text and content fields are properly hierarchical
     */
    public static void assertHierarchy(JSONObject document) {
        assertTrue(document.has(Constants.TEXT_HIGH), "Document should have text high field");
        assertTrue(document.has(Constants.TEXT_LOW), "Document should have text low field");
        assertTrue(document.has(Constants.TEXT_CONTENT), "Document should have text content field");
        
        JSONArray highArray = document.getJSONArray(Constants.TEXT_HIGH);
        JSONArray lowArray = document.getJSONArray(Constants.TEXT_LOW);
        JSONArray contentArray = document.getJSONArray(Constants.TEXT_CONTENT);
        
        // Low should contain all high fields
        assertTrue(lowArray.length() >= highArray.length(), 
            "Low fields should contain at least all high fields");

        // Content should contain all high fields
        assertTrue(contentArray.length() >= lowArray.length(), 
            "Content fields should contain at least all low fields");
        
    }

    /**
     * Assert that a time value is within reasonable bounds
     */
    public static void assertReasonableTimestamp(long timestamp) {
        long now = System.currentTimeMillis();
        long oneHourAgo = now - (60 * 60 * 1000);
        long oneHourFromNow = now + (60 * 60 * 1000);
        
        assertTrue(timestamp >= oneHourAgo, "Timestamp should not be more than 1 hour old");
        assertTrue(timestamp <= oneHourFromNow, "Timestamp should not be more than 1 hour in the future");
    }

    /**
     * Assert that a string contains expected substrings
     */
    public static void assertContainsAll(String text, String... expectedSubstrings) {
        assertNotNull(text, "Text should not be null");
        
        for (String substring : expectedSubstrings) {
            assertTrue(text.contains(substring), 
                "Text should contain '" + substring + "', but was: " + text);
        }
    }

    /**
     * Assert that two lists have the same size and contain the same elements (order independent)
     */
    public static void assertListsEquivalent(List<?> expected, List<?> actual) {
        assertNotNull(expected, "Expected list should not be null");
        assertNotNull(actual, "Actual list should not be null");
        assertEquals(expected.size(), actual.size(), "Lists should have the same size");
        
        for (Object expectedItem : expected) {
            assertTrue(actual.contains(expectedItem), 
                "Actual list should contain: " + expectedItem);
        }
    }

    /**
     * Assert that a collection is not empty
     */
    public static void assertNotEmpty(List<?> list, String message) {
        assertNotNull(list, message + " - list should not be null");
        assertFalse(list.isEmpty(), message + " - list should not be empty");
    }

    /**
     * Assert that a string matches a regex pattern
     */
    public static void assertMatches(String pattern, String actual) {
        assertNotNull(actual, "String should not be null");
        assertTrue(Pattern.matches(pattern, actual), 
            "String '" + actual + "' should match pattern: " + pattern);
    }
}