/**
 * Copyright © 2012-2025 by Data Virtuality GmbH
 */
package org.teiid.util;

import org.junit.jupiter.api.Test;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CoderResult;
import java.nio.charset.CharsetDecoder;

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

/**
 * Unit tests for the rewritten UTF-8-BOM decoder
 * Tests DVCORE-8725 fix: Add encoding as "UTF-8-BOM" in the file translator override property
 * 
 * @author Serhii Bashniak
 * @date 2025.09.16
 */
public class TestUtf8BomDecoderNew {

    private Charset getUtf8BomCharset() {
        return CharsetUtils.getCharset("UTF-8-BOM");
    }

    @Test
    public void testUtf8BomCharsetExists() {
        Charset utf8Bom = getUtf8BomCharset();
        assertNotNull(utf8Bom, "UTF-8-BOM charset should be available");
        assertEquals("UTF-8-BOM", utf8Bom.name());
    }

    @Test
    public void testSimpleAsciiWithBom() throws Exception {
        // BOM + "Hello"
        byte[] input = {(byte)0xEF, (byte)0xBB, (byte)0xBF, 'H', 'e', 'l', 'l', 'o'};
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("Hello", result);
        assertEquals(5, result.length());
        assertFalse(result.startsWith("\uFEFF"), "BOM should be stripped");
    }

    @Test
    public void testSimpleAsciiWithoutBom() throws Exception {
        // Just "Hello" without BOM
        byte[] input = {'H', 'e', 'l', 'l', 'o'};
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("Hello", result);
        assertEquals(5, result.length());
    }

    @Test
    public void testFrenchCharactersWithBom() throws Exception {
        // BOM + "Prénom" in UTF-8
        byte[] input = {
            (byte)0xEF, (byte)0xBB, (byte)0xBF,  // BOM
            0x50, 0x72, (byte)0xC3, (byte)0xA9, 0x6E, 0x6F, 0x6D  // "Prénom"
        };
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("Prénom", result);
        assertEquals(6, result.length());
        assertTrue(result.contains("é"));
        assertFalse(result.startsWith("\uFEFF"), "BOM should be stripped");
    }

    @Test
    public void testFrenchCharactersWithoutBom() throws Exception {
        // Just "Prénom" without BOM
        byte[] input = {0x50, 0x72, (byte)0xC3, (byte)0xA9, 0x6E, 0x6F, 0x6D};
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("Prénom", result);
        assertEquals(6, result.length());
        assertTrue(result.contains("é"));
    }

    @Test
    public void testComplexFrenchWithBom() throws Exception {
        // BOM + "François Cœur"
        String expected = "François Cœur";
        byte[] utf8Bytes = expected.getBytes(StandardCharsets.UTF_8);
        byte[] input = new byte[3 + utf8Bytes.length];
        input[0] = (byte)0xEF;
        input[1] = (byte)0xBB;
        input[2] = (byte)0xBF;
        System.arraycopy(utf8Bytes, 0, input, 3, utf8Bytes.length);
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals(expected, result);
        assertTrue(result.contains("ç"));
        assertTrue(result.contains("œ"));
        assertFalse(result.startsWith("\uFEFF"), "BOM should be stripped");
    }

    @Test
    public void testBomOnlyInput() throws Exception {
        // Just BOM, no content
        byte[] input = {(byte)0xEF, (byte)0xBB, (byte)0xBF};
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("", result);
        assertEquals(0, result.length());
    }

    @Test
    public void testPartialBomInput() throws Exception {
        // Only 2 bytes that look like start of BOM + valid UTF-8
        byte[] input = {(byte)0xEF, (byte)0xBB, 'A'};
        
        Charset utf8Bom = getUtf8BomCharset();
        
        try {
            String result = new String(input, utf8Bom);
            // If it doesn't throw, check that it's not just "A"
            assertNotEquals("A", result); // Because 0xEF 0xBB 'A' is not valid UTF-8
        } catch (Exception e) {
            // It's acceptable for this to throw an exception since it's malformed
            assertTrue(e instanceof IllegalArgumentException || 
                      e.getCause() instanceof java.nio.charset.MalformedInputException,
                      "Should fail with appropriate exception for malformed input");
        }
    }

    @Test
    public void testEmptyInput() throws Exception {
        byte[] input = {};
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals("", result);
        assertEquals(0, result.length());
    }

    @Test
    public void testCompareWithRegularUtf8() throws Exception {
        String text = "café français";
        byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);
        
        // Create input with BOM
        byte[] inputWithBom = new byte[3 + utf8Bytes.length];
        inputWithBom[0] = (byte)0xEF;
        inputWithBom[1] = (byte)0xBB;
        inputWithBom[2] = (byte)0xBF;
        System.arraycopy(utf8Bytes, 0, inputWithBom, 3, utf8Bytes.length);
        
        // Test UTF-8-BOM (should strip BOM)
        Charset utf8Bom = getUtf8BomCharset();
        String bomResult = new String(inputWithBom, utf8Bom);
        
