import org.postgresql.core.BackendMessageDecoder;
import org.postgresql.core.DecoderInputStream;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.mockito.InOrder;
import java.io.BufferedInputStream;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayInputStream;
import org.postgresql.core.BackendMessage;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.mockito.Mockito;
import org.junit.jupiter.api.extension.ExtendWith;
import java.io.InputStream;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.ArgumentMatchers.anyInt;
import java.util.ArrayList;
import java.io.IOException;
import org.junit.jupiter.api.DisplayName;
import java.util.List;

@ExtendWith(MockitoExtension.class)
public class GeneratedTests() {

    /** A DecoderInputStream that does not support marking. */
    static class NonMarkingDecoderInputStream extends DecoderInputStream {
        NonMarkingDecoderInputStream() {
            super(new ByteArrayInputStream(new byte[0]));
        }
        @Override
        public boolean markSupported() {
            return false;
        }
        @Override
        public void mark(int readlimit) {
            throw new UnsupportedOperationException("mark not supported");
        }
        @Override
        public int read() throws IOException {
            return -1;
        }
    }
    /** A minimal message implementation used for stubbing. */
    private static class Message {
        private final String payload;
        Message(String payload) { this.payload = payload; }
    }
    /**
     * A test double for {@link InputStream} that records calls to {@code mark} and {@code reset}.
     */
    private static class RecordingInputStream extends InputStream {
        final List<String> events = new ArrayList<>();
        @Override
        public int read() throws IOException { return -1; }
        @Override
        public boolean markSupported() { return true; }
        @Override
        public void mark(int readlimit) { events.add("mark:" + readlimit); }
        @Override
        public void reset() throws IOException { events.add("reset"); }
    }
    /**
     * A concrete implementation of {@link Decoder} that delegates to a spy for {@code doDecode}.
     */
    private static class TestDecoder extends Decoder {
        private final Decoder delegate = spy(new Decoder());
        @Override
        protected Message doDecode(DecoderInputStream in, boolean flag) throws IOException {
            return delegate.doDecode(in, flag);
        }
    }
    /** Decoder that always returns {@code null} from {@code doDecode}. */
    private static class NullReturningDecoder extends BackendMessageDecoder {
        @Override
        protected Object doDecode(DecoderInputStream stream) throws IOException {
            return null;
        }
    }
    // Simple InputStream that records reset calls and current position
    static class SpyInputStream extends InputStream {
        private int pos = 0;
        private final int limit;
        private int markPos = 0;
        private int resetCalls = 0;
        SpyInputStream(int limit) { this.limit = limit; }
        @Override
        public int read() { return pos < limit ? pos++ : -1; }
        @Override
        public void mark(int readlimit) { markPos = pos; }
        @Override
        public synchronized void reset() { resetCalls++; pos = markPos; }
        int getResetCalls() { return resetCalls; }
        int getPosition() { return pos; }
    }
    // Minimal abstract decoder to allow stubbing doDecode
    abstract static class BackendMessageDecoder {
        public Object decode(InputStream stream, boolean more) throws IOException {
            Object msg = doDecode(stream, more);
            if (msg == null) {
                stream.reset();
            }
            return msg;
        }
        protected abstract Object doDecode(InputStream stream, boolean more) throws IOException;
    }

    static class NullDecoder extends BackendMessageDecoder {
        @Override protected Object doDecode(InputStream stream, boolean more) { return null; }
    }

    static class NonNullDecoder extends BackendMessageDecoder {
        @Override protected Object doDecode(InputStream stream, boolean more) { return new Object(); }
    }

    private static class TestDecoder extends BackendMessageDecoder {
        @Override
        protected void doDecode(InputStream stream) throws IOException {
            throw new IllegalStateException("boom");
        }
    }

    @Test

    void testScenario() {
        // Replace with actual test logic based on the scenario
        assertTrue(true);
    }

    @Test

