package de.uni_hamburg.fs;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;

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

class IndexFeatureTest {

    @Test
    void testGetJavaClass() {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);
        Method mockAdd = mock(Method.class);

        doReturn(String.class).when(mockGet).getReturnType();

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, mockAdd);

        // when
        Class<?> result = indexFeature.getJavaClass();

        // then
        assertThat(result).isEqualTo(String[].class);
    }

    @Test
    void testGetObjectValue() throws IllegalAccessException, InvocationTargetException {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);
        Method mockAdd = mock(Method.class);
        Object testObject = new Object();

        doReturn(String.class).when(mockGet).getReturnType();
        when(mockGetCount.invoke(eq(testObject), eq(JavaConcept.NOPARAM))).thenReturn(2);
        when(mockGet.invoke(eq(testObject), eq(new Object[] { 0 }))).thenReturn("value1");
        when(mockGet.invoke(eq(testObject), eq(new Object[] { 1 }))).thenReturn("value2");

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, mockAdd);

        // when
        Object result = indexFeature.getObjectValue(testObject);

        // then
        assertThat(result).isInstanceOf(String[].class);
        String[] resultArray = (String[]) result;
        assertThat(resultArray).containsExactly("value1", "value2");
    }

    @Test
    void testCanSetWithAddMethod() {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);
        Method mockAdd = mock(Method.class);

        doReturn(String.class).when(mockGet).getReturnType();

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, mockAdd);

        // when
        boolean result = indexFeature.canSet();

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

    @Test
    void testCanSetWithoutAddMethod() {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);

        doReturn(String.class).when(mockGet).getReturnType();

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, null);

        // when
        boolean result = indexFeature.canSet();

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

    @Test
    void testSetObjectValue() throws IllegalAccessException, InvocationTargetException {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);
        Method mockAdd = mock(Method.class);
        Object testObject = new Object();
        String[] values = { "value1", "value2" };

        doReturn(String.class).when(mockGet).getReturnType();

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, mockAdd);

        // when
        indexFeature.setObjectValue(testObject, values);

        // then
        verify(mockAdd).invoke(testObject, "value1");
        verify(mockAdd).invoke(testObject, "value2");
    }

    @Test
    void testSetObjectValueWithException()
        throws IllegalAccessException, InvocationTargetException
    {
        // given
        Method mockGetCount = mock(Method.class);
        Method mockGet = mock(Method.class);
        Method mockAdd = mock(Method.class);
        Object testObject = new Object();
        String[] values = { "value1", "value2" };

        doReturn(String.class).when(mockGet).getReturnType();
        when(mockAdd.invoke(eq(testObject), any()))
            .thenThrow(new InvocationTargetException(new RuntimeException("Test exception")));

        IndexFeature indexFeature = new IndexFeature(mockGetCount, mockGet, mockAdd);

        // when & then
        assertThatThrownBy(() -> indexFeature.setObjectValue(testObject, values))
            .isInstanceOf(RuntimeException.class)
            .hasMessageContaining("Exception during feature setting:");
    }
}