package de.renew.shadowcompiler;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import de.renew.net.Net;
import de.renew.simulatorontology.loading.NetNotFoundException;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.ShadowNetLoader;
import de.renew.simulatorontology.shadow.ShadowNetSystem;

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

/**
 * Test class for {@link LoopbackNetLoader}.
 */
public class LoopbackNetLoaderTest {
    private static final String TEST_NET_NAME = "testNet";
    private static final String DIFFERENT_NET_NAME = "differentNet";

    private ShadowLookup _shadowLookup;
    private ShadowNetSystem _shadowNetSystemMock;
    private ShadowCompiler _shadowCompilerMock;
    private ShadowNetLoader _shadowNetLoader;
    private Net _netMock;
    private ShadowNet _shadowNet;

    @BeforeEach
    public void setUp() {
        _shadowLookup = new ShadowLookup();
        _shadowNetSystemMock = mock(ShadowNetSystem.class);
        ShadowCompilerFactory shadowCompilerFactoryMock = mock(ShadowCompilerFactory.class);
        _shadowCompilerMock = mock(ShadowCompiler.class);
        _shadowNetLoader = mock(ShadowNetLoader.class);
        _netMock = mock(Net.class);

        when(shadowCompilerFactoryMock.createCompiler()).thenReturn(_shadowCompilerMock);
        when(_shadowNetSystemMock.getNetLoader()).thenReturn(_shadowNetLoader);
        when(_shadowCompilerMock.createNet(TEST_NET_NAME)).thenReturn(_netMock);

        _shadowNet = new ShadowNet(TEST_NET_NAME, _shadowNetSystemMock);

        ShadowNetSystemCompiler.getInstance()
            .setDefaultCompilerFactory(shadowCompilerFactoryMock, _shadowNetSystemMock);
    }

    /**
     * Test method for {@link LoopbackNetLoader#loadNet(String)}.
     * @throws NetNotFoundException if no net could be found for the given name
     */
    @Test
    public void testLoadNet() throws NetNotFoundException {
        // Given
        when(_shadowNetLoader.loadShadowNet(TEST_NET_NAME, _shadowNetSystemMock))
            .thenReturn(_shadowNet);

        LoopbackNetLoader loopbackNetLoader =
            new LoopbackNetLoader(_shadowNetSystemMock, _shadowLookup);

        // When
        loopbackNetLoader.loadNet(TEST_NET_NAME);

        // Then
        verify(_shadowNetLoader).loadShadowNet(TEST_NET_NAME, _shadowNetSystemMock);
        verify(_shadowCompilerMock).createNet(TEST_NET_NAME);
        assertThat(_shadowLookup.getNet(TEST_NET_NAME)).isEqualTo(_netMock);
    }

    /**
     * Test method for {@link LoopbackNetLoader#loadNet(String)}.
     * This test verifies that the expected exception is thrown when the net cannot be found.
     *
     * @throws NetNotFoundException if no net could be found for the given name
     */
    @Test
    public void testLoadNetThrowsExceptionWhenNetNotFound() throws NetNotFoundException {
        // Given
        when(_shadowNetLoader.loadShadowNet(TEST_NET_NAME, _shadowNetSystemMock))
            .thenThrow(new NetNotFoundException("Test exception"));

        LoopbackNetLoader loopbackNetLoader =
            new LoopbackNetLoader(_shadowNetSystemMock, _shadowLookup);

        // When & Then
        assertThatExceptionOfType(NetNotFoundException.class)
            .isThrownBy(() -> loopbackNetLoader.loadNet(TEST_NET_NAME));
    }

    /**
     * Test method for {@link LoopbackNetLoader#loadNet(String)}.
     * This test verifies that the expected exception is thrown when the shadow net name does not match
     * the requested net name.
     *
     * @throws NetNotFoundException if no net could be found for the given name
     */
    @Test
    public void testLoadNetThrowsExceptionWhenShadowNetNameMismatch() throws NetNotFoundException {
        // Given
        ShadowNet shadowNet = new ShadowNet(DIFFERENT_NET_NAME, _shadowNetSystemMock);
        when(_shadowNetLoader.loadShadowNet(TEST_NET_NAME, _shadowNetSystemMock))
            .thenReturn(shadowNet);

        LoopbackNetLoader loopbackNetLoader =
            new LoopbackNetLoader(_shadowNetSystemMock, _shadowLookup);

        // When & Then
        assertThatExceptionOfType(NetNotFoundException.class)
            .isThrownBy(() -> loopbackNetLoader.loadNet(TEST_NET_NAME)).withMessageContaining(
                TEST_NET_NAME + " (ShadowNetLoader provided " + DIFFERENT_NET_NAME + " instead)");
    }
}