    void nonBlockingReadOnNonMarkingStreamThrowsIllegalArgumentException() {
        // Arrange
        DecoderInputStream nonMarkingStream = new NonMarkingDecoderInputStream();
        BackendMessageDecoder decoder = new BackendMessageDecoder() {
            @Override
            protected BackendMessage doDecode(DecoderInputStream stream, int length) {
                // No-op stub
                return null;
            }
        };
        // Act & Assert
        IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
                () -> decoder.decode(nonMarkingStream, false),
                "Expected IllegalArgumentException when decoding with block=false on non‑marking stream");
        String msg = ex.getMessage();
        assertTrue(msg.contains("block == false") || msg.contains("markSupported"),
                "Exception message should contain debugging information: " + msg);
    }

    @Test

    void BlockingReadOnNonMarkingStream_NoExceptionMarkNoOp() throws IOException {
        // Arrange: stream that does not support marking
        InputStream nonMarkingStream = spy(new InputStream() {
            @Override
            public int read() throws IOException {
                return -1; // EOF
            }
            @Override
            public boolean markSupported() {
                return false;
            }
        });
        AbstractBackendMessage dummyMessage = mock(AbstractBackendMessage.class);
        // DecoderInputStream with overridden doDecode to return dummyMessage
        DecoderInputStream decoder = new DecoderInputStream() {
            @Override
            protected AbstractBackendMessage doDecode(InputStream in, boolean blocking) throws IOException {
                return dummyMessage;
            }
        };
        // Act
        AbstractBackendMessage result = decoder.decode(nonMarkingStream, true);
        // Assert
        assertSame(dummyMessage, result);
        verify(nonMarkingStream, times(1)).mark(Integer.MAX_VALUE);
        verify(nonMarkingStream, never()).reset();
    }

    @Test

    void markCalledWithMaxValueOnSuccessfulDecode() throws IOException {
        // Arrange
        RecordingInputStream markingStream = new RecordingInputStream();
        DecoderInputStream decoderInput = new DecoderInputStream(markingStream);
        TestDecoder decoder = new TestDecoder();
        // Stub doDecode to return a non-null message
        Message stubMessage = new Message("ok");
        doReturn(stubMessage).when(decoder.delegate).doDecode(any(), anyBoolean());
        // Act
        Message result = decoder.decode(decoderInput, true);
        // Assert
        assertSame(stubMessage, result);
        assertEquals(List.of("mark:2147483647"), markingStream.events);
        assertEquals(0, markingStream.events.stream()
                .filter(e -> e.equals("reset")).count());
    }

    @Test

    void resetCalledOnlyWhenDoDecodeReturnsNullOrThrowsIOException() throws IOException {
        // Arrange
        RecordingInputStream markingStream = new RecordingInputStream();
        DecoderInputStream decoderInput = new DecoderInputStream(markingStream);
        TestDecoder decoder = new TestDecoder();
        // Case 1: doDecode returns null
        doReturn(null).when(decoder.delegate).doDecode(any(), anyBoolean());
        decoder.decode(decoderInput, true);
        assertEquals(List.of("mark:2147483647", "reset"), markingStream.events);
        markingStream.events.clear();
        // Case 2: doDecode throws IOException
        doThrow(new IOException("boom")).when(decoder.delegate).doDecode(any(), anyBoolean());
        assertThrows(IOException.class, () -> decoder.decode(decoderInput, true));
        assertEquals(List.of("mark:2147483647", "reset"), markingStream.events);
    }

    @Test

    void SuccessfulDecode_ReturnsMessage_NoReset() throws IOException {
        // Arrange
        byte[] data = new byte[]{0, 1, 2};
        DecoderInputStream markingStream = new DecoderInputStream(
                new ByteArrayInputStream(data));
        DecoderInputStream streamSpy = Mockito.spy(markingStream);
        AbstractBackendMessage stubMessage = new AbstractBackendMessage() {};
        BackendMessageDecoder decoderSpy = Mockito.spy(new BackendMessageDecoder());
        doReturn(stubMessage).when(decoderSpy).doDecode(any());
        // Act
        AbstractBackendMessage result = decoderSpy.decode(streamSpy, true);
        // Assert
        assertSame(stubMessage, result);
        verify(streamSpy, never()).reset();
    }

    @Test

    void DecodeReturnsNull_TriggersResetAndReturnsNull() throws IOException {
        // Spy on a marking DecoderInputStream
        DecoderInputStream markingStream = spy(mock(DecoderInputStream.class));
        // Use a decoder that returns null
        BackendMessageDecoder decoder = new NullReturningDecoder();
        // Invoke decode
        Object result = decoder.decode(markingStream, true);
        // Assertions
        assertNull(result);
        InOrder inOrder = inOrder(markingStream);
        inOrder.verify(markingStream, times(1)).reset();
        inOrder.verifyNoMoreInteractions();
    }

    @Test

    void IOExceptionFromDoDecode_PropagatesAndResets() throws Exception {
        // Arrange: create a marking DecoderInputStream and spy on it
        DecoderInputStream markingStream =
                Mockito.spy(new DecoderInputStream(new ByteArrayInputStream(new byte[0])));
        // Subclass BackendMessageDecoder to throw IOException from doDecode
        BackendMessageDecoder decoder = new BackendMessageDecoder() {
            @Override
            protected void doDecode(DecoderInputStream stream) throws IOException {
                throw new IOException("boom");
            }
        };
        // Act & Assert: decode should throw the same IOException
        IOException thrown =
                assertThrows(IOException.class, () -> decoder.decode(markingStream, true));
        assertEquals("boom", thrown.getMessage());
        // Verify that the stream was reset exactly once
        verify(markingStream, times(1)).reset();
        // No other interactions with the stream should occur
        verifyNoMoreInteractions(markingStream);
    }

    @Test

    void NullInputStream_ThrowsNullPointerException() {
        NullPointerException exception = assertThrows(
                NullPointerException.class,
                () -> Decoder.decode(null, true));
        boolean containsMark = false;
        for (StackTraceElement element : exception.getStackTrace()) {
            if ("mark".equals(element.getMethodName())) {
                containsMark = true;
                break;
            }
        }
        assertTrue(containsMark, "Stack trace should reference input.mark()");
    }

    @Test

    void markCalledBeforeDoDecode_OrderingGuarantee() {
        InputStream stream = mock(InputStream.class);
        when(stream.markSupported()).thenReturn(true);
        Decoder decoder = spy(new Decoder());
        doReturn("decoded").when(decoder).doDecode(any(InputStream.class));
        InOrder inOrder = inOrder(stream, decoder);
        decoder.decode(stream, true);
        inOrder.verify(stream).mark(Integer.MAX_VALUE);
        inOrder.verify(decoder).doDecode(stream);
    }

    @Test

    void MultipleConsecutiveDecodes_IndependentState() throws Exception {
        SpyInputStream realStream = new SpyInputStream(100);
        InputStream stream = Mockito.spy(realStream);
        BackendMessageDecoder decoder1 = new NullDecoder();
        BackendMessageDecoder decoder2 = new NonNullDecoder();
        // First decode should return null and trigger a reset
        Object result1 = decoder1.decode(stream, true);
        assertNull(result1);
        assertEquals(1, realStream.getResetCalls(), "First decode should trigger one reset");
        int posAfterFirstReset = realStream.getPosition();
        // Second decode should return a non‑null message and NOT trigger another reset
        Object result2 = decoder2.decode(stream, true);
        assertNotNull(result2);
        assertEquals(1, realStream.getResetCalls(), "Second decode should not trigger reset");
        int posAfterSecond = realStream.getPosition();
        assertEquals(posAfterFirstReset, posAfterSecond, "Stream position should be unchanged after second decode");
    }

    @Test

    void LargeReadLimit_NoOverflowOrPerformanceIssue() {
        byte[] data = new byte[1024]; // small sample data
        try (InputStream base = new ByteArrayInputStream(data);
             BufferedInputStream buffered = new BufferedInputStream(base)) {
            assertDoesNotThrow(() -> {
                // Mark with the largest possible read limit
                buffered.mark(Integer.MAX_VALUE);
                // Simulate a decode operation that just reads the stream
                int totalRead = 0;
                int b;
                while ((b = buffered.read()) != -1) {
                    totalRead++;
                }
                // Optionally, verify all data was read
                assert totalRead == data.length;
            });
        } catch (IOException e) {
            // In case of an unexpected IOException, fail the test
            throw new RuntimeException(e);
        }
    }

    @Test

    void testRuntimeExceptionFromDoDecodeDoesNotGuaranteeReset() {
        InputStream original = new ByteArrayInputStream(new byte[] {1, 2, 3});
        InputStream spyStream = Mockito.spy(original);
        BackendMessageDecoder decoder = new TestDecoder();
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
            decoder.decode(spyStream, true);
        });
        assertEquals("boom", thrown.getMessage());
        // Verify that reset() was invoked on the stream
        verify(spyStream, atLeastOnce()).reset();
    }

    @Test

    void BlockFlagTrue_StillMarksButDoesNotGuardAgainstMarkSupport() throws IOException {
        InputStream nonMarkingStream = new InputStream() {
            @Override
            public int read() throws IOException {
                return -1;
            }
            @Override
            public boolean markSupported() {
                return false;
            }
        };
        InputStream spyStream = Mockito.spy(nonMarkingStream);
        assertDoesNotThrow(() -> Decoder.decode(spyStream, true));
        verify(spyStream).mark(anyInt());
        verify(spyStream, never()).reset();
    }

    @Test

    @DisplayName("Decoder uses mark() when block flag is true, even if markSupported() is false")

    void decode_BlockFlagTrue_UsesMarkEvenIfMarkNotSupported() throws IOException {
        InputStream mockStream = mock(InputStream.class);
        when(mockStream.markSupported()).thenReturn(false);
        when(mockStream.read()).thenReturn(42, -1);
        assertDoesNotThrow(() -> Decoder.decode(mockStream, true));
        verify(mockStream, times(1)).mark(anyInt());
        verify(mockStream, never()).reset();
        verify(mockStream).markSupported();
    }
}
