package de.uni_hamburg.fs;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

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

import collections.CollectionEnumeration;

import de.renew.util.Value;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

class JavaObjectTest {

    @BeforeEach
    void setUp() {
        TypeSystem.newInstance();
    }

    @Test
    void testConstructorWithValidObject() {
        // given
        Object validObject = new ArrayList<>();

        // when
        JavaObject javaObject = new JavaObject(validObject);

        // then
        assertThat(javaObject).isNotNull();
        assertThat(javaObject.getJavaObject()).isSameAs(validObject);
    }

    @Test
    void testConstructorWithInvalidObjects() {
        // when & then
        assertThatThrownBy(() -> new JavaObject("test")).isInstanceOf(RuntimeException.class);
        assertThatThrownBy(() -> new JavaObject(new Value(42)))
            .isInstanceOf(RuntimeException.class);
        assertThatThrownBy(() -> new JavaObject(new int[0])).isInstanceOf(RuntimeException.class);
        assertThatThrownBy(() -> new JavaObject(mock(Enumeration.class)))
            .isInstanceOf(RuntimeException.class);
        assertThatThrownBy(() -> new JavaObject(mock(Iterator.class)))
            .isInstanceOf(RuntimeException.class);
    }

    @Test
    void testGetJavaTypeWithArrayAndCollections() {
        // given
        int[] array = new int[0];
        Enumeration<?> enumeration = mock(Enumeration.class);
        Iterator<?> iterator = mock(Iterator.class);

        // when
        JavaType arrayResult = JavaObject.getJavaType(array);
        JavaType enumerationResult = JavaObject.getJavaType(enumeration);
        JavaType iteratorResult = JavaObject.getJavaType(iterator);

        // then
        assertThat(arrayResult).isInstanceOf(JavaArrayType.class);
        assertThat(enumerationResult).isInstanceOf(JavaArrayType.class);
        assertThat(iteratorResult).isInstanceOf(JavaArrayType.class);
    }

    @Test
    void testGetJavaTypeWithObject() {
        // given
        Object regularObject = new ArrayList<>();

        // when
        JavaType result = JavaObject.getJavaType(regularObject);

        // then
        assertThat(result).isInstanceOf(JavaObject.class);
        assertThat((result).getJavaObject()).isSameAs(regularObject);
    }

    @Test
    void testHashCode() {
        // given
        Object testObject = new ArrayList<>();
        JavaObject javaObject = new JavaObject(testObject);

        // when
        int result = javaObject.hashCode();

        // then
        assertThat(result).isEqualTo(testObject.hashCode());
    }

    @Test
    void testEquals() {
        // given
        Object object1 = new ArrayList<>();
        Object object2 = new ArrayList<>();
        JavaObject javaObject1 = new JavaObject(object1);
        JavaObject javaObject2 = new JavaObject(object2);
        JavaObject javaObject3 = new JavaObject(new Dimension(10, 20));

        // when & then
        assertThat(javaObject1.equals(javaObject2)).isEqualTo(object1.equals(object2));
        assertThat(javaObject1.equals(javaObject3)).isFalse();
    }

    @Test
    void testDeltaWithPath() throws NoSuchFeatureException {
        // given
        JavaObject javaObject = spy(new JavaObject(new ArrayList<>()));
        Path path = mock(Path.class);
        Name feature1 = new Name("feature1");
        Name feature2 = new Name("feature2");
        CollectionEnumeration features = mock(CollectionEnumeration.class);

        doReturn(features).when(path).features();
        when(features.hasMoreElements()).thenReturn(true, true, false);
        when(features.nextElement()).thenReturn(feature1, feature2);

        Node intermediateNode = mock(Node.class);
        Node finalNode = mock(Node.class);

        doReturn(intermediateNode).when(javaObject).delta(feature1);
        when(intermediateNode.delta(feature2)).thenReturn(finalNode);

        // when
        Node result = javaObject.delta(path);

        // then
        assertThat(result).isSameAs(finalNode);
    }

    @Test
    void testDuplicate() {
        // given
        JavaObject javaObject = new JavaObject(new ArrayList<>());

        // when
        Node result = javaObject.duplicate();

        // then
        assertThat(result).isSameAs(javaObject);
    }
}