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 TransitionEventListenerSetTest {

    @Mock
    private TransitionEventListener _mockListener1;

    @Mock
    private TransitionEventListener _mockListener2;

    @Mock
    private FiringEvent _mockFiringEvent;

    private TransitionEventListenerSet _listenerSet;

    private MockedStatic<SimulationThreadPool> _simulationThreadPoolMockedStatic;

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

        _listenerSet = new TransitionEventListenerSet();

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

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

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

        //then
        assertNotNull(newSet);
        assertDoesNotThrow(() -> newSet.firingStarted(_mockFiringEvent));
        assertDoesNotThrow(() -> newSet.firingComplete(_mockFiringEvent));
    }

    @Test
    void testAddTransitionEventListenerValidListener() {
        //given / when
        _listenerSet.addTransitionEventListener(_mockListener1);
        _listenerSet.firingStarted(_mockFiringEvent);

        //then
        verify(_mockListener1).firingStarted(_mockFiringEvent);
    }

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

    @Test
    void testAddTransitionEventListenerSameListenerTwice() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);
        _listenerSet.addTransitionEventListener(_mockListener1);

        //when
        _listenerSet.firingStarted(_mockFiringEvent);

        //then
        verify(_mockListener1).firingStarted(_mockFiringEvent);
    }

    @Test
    void testRemoveTransitionEventListenerExistingListener() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);
        _listenerSet.removeTransitionEventListener(_mockListener1);

        //when
        _listenerSet.firingStarted(_mockFiringEvent);

        //then
        verify(_mockListener1, never()).firingStarted(_mockFiringEvent);
    }

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

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

    @Test
    void testFiringStartedMultipleListeners() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);
        _listenerSet.addTransitionEventListener(_mockListener2);

        //when
        _listenerSet.firingStarted(_mockFiringEvent);

        //then
        verify(_mockListener1).firingStarted(_mockFiringEvent);
        verify(_mockListener2).firingStarted(_mockFiringEvent);
    }

    @Test
    void testFiringStartedNullEvent() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);

        //when
        _listenerSet.firingStarted(null);

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

    @Test
    void testFiringCompleteMultipleListeners() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);
        _listenerSet.addTransitionEventListener(_mockListener2);

        //when
        _listenerSet.firingComplete(_mockFiringEvent);

        //then
        verify(_mockListener1).firingComplete(_mockFiringEvent);
        verify(_mockListener2).firingComplete(_mockFiringEvent);
    }

    @Test
    void testFiringCompleteNullEvent() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);

        //when
        _listenerSet.firingComplete(null);

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

    @Test
    void testFiringStartedListenerException() {
        // given
        TransitionEventListener faultyListener = mock(TransitionEventListener.class);
        when(faultyListener.wantSynchronousNotification()).thenReturn(true);
        doThrow(new RuntimeException("Test exception")).when(faultyListener)
            .firingStarted(_mockFiringEvent);

        _listenerSet.addTransitionEventListener(faultyListener);
        _listenerSet.addTransitionEventListener(_mockListener1);

        //when / then
        assertDoesNotThrow(() -> _listenerSet.firingStarted(_mockFiringEvent));

        verify(_mockListener1).firingStarted(_mockFiringEvent);
        verify(faultyListener).firingStarted(_mockFiringEvent);
    }

    @Test
    void testAndRemoveTransitionEventSynchronization() {
        //given
        _listenerSet.addTransitionEventListener(_mockListener1);

        //when
        assertDoesNotThrow(() -> {
            _listenerSet.firingStarted(_mockFiringEvent);
            _listenerSet.addTransitionEventListener(_mockListener2);
            _listenerSet.removeTransitionEventListener(_mockListener1);
        });

        //then
        verify(_mockListener1).firingStarted(_mockFiringEvent);
    }
}