/**
 * 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 static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Pattern;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import com.softcorporation.util.net.ClientRequest;
import com.softcorporation.util.net.ClientResponse;
import com.softcorporation.util.net.HttpClient;

/**
 * JUnit test cases for Utils class
 */
class UtilsTest {

    private TimeZone originalTimeZone;
    
    @TempDir
    Path tempDir;

    @BeforeEach
    void setUp() {
        // Store original timezone to restore later
        originalTimeZone = TimeZone.getDefault();
    }

    @AfterEach
    void tearDown() {
        // Restore original timezone
        TimeZone.setDefault(originalTimeZone);
    }

    @Test
    void testSetDefaultTimeZone() {
        // Test setting default timezone
        Utils.setDefaultTimeZone();
        assertEquals("UTC", TimeZone.getDefault().getID());
    }

    @Test
    void testGetCurrentTime() {
        // Test that getCurrentTime returns a properly formatted date string
        String currentTime = Utils.getCurrentTime();
        
        // Check that it's not null or empty
        assertNotNull(currentTime);
        assertFalse(currentTime.isEmpty());
        
        // Check format matches Constants.DATE_FORMAT_STANDARD (yyyy-MM-dd'T'HH:mm:ss.SSS'Z')
        Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z");
        assertTrue(pattern.matcher(currentTime).matches(), 
            "Current time format should match yyyy-MM-dd'T'HH:mm:ss.SSS'Z', got: " + currentTime);
    }

    @Test
    void testGetSolrTime() {
        // Test that getSolrTime returns a properly formatted Solr date string
        String solrTime = Utils.getSolrTime();
        
        // Check that it's not null or empty
        assertNotNull(solrTime);
        assertFalse(solrTime.isEmpty());
        
        // Check format matches Constants.SOLR_DATE_FORMAT (yyyy-MM-dd'T'HH:mm:ss'Z')
        Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
        assertTrue(pattern.matcher(solrTime).matches(), 
            "Solr time format should match yyyy-MM-dd'T'HH:mm:ss'Z', got: " + solrTime);
    }

    @Test
    void testParseDate() throws ParseException {
        // Test parsing a valid date string
        String dateString = "2025-08-17";
        Date parsedDate = Utils.parseDate(dateString);
        
        assertNotNull(parsedDate);
        
        // Verify the parsed date
        Calendar cal = Calendar.getInstance();
        cal.setTime(parsedDate);
        assertEquals(2025, cal.get(Calendar.YEAR));
        assertEquals(7, cal.get(Calendar.MONTH)); // Month is 0-based
        assertEquals(17, cal.get(Calendar.DAY_OF_MONTH));
    }

    @Test
    void testParseDateWithInvalidFormat() {
        // Test parsing an invalid date string
        String invalidDateString = "invalid-date";
        assertThrows(ParseException.class, () -> {
            Utils.parseDate(invalidDateString);
        });
    }

    @Test
    void testFormatDate() {
        // Create a specific date
        Calendar cal = Calendar.getInstance();
        cal.set(2025, 7, 17, 10, 30, 45); // August 17, 2025, 10:30:45
        Date testDate = cal.getTime();
        
        // Format the date
        String formattedDate = Utils.formatDate(testDate);
        
        // Verify format
        assertEquals("2025-08-17", formattedDate);
    }

    @Test
    void testFormatStandardDate() {
        // Create a specific date
        Calendar cal = Calendar.getInstance();
        cal.set(2025, 7, 17, 10, 30, 45); // August 17, 2025, 10:30:45
        cal.set(Calendar.MILLISECOND, 123);
        Date testDate = cal.getTime();
        
        // Format the date
        String formattedDate = Utils.formatStandardDate(testDate);
        
        // Check that it contains expected components
        assertTrue(formattedDate.startsWith("2025-08-17T"));
        assertTrue(formattedDate.endsWith("Z"));
        assertTrue(formattedDate.contains(".123"));
    }

    @Test
    void testParseStandardDate() throws ParseException {
        // Test parsing a valid standard date string
        String standardDateString = "2025-08-17T10:30:45.123Z";
        Date parsedDate = Utils.parseStandardDate(standardDateString);
        
        assertNotNull(parsedDate);
        
        // Verify the parsed date
        Calendar cal = Calendar.getInstance();
        cal.setTime(parsedDate);
        assertEquals(2025, cal.get(Calendar.YEAR));
        assertEquals(7, cal.get(Calendar.MONTH)); // Month is 0-based
        assertEquals(17, cal.get(Calendar.DAY_OF_MONTH));
    }