        // Test regular UTF-8 (should include BOM as character)
        String utf8Result = new String(inputWithBom, StandardCharsets.UTF_8);
        
        assertEquals(text, bomResult);
        assertNotEquals(text, utf8Result);
        assertTrue(utf8Result.startsWith("\uFEFF"));
        assertFalse(bomResult.startsWith("\uFEFF"));
    }

    @Test
    public void testMultipleBoms() throws Exception {
        // Two BOMs + "Hi"
        byte[] input = {
            (byte)0xEF, (byte)0xBB, (byte)0xBF,  // First BOM (should be stripped)
            (byte)0xEF, (byte)0xBB, (byte)0xBF,  // Second BOM (should remain as content)
            'H', 'i'
        };
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        // Only first BOM should be stripped
        assertNotEquals("Hi", result);
        assertTrue(result.length() > 2);
        assertTrue(result.endsWith("Hi"));
    }

    @Test
    public void testLargeContentWithBom() throws Exception {
        StringBuilder largeText = new StringBuilder();
        for (int i = 0; i < 1000; i++) {
            largeText.append("Émile café ").append(i).append(" ");
        }
        String expected = largeText.toString();
        
        byte[] utf8Bytes = expected.getBytes(StandardCharsets.UTF_8);
        byte[] input = new byte[3 + utf8Bytes.length];
        input[0] = (byte)0xEF;
        input[1] = (byte)0xBB;
        input[2] = (byte)0xBF;
        System.arraycopy(utf8Bytes, 0, input, 3, utf8Bytes.length);
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals(expected, result);
        assertTrue(result.contains("Émile"));
        assertTrue(result.contains("café"));
        assertFalse(result.startsWith("\uFEFF"));
    }

    @Test
    public void testDecoderDirectly() throws Exception {
        Charset utf8Bom = getUtf8BomCharset();
        CharsetDecoder decoder = utf8Bom.newDecoder();
        
        // Test with BOM + "Test"
        byte[] input = {(byte)0xEF, (byte)0xBB, (byte)0xBF, 'T', 'e', 's', 't'};
        ByteBuffer inBuffer = ByteBuffer.wrap(input);
        CharBuffer outBuffer = CharBuffer.allocate(10);
        
        CoderResult result = decoder.decode(inBuffer, outBuffer, true);
        decoder.flush(outBuffer);
        
        assertTrue(result.isUnderflow() || result.isOverflow());
        outBuffer.flip();
        String decoded = outBuffer.toString();
        
        assertEquals("Test", decoded);
        assertFalse(decoded.startsWith("\uFEFF"));
    }

    @Test
    public void testDecoderReset() throws Exception {
        Charset utf8Bom = getUtf8BomCharset();
        CharsetDecoder decoder = utf8Bom.newDecoder();
        
        // First decode with BOM
        byte[] input1 = {(byte)0xEF, (byte)0xBB, (byte)0xBF, 'A'};
        ByteBuffer inBuffer1 = ByteBuffer.wrap(input1);
        CharBuffer outBuffer1 = CharBuffer.allocate(5);
        
        decoder.decode(inBuffer1, outBuffer1, true);
        decoder.flush(outBuffer1);
        outBuffer1.flip();
        assertEquals("A", outBuffer1.toString());
        
        // Reset decoder
        decoder.reset();
        
        // Second decode with BOM should work again
        byte[] input2 = {(byte)0xEF, (byte)0xBB, (byte)0xBF, 'B'};
        ByteBuffer inBuffer2 = ByteBuffer.wrap(input2);
        CharBuffer outBuffer2 = CharBuffer.allocate(5);
        
        decoder.decode(inBuffer2, outBuffer2, true);
        decoder.flush(outBuffer2);
        outBuffer2.flip();
        assertEquals("B", outBuffer2.toString());
    }

    @Test
    public void testCsvScenario() throws Exception {
        // Real-world CSV scenario from QA
        String csvContent = "ID;Nom;Prénom;Âge\r\n1;Dupont;François;25\r\n2;Martin;Céline;30\r\n";
        byte[] utf8Bytes = csvContent.getBytes(StandardCharsets.UTF_8);
        
        // Add BOM
        byte[] input = new byte[3 + utf8Bytes.length];
        input[0] = (byte)0xEF;
        input[1] = (byte)0xBB;
        input[2] = (byte)0xBF;
        System.arraycopy(utf8Bytes, 0, input, 3, utf8Bytes.length);
        
        Charset utf8Bom = getUtf8BomCharset();
        String result = new String(input, utf8Bom);
        
        assertEquals(csvContent, result);
        assertTrue(result.contains("Prénom"));
        assertTrue(result.contains("François"));
        assertTrue(result.contains("Céline"));
        assertTrue(result.contains("Âge"));
        assertFalse(result.contains("?"));
        assertFalse(result.startsWith("\uFEFF"));
    }
}