package de.renew.net;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import de.renew.engine.thread.SimulationThreadPool;
import de.renew.net.serialisation.INetDeserializer;
import de.renew.net.serialisation.INetSerializer;
import de.renew.net.serialisation.NetDeserializer;
import de.renew.net.serialisation.NetSerializer;
import de.renew.simulator.api.ISimulationLockExecutor;
import de.renew.util.RenewObjectInputStream;
import de.renew.util.RenewObjectOutputStream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Integration test class for {@link NetSerializer} and {@link NetDeserializer}.
 */
@ExtendWith(MockitoExtension.class)
public class NetSerialisationTest {

    private static final String NET_1_NAME = "Net1";
    private static final String NET_2_NAME = "Net2";
    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;
    @Mock
    private INetLookup _netLookup;

    private INetSerializer _netSerializer;
    private INetDeserializer _netDeserializer;

    /**
     * Setup method to initialize this test environment.
     */
    @BeforeEach
    public void setUp() {
        ISimulationLockExecutor lock = mock(ISimulationLockExecutor.class);
        _simulationThreadPoolMockedStatic = mockStatic(SimulationThreadPool.class);
        _simulationThreadPoolMockedStatic.when(SimulationThreadPool::isSimulationThread)
            .thenReturn(true);

        _netSerializer = new NetSerializer(lock, _netLookup);
        _netDeserializer = new NetDeserializer(_netLookup);
    }

    private void setupKnownNets() {
        Net net1 = new Net(NET_1_NAME);
        Net net2 = new Net(NET_2_NAME);
        when(_netLookup.getAllKnownNets()).thenReturn(List.of(net1, net2));
    }

    /**
     * Teardown method to clean up this test environment.
     */
    @AfterEach
    public void tearDown() {
        _simulationThreadPoolMockedStatic.close();
    }

    /**
     * Integration test method for {@link NetSerializer#saveAllKnownNets} and
     * {@link NetDeserializer#loadNets}.
     *
     * @throws IOException if there is an error with the streams
     * @throws ClassNotFoundException if there is an error while loading the net
     */
    @Test
    public void testSaveAndLoadNets() throws IOException, ClassNotFoundException {
        //given
        setupKnownNets();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        //when
        _netSerializer.saveAllKnownNets(oos);
        oos.close();

        //given
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);

        //when
        _netDeserializer.loadNets(ois);
        ois.close();

        //then
        ArgumentCaptor<Net> argumentCaptor = ArgumentCaptor.forClass(Net.class);
        verify(_netLookup, times(2)).makeNetKnown(argumentCaptor.capture());
        assertThat(argumentCaptor.getAllValues().stream().map(Net::getName))
            .containsExactlyInAnyOrder(NET_1_NAME, NET_2_NAME);
    }

    /**
     * Integration test method for {@link NetSerializer#saveAllKnownNets} and
     * {@link NetDeserializer#loadNets} while using {@link RenewObjectInputStream}
     * and {@link RenewObjectOutputStream} instances to save and load the net.
     *
     * @throws IOException if there is an error with the streams
     * @throws ClassNotFoundException if there is an error while loading the net
     */
    @Test
    public void testSaveAndLoadNetsWithRenewStreams() throws IOException, ClassNotFoundException {
        //given
        setupKnownNets();

        RenewObjectOutputStream roos = mock(RenewObjectOutputStream.class);

        //when
        _netSerializer.saveAllKnownNets(roos);

        //then
        verify(roos).beginDomain(Net.class);
        verify(roos).writeDelayedObjects();
        verify(roos).endDomain(Net.class);

        //given
        RenewObjectInputStream rois = mock(RenewObjectInputStream.class);
        when(rois.readInt()).thenReturn(0);

        //when
        _netDeserializer.loadNets(rois);

        //then
        verify(rois).readDelayedObjects();
    }
}