    @Test
    void testParseStandardDateWithInvalidFormat() {
        // Test parsing an invalid standard date string
        String invalidDateString = "invalid-standard-date";
        assertThrows(ParseException.class, () -> {
            Utils.parseStandardDate(invalidDateString);
        });
    }

    @Test
    void testIsWritableDirWithValidDirectory() throws IOException {
        // Create a temporary directory
        Path writableDir = Files.createTempDirectory(tempDir, "writable");
        
        // Test with valid writable directory
        assertTrue(Utils.isWritableDir(writableDir.toString()));
    }

    @Test
    void testIsWritableDirWithNull() {
        // Test with null directory
        assertFalse(Utils.isWritableDir(null));
    }

    @Test
    void testIsWritableDirWithNonExistentDirectory() {
        // Test with non-existent directory
        assertFalse(Utils.isWritableDir("/non/existent/directory"));
    }

    @Test
    void testIsWritableDirWithFile() throws IOException {
        // Create a temporary file
        Path tempFile = Files.createTempFile(tempDir, "test", ".txt");
        
        // Test with file (not directory)
        assertFalse(Utils.isWritableDir(tempFile.toString()));
    }

    @Test
    void testCutTextShorterThanMaxLength() {
        String source = "Hello World";
        int maxLength = 20;
        
        String result = Utils.cutText(source, maxLength);
        
        assertEquals("Hello World", result);
    }

    @Test
    void testCutTextExactMaxLength() {
        String source = "Exact text";
        int maxLength = 10;
        
        String result = Utils.cutText(source, maxLength);
        
        assertEquals("Exact text", result);
    }

    @Test
    void testGetStackTrace() {
        // Create a test exception
        Exception testException = new RuntimeException("Test exception message");
        
        // Get stack trace
        String stackTrace = Utils.getStackTrace(testException);
        
        // Verify stack trace contains expected elements
        assertNotNull(stackTrace);
        assertTrue(stackTrace.contains("RuntimeException"));
        assertTrue(stackTrace.contains("Test exception message"));
        assertTrue(stackTrace.contains("at "));
    }

    @Test
    void testGetStackTraceWithCause() {
        // Create a test exception with cause
        Exception cause = new IllegalArgumentException("Root cause");
        Exception testException = new RuntimeException("Test exception", cause);
        
        // Get stack trace
        String stackTrace = Utils.getStackTrace(testException);
        
        // Verify stack trace contains both exceptions
        assertNotNull(stackTrace);
        assertTrue(stackTrace.contains("RuntimeException"));
        assertTrue(stackTrace.contains("IllegalArgumentException"));
        assertTrue(stackTrace.contains("Root cause"));
    }

    @Test
    void testCreateZipFileWithSingleFile() throws IOException {
        // Create a test file
        Path testFile = Files.createTempFile(tempDir, "test", ".txt");
        Files.write(testFile, "Test content".getBytes());
        
        // Create zip file
        Path zipFile = tempDir.resolve("test.zip");
        Utils.createZipFile(testFile, zipFile);
        
        // Verify zip file was created
        assertTrue(Files.exists(zipFile));
        assertTrue(Files.size(zipFile) > 0);
    }

    @Test
    void testCreateZipFileWithDirectory() throws IOException {
        // Create a test directory with files
        Path testDir = Files.createTempDirectory(tempDir, "testdir");
        Path file1 = Files.createTempFile(testDir, "file1", ".txt");
        Path file2 = Files.createTempFile(testDir, "file2", ".txt");
        Files.write(file1, "Content 1".getBytes());
        Files.write(file2, "Content 2".getBytes());
        
        // Create zip file
        Path zipFile = tempDir.resolve("directory.zip");
        Utils.createZipFile(testDir, zipFile);
        
        // Verify zip file was created
        assertTrue(Files.exists(zipFile));
        assertTrue(Files.size(zipFile) > 0);
    }

    @Test
    void testCreateZipFileWithNullInput() {
        Path zipFile = tempDir.resolve("test.zip");
        
        assertThrows(IllegalArgumentException.class, () -> {
            Utils.createZipFile(null, zipFile);
        });
    }

    @Test
    void testCreateZipFileWithNullOutput() throws IOException {
        Path testFile = Files.createTempFile(tempDir, "test", ".txt");
        
        assertThrows(IllegalArgumentException.class, () -> {
            Utils.createZipFile(testFile, null);
        });
    }

