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.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class BeanFeatureTest {

    @Test
    void testGetJavaClass() {
        // given
        Method mockGetter = mock(Method.class);
        Method mockSetter = mock(Method.class);
        Class<?> expectedClass = String.class;

        doReturn(expectedClass).when(mockGetter).getReturnType();

        BeanFeature beanFeature = new BeanFeature(mockGetter, mockSetter);

        // when
        Class<?> actualClass = beanFeature.getJavaClass();

        // then
        assertThat(actualClass).isSameAs(expectedClass);
    }

    @Test
    void testGetObjectValueSuccess() throws InvocationTargetException, IllegalAccessException {
        // given
        Method mockGetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, null);
        Object targetObject = new Object();
        String expectedValue = "testValue";

        when(mockGetter.invoke(targetObject, JavaConcept.NOPARAM)).thenReturn(expectedValue);

        // when
        Object actualValue = beanFeature.getObjectValue(targetObject);

        // then
        assertThat(actualValue).isSameAs(expectedValue);
    }

    @Test
    void testGetObjectValueIllegalAccess()
        throws InvocationTargetException, IllegalAccessException
    {
        // given
        Method mockGetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, null);
        Object targetObject = new Object();
        IllegalAccessException expectedException = new IllegalAccessException("Test Exception");

        when(mockGetter.invoke(targetObject, JavaConcept.NOPARAM)).thenThrow(expectedException);

        // when
        Object result = beanFeature.getObjectValue(targetObject);

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

    @Test
    void testGetObjectValueIllegalArgument()
        throws InvocationTargetException, IllegalAccessException
    {
        // given
        Method mockGetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, null);
        Object targetObject = new Object();
        IllegalArgumentException expectedException = new IllegalArgumentException("Test Exception");

        when(mockGetter.invoke(targetObject, JavaConcept.NOPARAM)).thenThrow(expectedException);

        // when
        Object result = beanFeature.getObjectValue(targetObject);

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

    @Test
    void testGetObjectValueInvocationTargetException()
        throws InvocationTargetException, IllegalAccessException
    {
        // given
        Method mockGetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, null);
        Object targetObject = new Object();

        RuntimeException targetException = new RuntimeException("Target exception");
        InvocationTargetException wrappedException = new InvocationTargetException(targetException);

        when(mockGetter.invoke(targetObject, JavaConcept.NOPARAM)).thenThrow(wrappedException);

        // when
        Object result = beanFeature.getObjectValue(targetObject);

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

    @Test
    void testCanSetTrue() {
        // given
        Method mockGetter = mock(Method.class);
        Method mockSetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, mockSetter);

        // when
        boolean canSet = beanFeature.canSet();

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

    @Test
    void testCanSetFalse() {
        // given
        Method mockGetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, null);

        // when
        boolean canSet = beanFeature.canSet();

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

    @Test
    void testSetObjectValueWhenNoSetterExists() {
        // given
        Method mockGetter = mock(Method.class);
        doReturn(Object.class).when(mockGetter).getDeclaringClass();
        when(mockGetter.toString()).thenReturn("mockGetterMethod()");

        BeanFeature beanFeature = new BeanFeature(mockGetter, null);
        Object targetObject = new Object();
        String valueToSet = "newValue";


        // when & then
        assertThatThrownBy(() -> beanFeature.setObjectValue(targetObject, valueToSet))
            .isInstanceOf(RuntimeException.class).hasMessageContaining("Cannot set attribute")
            .hasMessageContaining("No set method for");
    }

    @Test
    void testSetObjectValueSuccess() throws InvocationTargetException, IllegalAccessException {
        // given
        Method mockGetter = mock(Method.class);
        Method mockSetter = mock(Method.class);
        BeanFeature beanFeature = new BeanFeature(mockGetter, mockSetter);
        Object targetObject = new Object();
        String valueToSet = "newValue";

        // when
        beanFeature.setObjectValue(targetObject, valueToSet);

        // then
        verify(mockSetter).invoke(targetObject, valueToSet);
    }
}