package de.uni_hamburg.fs;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

class FeatureStructureTest {

    @Test
    void testConstructorWithType() {
        // given
        Type mockType = mock(Type.class);
        Node mockNode = mock(Node.class);
        when(mockType.newNode()).thenReturn(mockNode);
        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);

        // when
        FeatureStructure fs = new FeatureStructure(mockType);

        // then
        assertThat(fs).returns(mockNode, FeatureStructure::getRoot)
            .returns(mockType, FeatureStructure::getType);
    }

    @Test
    void testConstructorWithNode() {
        // given
        Node mockNode = mock(Node.class);
        Type mockType = mock(Type.class);
        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);

        // when
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        // then
        assertThat(fs.getRoot()).isSameAs(mockNode);
    }

    @Test
    void testGetType() {
        // given
        Node mockNode = mock(Node.class);
        Type mockType = mock(Type.class);
        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        // when
        Type result = fs.getType();

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

    @Test
    void testGetRoot() {
        // given
        Node mockNode = mock(Node.class);
        Type mockType = mock(Type.class);
        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        // when
        Node result = fs.getRoot();

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

    @Test
    void testDeltaWithPath() throws NoSuchFeatureException {
        // given
        Node mockNode = mock(Node.class);
        Node mockNodeDelta = mock(Node.class);
        Type mockType = mock(Type.class);
        Path path = new Path("path1");

        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        when(mockNode.delta(path)).thenReturn(mockNodeDelta);

        FeatureStructure fs = new FeatureStructure(mockNode, false);

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

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

    @Test
    void testUnify() throws UnificationFailure {
        // given
        Node mockNode = mock(Node.class);
        Node mockNodeUnified = mock(Node.class);
        Type mockType = mock(Type.class);
        FeatureStructure mockFeatureStructure = mock(FeatureStructure.class);

        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        when(mockNodeUnified.getType()).thenReturn(mockType);
        when(mockNodeUnified.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);

        try (MockedStatic<EquivRelation> mockedEquiv = mockStatic(EquivRelation.class)) {
            mockedEquiv.when(() -> EquivRelation.unify(fs, mockFeatureStructure))
                .thenReturn(mockNodeUnified);

            // when
            FeatureStructure result = fs.unify(mockFeatureStructure);

            // then
            assertThat(result).isNotNull();
            assertThat(result.getRoot()).isSameAs(mockNodeUnified);
        }
    }

    @Test
    void testEquateWithPath() throws UnificationFailure {
        // given
        Node mockNode = mock(Node.class);
        Node mockNodeUnified = mock(Node.class);
        Type mockType = mock(Type.class);
        Path path1 = new Path("path1");
        Path path2 = new Path("path2");

        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        when(mockNodeUnified.getType()).thenReturn(mockType);
        when(mockNodeUnified.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);

        try (MockedStatic<EquivRelation> mockedEquiv = mockStatic(EquivRelation.class)) {
            mockedEquiv.when(() -> EquivRelation.unify(fs, path1, path2))
                .thenReturn(mockNodeUnified);

            // when
            FeatureStructure result = fs.equate(path1, path2);

            // then
            assertThat(result).isNotNull();
            assertThat(result.getRoot()).isSameAs(mockNodeUnified);
        }
    }

    @Test
    void testSubsumes() {
        // given
        Node mockNode = mock(Node.class);
        Type mockType = mock(Type.class);
        FeatureStructure mockFeatureStructure = mock(FeatureStructure.class);

        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        try (MockedStatic<Subsumption> mockedSubsumption = mockStatic(Subsumption.class)) {
            mockedSubsumption.when(() -> Subsumption.subsumes(fs, mockFeatureStructure))
                .thenReturn(true);

            // when
            boolean result = fs.subsumes(mockFeatureStructure);

            // then
            assertThat(result).isTrue();
        }
    }

    @Test
    void testCanUnify() {
        // given
        Node mockNode = mock(Node.class);
        Type mockType = mock(Type.class);
        FeatureStructure mockFeatureStructure = mock(FeatureStructure.class);

        when(mockNode.getType()).thenReturn(mockType);
        when(mockNode.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode, false);

        try (MockedStatic<EquivRelation> mockedEquiv = mockStatic(EquivRelation.class)) {
            mockedEquiv.when(() -> EquivRelation.canUnify(fs, mockFeatureStructure))
                .thenReturn(true);

            // when
            boolean result = fs.canUnify(mockFeatureStructure);

            // then
            assertThat(result).isTrue();
        }
    }

    @Test
    void testEquals() {
        // given
        Node mockNode1 = mock(Node.class);
        Type mockType = mock(Type.class);
        FeatureStructure mockFeatureStructure = mock(FeatureStructure.class); // Mock the other FS

        when(mockNode1.getType()).thenReturn(mockType);
        when(mockNode1.featureNames()).thenReturn(EmptyEnumeration.INSTANCE);
        FeatureStructure fs = new FeatureStructure(mockNode1, false);

        try (MockedStatic<Equality> mockedEquality = mockStatic(Equality.class)) {
            mockedEquality.when(() -> Equality.equals(fs, mockFeatureStructure)).thenReturn(true);

            // when
            boolean result = fs.equals(mockFeatureStructure);

            // then
            assertThat(result).isTrue();
        }
    }
}