    @Test
    void testCreateZipFileWithNonExistentInput() {
        Path nonExistentFile = tempDir.resolve("nonexistent.txt");
        Path zipFile = tempDir.resolve("test.zip");
        
        assertThrows(IllegalArgumentException.class, () -> {
            Utils.createZipFile(nonExistentFile, zipFile);
        });
    }

    @Test
    void testStringToListWithValidInput() {
        String input = "apple,banana,cherry,apple"; // Note: apple appears twice
        
        List<String> result = Utils.stringToList(input, true);
        
        assertNotNull(result);
        assertEquals(3, result.size()); // Should contain unique items only
        assertTrue(result.contains("apple"));
        assertTrue(result.contains("banana"));
        assertTrue(result.contains("cherry"));
    }

    @Test
    void testStringToListWithWhitespace() {
        String input = " apple , banana , cherry ";
        
        List<String> result = Utils.stringToList(input, true);
        
        assertNotNull(result);
        assertEquals(3, result.size());
        assertTrue(result.contains("apple"));
        assertTrue(result.contains("banana"));
        assertTrue(result.contains("cherry"));
    }

    @Test
    void testStringToListWithEmptyString() {
        String input = "";
        
        List<String> result = Utils.stringToList(input, true);
        
        assertNull(result);
    }

    @Test
    void testStringToListWithNull() {
        List<String> result = Utils.stringToList(null, true);
        
        assertNull(result);
    }

    @Test
    void testStringToListWithEmptyElements() {
        String input = "apple,,banana,   ,cherry";
        
        List<String> result = Utils.stringToList(input, true);
        
        assertNotNull(result);
        assertEquals(3, result.size()); // Empty elements should be filtered out
        assertTrue(result.contains("apple"));
        assertTrue(result.contains("banana"));
        assertTrue(result.contains("cherry"));
    }

    @Test
    void testListToStringWithValidInput() {
        List<String> input = Arrays.asList("apple", "banana", "cherry");
        
        String result = Utils.listToString(input);
        
        assertNotNull(result);
        assertEquals("apple,banana,cherry", result);
    }

    @Test
    void testListToStringWithEmptyList() {
        List<String> input = Arrays.asList();
        
        String result = Utils.listToString(input);
        
        assertNull(result);
    }

    @Test
    void testListToStringWithNull() {
        String result = Utils.listToString(null);
        
        assertNull(result);
    }

    @Test
    void testListToStringWithEmptyStrings() {
        List<String> input = Arrays.asList("apple", "", "banana", "cherry");
        
        String result = Utils.listToString(input);
        
        assertNotNull(result);
        // Current implementation handles empty strings correctly
        assertEquals("apple,,banana,cherry", result);
    }

    @Test
    void testStringToListAndListToStringRoundTrip() {
        String original = "apple,banana,cherry";
        
        List<String> list = Utils.stringToList(original, true);
        String result = Utils.listToString(list);
        
        assertNotNull(result);
        // Round trip should preserve the original string
        assertEquals("apple,banana,cherry", result);
    }

    // UUID Generation Tests
    
