package de.renew.draw.storables.impl.util.internal;

import java.io.ObjectOutputStream;
import java.io.Serial;

import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;

import CH.ifa.draw.standard.StandardDrawing;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Storable;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;

/** This class is a test class for {@link StorableHelper}. */
class StorableHelperTest {
    /** Tests the cloning of a Storable. */
    @Test
    void testCloneStorable() throws Exception {
        //given
        Storable standardDrawing = new StandardDrawing();

        //when
        Storable clonedDrawing = StorableHelper.cloneStorable(standardDrawing);

        //then
        assertThat(clonedDrawing).isInstanceOf(StandardDrawing.class);
    }

    /** Tests if null can be cloned. */
    @Test
    void testCloneStorableCloneNull() throws Exception {
        //given/when
        Storable clonedDrawing = StorableHelper.cloneStorable(null);

        //then
        assertThat(clonedDrawing).isNull();
    }

    /** Tests that if a clone of a Storable fails, an Exception is thrown. */
    @Test
    void testCloneStorableThrowsExceptionWhenCloneFails() {
        //given
        Storable storable = mock(Storable.class);

        //when
        // mockedOOS is unused as variable but still needed to close the mock after the try block
        try (MockedConstruction<ObjectOutputStream> mockedOOS = mockConstruction(
            // For the scope of this try block when writeObject() is called an Exception will always be thrown
            ObjectOutputStream.class, (mock, context) -> doThrow(new Exception("Mocked Exception"))
                .when(mock).writeObject(any()))) {

            //then
            Exception exception =
                assertThrows(Exception.class, () -> StorableHelper.cloneStorable(storable));
            assertThat(exception.getMessage()).isEqualTo("Could not clone Storable.");
        }
    }

    /** Tests that if a clone of a Drawing fails, the name of the Drawing appears in the error message. */
    @Test
    void testCloneStorableThrowsExceptionWhenCloneOfDrawingFails() {
        //given
        String name = "ProblematicDrawing";
        Drawing problematicDrawing = new StandardDrawing() {
            @Serial
            private void writeObject(ObjectOutputStream oos) throws Exception {
                throw new Exception("Simulated failure");
            }

            @Override
            public String getName() {
                return name;
            }
        };

        //when/then
        Exception exception =
            assertThrows(Exception.class, () -> StorableHelper.cloneStorable(problematicDrawing));
        assertThat(exception.getMessage())
            .isEqualTo(String.format("Could not clone Storable: %s", problematicDrawing.getName()));
    }

}