package de.renew.net.event;

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.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import de.renew.engine.thread.SimulationThreadPool;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith({ MockitoExtension.class })
@MockitoSettings(strictness = Strictness.LENIENT)
class PlaceEventListenerSetTest {

    @Mock
    private PlaceEventListener _mockListener1;

    @Mock
    private PlaceEventListener _mockListener2;

    @Mock
    private PlaceEvent _mockPlaceEvent;

    @Mock
    private TokenEvent _mockTokenEvent;

    private PlaceEventListenerSet _listenerSet;

    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;

    @BeforeEach
    void setUp() {
        _simulationThreadPoolMockedStatic = mockStatic(SimulationThreadPool.class);
        _simulationThreadPoolMockedStatic.when(SimulationThreadPool::isSimulationThread)
            .thenReturn(true);

        _listenerSet = new PlaceEventListenerSet();

        when(_mockListener1.wantSynchronousNotification()).thenReturn(true);
        when(_mockListener2.wantSynchronousNotification()).thenReturn(true);
    }

    @AfterEach
    public void tearDown() {
        _simulationThreadPoolMockedStatic.close();
    }

    @Test
    void testConstructor() {
        //given / when
        PlaceEventListenerSet newSet = new PlaceEventListenerSet();

        //then
        assertNotNull(newSet);
        assertDoesNotThrow(() -> newSet.markingChanged(_mockPlaceEvent));
        assertDoesNotThrow(() -> newSet.tokenAdded(_mockTokenEvent));
        assertDoesNotThrow(() -> newSet.tokenRemoved(_mockTokenEvent));
        assertDoesNotThrow(() -> newSet.tokenTested(_mockTokenEvent));
        assertDoesNotThrow(() -> newSet.tokenUntested(_mockTokenEvent));
    }

    @Test
    void testAddPlaceEventListenerValidListener() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.markingChanged(_mockPlaceEvent);

        //then
        verify(_mockListener1).markingChanged(_mockPlaceEvent);
    }

    @Test
    void testAddPlaceEventListenerNullListener() {
        //when / then
        assertDoesNotThrow(() -> _listenerSet.addPlaceEventListener(null));
    }

    @Test
    void testAddPlaceEventListenerSameListenerTwice() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.markingChanged(_mockPlaceEvent);

        //then
        verify(_mockListener1).markingChanged(_mockPlaceEvent);
    }

    @Test
    void testRemovePlaceEventListenerExistingListener() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.removePlaceEventListener(_mockListener1);

        //when
        _listenerSet.markingChanged(_mockPlaceEvent);

        //then
        verify(_mockListener1, never()).markingChanged(_mockPlaceEvent);
    }

    @Test
    void testRemovePlaceEventListenerNonExistentListener() {
        //when / then
        assertDoesNotThrow(() -> _listenerSet.removePlaceEventListener(_mockListener1));
    }

    @Test
    void testRemovePlaceEventListenerNullListener() {
        //when / then
        assertDoesNotThrow(() -> _listenerSet.removePlaceEventListener(null));
    }

    @Test
    void testMarkingChangedMultipleListeners() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener2);

        //when
        _listenerSet.markingChanged(_mockPlaceEvent);

        //then
        verify(_mockListener1).markingChanged(_mockPlaceEvent);
        verify(_mockListener2).markingChanged(_mockPlaceEvent);
    }

    @Test
    void testMarkingChangedNullEvent() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.markingChanged(null);

        //then
        verify(_mockListener1).markingChanged(null);
    }

    @Test
    void testTokenAddedMultipleListeners() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener2);

        //when
        _listenerSet.tokenAdded(_mockTokenEvent);

        //then
        verify(_mockListener1).tokenAdded(_mockTokenEvent);
        verify(_mockListener2).tokenAdded(_mockTokenEvent);
    }

    @Test
    void testTokenAddedNullEvent() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.tokenAdded(null);

        //then
        verify(_mockListener1).tokenAdded(null);
    }

    @Test
    void testTokenRemovedMultipleListeners() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener2);

        //when
        _listenerSet.tokenRemoved(_mockTokenEvent);

        //then
        verify(_mockListener1).tokenRemoved(_mockTokenEvent);
        verify(_mockListener2).tokenRemoved(_mockTokenEvent);
    }

    @Test
    void testTokenRemovedNullEvent() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.tokenRemoved(null);

        //then
        verify(_mockListener1).tokenRemoved(null);
    }

    @Test
    void testTokenTestedMultipleListeners() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener2);

        //when
        _listenerSet.tokenTested(_mockTokenEvent);

        //then
        verify(_mockListener1).tokenTested(_mockTokenEvent);
        verify(_mockListener2).tokenTested(_mockTokenEvent);
    }

    @Test
    void testTokenTestedNullEvent() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.tokenTested(null);

        //then
        verify(_mockListener1).tokenTested(null);
    }

    @Test
    void testTokenUntestedMultipleListeners() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);
        _listenerSet.addPlaceEventListener(_mockListener2);

        //when
        _listenerSet.tokenUntested(_mockTokenEvent);

        //then
        verify(_mockListener1).tokenUntested(_mockTokenEvent);
        verify(_mockListener2).tokenUntested(_mockTokenEvent);
    }

    @Test
    void testTokenUntestedNullEvent() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when
        _listenerSet.tokenUntested(null);

        //then
        verify(_mockListener1).tokenUntested(null);
    }

    @Test
    void testMarkingChangedListenerException() {
        // given
        PlaceEventListener faultyListener = mock(PlaceEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .markingChanged(_mockPlaceEvent);

        _listenerSet.addPlaceEventListener(faultyListener);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        assertDoesNotThrow(() -> _listenerSet.markingChanged(_mockPlaceEvent));

        verify(_mockListener1).markingChanged(_mockPlaceEvent);
        verify(faultyListener).markingChanged(_mockPlaceEvent);
    }

    @Test
    void testTokenAddedListenerException() {
        // given
        PlaceEventListener faultyListener = mock(PlaceEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .tokenAdded(_mockTokenEvent);

        _listenerSet.addPlaceEventListener(faultyListener);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        _listenerSet.tokenAdded(_mockTokenEvent);
    }

    @Test
    void testTokenRemovedListenerException() {
        // given
        PlaceEventListener faultyListener = mock(PlaceEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .tokenRemoved(_mockTokenEvent);

        _listenerSet.addPlaceEventListener(faultyListener);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        _listenerSet.tokenRemoved(_mockTokenEvent);
    }

    @Test
    void testTokenTestedListenerException() {
        // given
        PlaceEventListener faultyListener = mock(PlaceEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .tokenTested(_mockTokenEvent);

        _listenerSet.addPlaceEventListener(faultyListener);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        _listenerSet.tokenTested(_mockTokenEvent);
    }

    @Test
    void testTokenUntestedListenerException() {
        // given
        PlaceEventListener faultyListener = mock(PlaceEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .tokenUntested(_mockTokenEvent);

        _listenerSet.addPlaceEventListener(faultyListener);
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        _listenerSet.tokenUntested(_mockTokenEvent);
    }

    @Test
    void testAddAndRemovePlaceEventSynchronization() {
        //given
        _listenerSet.addPlaceEventListener(_mockListener1);

        //when / then
        assertDoesNotThrow(() -> {
            _listenerSet.markingChanged(_mockPlaceEvent);
            _listenerSet.addPlaceEventListener(_mockListener2);
            _listenerSet.removePlaceEventListener(_mockListener1);
        });

        verify(_mockListener1).markingChanged(_mockPlaceEvent);
    }
}