    @Test
    void testGetUUID() {
        String uuid1 = Utils.getUUID();
        String uuid2 = Utils.getUUID();
        
        assertNotNull(uuid1);
        assertNotNull(uuid2);
        assertNotEquals(uuid1, uuid2); // UUIDs should be unique
        
        // Validate UUID format (8-4-4-4-12 characters)
        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(uuid1).matches());
        assertTrue(uuidPattern.matcher(uuid2).matches());
    }

    @Test
    void testGetUUIDFromString() {
        String input = "test string";
        String uuid1 = Utils.getUUID(input);
        String uuid2 = Utils.getUUID(input);
        
        assertNotNull(uuid1);
        assertNotNull(uuid2);
        assertEquals(uuid1, uuid2); // Same input should produce same UUID
        
        // Validate UUID format
        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(uuid1).matches());
    }

    @Test
    void testGetUUIDFromStringWithDifferentInputs() {
        String input1 = "test string 1";
        String input2 = "test string 2";
        
        String uuid1 = Utils.getUUID(input1);
        String uuid2 = Utils.getUUID(input2);
        
        assertNotNull(uuid1);
        assertNotNull(uuid2);
        assertNotEquals(uuid1, uuid2); // Different inputs should produce different UUIDs
    }

    @Test
    void testGetUUIDFromEmptyString() {
        String input = "";
        String uuid = Utils.getUUID(input);
        
        assertNotNull(uuid);
        
        // Validate UUID format
        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());
    }

    // HTTP Request Tests (using Mockito for mocking external dependencies)
    
    @Test
    void testGetRequestSuccess() throws Exception {
//        String testUrl = "http://test.example.com";
        String expectedContent = "Test response content";
        
        // Mock HttpClient and ClientResponse
        HttpClient mockClient = mock(HttpClient.class);
        ClientResponse mockResponse = mock(ClientResponse.class);
        
        when(mockResponse.getContent()).thenReturn(expectedContent);
        when(mockClient.get(any(ClientRequest.class))).thenReturn(mockResponse);
        
        // We need to use static mocking or dependency injection to properly test this
        // For now, this test demonstrates the structure but may require PowerMock or refactoring
        // to make the HttpClient injectable for proper testing
        
        // Note: This test would need proper mocking framework setup to work fully
        // The current implementation creates HttpClient internally making it hard to mock
    }

    @Test
    void testGetRequestWithRetry() {
        // Test that demonstrates retry logic structure
//        String testUrl = "http://test.example.com";
        
        // This test would need proper mocking to simulate failures and retries
        // The current implementation has retry logic but it's tightly coupled
        assertDoesNotThrow(() -> {
            // Would need to mock HttpClient to properly test retry behavior
        });
    }

    @Test
    void testPostRequestSuccess() throws Exception {
//        String testUrl = "http://test.example.com";
//        String testBody = "{\"test\": \"data\"}";
        String expectedContent = "Test response content";
        
        // Similar to GET test - would need proper mocking framework
        // Mock HttpClient and ClientResponse
        HttpClient mockClient = mock(HttpClient.class);
        ClientResponse mockResponse = mock(ClientResponse.class);
        
        when(mockResponse.getContent()).thenReturn(expectedContent);
        when(mockClient.post(any(ClientRequest.class))).thenReturn(mockResponse);
        
        // Note: This test would need proper mocking framework setup to work fully
    }

    @Test
    void testPostRequestWithRetry() {
        // Test that demonstrates POST retry logic structure
//        String testUrl = "http://test.example.com";
//        String testBody = "{\"test\": \"data\"}";
        
        // This test would need proper mocking to simulate failures and retries
        assertDoesNotThrow(() -> {
            // Would need to mock HttpClient to properly test retry behavior
        });
    }

    // Integration-style tests that could work without mocking (but require actual HTTP endpoints)
    
    @Test
    void testGetRequestWithInvalidUrl() {
        String invalidUrl = "not-a-valid-url";
        
        // Test that invalid URL throws appropriate exception
        assertThrows(Exception.class, () -> {
            Utils.get(invalidUrl);
        });
    }

    @Test
    void testPostRequestWithInvalidUrl() {
        String invalidUrl = "not-a-valid-url";
        String testBody = "{\"test\": \"data\"}";
        
        // Test that invalid URL throws appropriate exception
        assertThrows(Exception.class, () -> {
            Utils.post(invalidUrl, testBody);
        });
    }

    @Test
    void testGetRequestWithNullUrl() {
        // Test null URL handling
        assertThrows(Exception.class, () -> {
            Utils.get(null);
        });
    }

    @Test
    void testPostRequestWithNullUrl() {
        String testBody = "{\"test\": \"data\"}";
        
        // Test null URL handling
        assertThrows(Exception.class, () -> {
            Utils.post(null, testBody);
        });
    }

    @Test
    void testPostRequestWithNullBody() {
        String testUrl = "http://test.example.com";
        
        // Test null body handling - should not throw exception during setup
        assertDoesNotThrow(() -> {
            // The method should handle null body gracefully in ClientRequest
            try {
                Utils.post(testUrl, null);
            } catch (IOException e) {
                // IOException is expected due to network call, but not NullPointerException
                assertTrue(e.getMessage().contains("Cannot download feed from url") || 
                          e.getMessage().contains("Connection") ||
                          e.getMessage().contains("timeout"));
            }
        });
    }
    
    @Test
    void testMergeListOfText_WithSingleText() {
        // Arrange
        List<String> texts = Arrays.asList("single line");

        // Act
        String result = Utils.getTextOfList(texts);

        // Assert
        assertEquals("single line", result);
    }

    @Test
    void testMergeListOfText_EmptyList() {
        // Arrange
        List<String> texts = Arrays.asList();

        // Act
        String result = Utils.getTextOfList(texts);

        // Assert
        assertEquals("", result);
    }

    @Test
    void testMergeListOfText_NullList() {
        // Act
        String result = Utils.getTextOfList(null);

        // Assert
        assertNull(result);
    }
    
}