package de.renew.draw.ui.impl.services;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.ui.api.EditorApi;
import de.renew.draw.ui.api.services.UndoRedoService;
import de.renew.draw.ui.ontology.DrawingEditor;
import de.renew.draw.ui.ontology.undoredo.UndoRedoManager;

import static org.mockito.ArgumentMatchers.any;
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;

class UndoRedoServiceImplTest {

    private static final Drawing CURRENT_DRAWING = mock(Drawing.class);
    private static final Drawing DUMMY_DRAWING = mock(Drawing.class);

    private MockedStatic<EditorApi> _mockedEditorApi;

    private UndoRedoService _undoRedoService;
    private DrawingEditor _mockedDrawingEditor;
    private UndoRedoManager _mockedUndoRedoManager;

    @BeforeEach
    void setUp() {
        _mockedEditorApi = mockStatic(EditorApi.class);
        _mockedDrawingEditor = mock(DrawingEditor.class);
        _mockedUndoRedoManager = mock(UndoRedoManager.class);
        _mockedEditorApi.when(EditorApi::getCurrentEditor).thenReturn(_mockedDrawingEditor);
        _mockedEditorApi.when(EditorApi::getCurrentDrawing).thenReturn(CURRENT_DRAWING);
        _undoRedoService = new UndoRedoServiceImpl();
    }

    @AfterEach
    void tearDown() {
        _mockedEditorApi.close();
        _mockedEditorApi = null;
        _mockedDrawingEditor = null;
        _undoRedoService = null;
    }

    @Test
    void testPrepareUndoSnapshot() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.prepareUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager).prepareUndoSnapshot(CURRENT_DRAWING);
    }

    @Test
    void testPrepareUndoSnapshotWithNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.prepareUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager, never()).prepareUndoSnapshot(any());
    }

    @Test
    void testPrepareUndoSnapshotWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.prepareUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).prepareUndoSnapshot(DUMMY_DRAWING);
    }

    @Test
    void testPrepareUndoSnapshotWithDrawingAndNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.prepareUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).prepareUndoSnapshot(any());
    }

    @Test
    void testCommitUndoSnapshot() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.commitUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager).commitUndoSnapshot(CURRENT_DRAWING);
    }

    @Test
    void testCommitUndoSnapshotWithNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.commitUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager, never()).commitUndoSnapshot(any());
    }


    @Test
    void testCommitUndoSnapshotWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.commitUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).commitUndoSnapshot(DUMMY_DRAWING);
    }

    @Test
    void testCommitUndoSnapshotWithDrawingAndNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.commitUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).commitUndoSnapshot(any());
    }

    @Test
    void testClearUndoRedoHistory() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.clearUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager).clearUndoHistory(CURRENT_DRAWING);
    }

    @Test
    void testClearUndoRedoHistoryWithNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.clearUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager, never()).clearUndoHistory(any());
    }

    @Test
    void testClearUndoRedoHistoryWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.clearUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).clearUndoHistory(DUMMY_DRAWING);
    }

    @Test
    void testClearUndoRedoHistoryWithDrawingAndNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.clearUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).clearUndoHistory(any());
    }

    @Test
    void testRestoreUndoSnapshot() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.restoreUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager).restoreUndoSnapshot(CURRENT_DRAWING);
    }

    @Test
    void testRestoreUndoSnapshotWithNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.restoreUndoSnapshot();

        // then
        verify(_mockedUndoRedoManager, never()).restoreUndoSnapshot(any());
    }

    @Test
    void testRestoreUndoSnapshotWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.restoreUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).restoreUndoSnapshot(DUMMY_DRAWING);

    }

    @Test
    void testRestoreUndoSnapshotWithDrawingAndNoUndoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.restoreUndoSnapshot(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).restoreUndoSnapshot(any());
    }

    /**
     * Tests the addition of an UndoRedoHistory without a drawing as a parameter and with an UndoRedoManager provided by the editor.
     */
    @Test
    void testAddUndoRedoHistory() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.addUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager).newUndoHistory(CURRENT_DRAWING);
    }

    /**
     * Tests the addition of an UndoRedoHistory without a drawing as a parameter and without an UndoRedoManager provided by the editor.
     */
    @Test
    void testAddUndoHistoryWithNoUndoRedoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.addUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager, never()).newUndoHistory(any());
    }

    /**
     * Tests the addition of an UndoRedoHistory with a drawing as a parameter and with an UndoRedoManager provided by the editor.
     */
    @Test
    void testAddUndoRedoHistoryWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.addUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).newUndoHistory(DUMMY_DRAWING);
    }

    /**
     * Tests the addition of an UndoRedoHistory with a drawing as a parameter and without an UndoRedoManager provided by the editor.
     */
    @Test
    void testAddUndoRedoHistoryWithDrawingAndNoUndoRedoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.addUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).newUndoHistory(any());
    }

    /**
     * Tests the removal of an UndoRedoHistory without a drawing as a parameter and with an UndoRedoManager provided by the editor.
     */
    @Test
    void testRemoveUndoRedoHistory() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.removeUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager).removeUndoHistory(CURRENT_DRAWING);
    }

    /**
     * Tests the removal of an UndoRedoHistory without a drawing as a parameter and without an UndoRedoManager provided by the editor.
     */
    @Test
    void testRemoveUndoHistoryWithNoUndoRedoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.removeUndoRedoHistory();

        // then
        verify(_mockedUndoRedoManager, never()).removeUndoHistory(any());
    }

    /**
     * Tests the removal of an UndoRedoHistory with a drawing as a parameter and with an UndoRedoManager provided by the editor.
     */
    @Test
    void testRemoveUndoRedoHistoryWithDrawing() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(_mockedUndoRedoManager);

        //when
        _undoRedoService.removeUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager).removeUndoHistory(DUMMY_DRAWING);
    }

    /**
     * Tests the removal of an UndoRedoHistory with a drawing as a parameter and without an UndoRedoManager provided by the editor.
     */
    @Test
    void testRemoveUndoRedoHistoryWithDrawingAndNoUndoRedoRedoManager() {
        //given
        when(_mockedDrawingEditor.getUndoRedoManager()).thenReturn(null);

        //when
        _undoRedoService.removeUndoRedoHistory(DUMMY_DRAWING);

        // then
        verify(_mockedUndoRedoManager, never()).removeUndoHistory(any());
    }
}