package de.uni_hamburg.fs;

import org.junit.jupiter.api.Test;

import de.renew.util.Value;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class BasicTypeTest {

    @Test
    void testConstructorWithValidPrimitiveType() {
        // given
        Class<?> primitiveType = int.class;

        // when
        BasicType basicType = new BasicType(primitiveType);

        // then
        assertThat(basicType.getJavaClass()).isEqualTo(primitiveType);
        assertThat(basicType.isInstanceType()).isFalse();
    }

    @Test
    void testConstructorWithString() {
        // given
        Class<?> stringType = String.class;

        // when
        BasicType basicType = new BasicType(stringType);

        // then
        assertThat(basicType.getJavaClass()).isEqualTo(stringType);
        assertThat(basicType.getName()).isEqualTo("String");
    }

    @Test
    void testConstructorWithInvalidType() {
        // given
        Class<?> invalidType = Integer.class;

        // when & then
        assertThatThrownBy(() -> new BasicType(invalidType)).isInstanceOf(RuntimeException.class);
    }

    @Test
    void testConstructorWithSingleValue() {
        // given
        String testValue = "test";

        // when
        BasicType basicType = new BasicType(testValue);

        // then
        assertThat(basicType.isObject()).isTrue();
        assertThat(basicType.getJavaClass()).isEqualTo(String.class);
        assertThat(basicType.getName()).isEqualTo("\"test\"");
    }

    @Test
    void testConstructorThrowsTypeExceptionWhenUpperLessThanLower() {
        // given
        Value upperValue = new Value(5);
        Value lowerValue = new Value(10);

        // when & then
        assertThatThrownBy(() -> new BasicType(lowerValue, upperValue))
            .isInstanceOf(TypeException.class);
    }

    @Test
    void testIsObjectWithRange() throws TypeException {
        // given
        Value lowerValue = new Value(1);
        Value upperValue = new Value(10);

        // when
        BasicType basicType = new BasicType(lowerValue, upperValue);

        // then
        assertThat(basicType.isObject()).isFalse();
    }

    @Test
    void testCanUnifyWithCompatibleTypes() {
        // given
        BasicType stringType = new BasicType(String.class);
        BasicType stringValue = new BasicType("test");

        // when
        boolean result = stringType.canUnify(stringValue);

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

    @Test
    void testCanUnifyWithIncompatibleTypes() {
        // given
        BasicType stringType = new BasicType(String.class);
        BasicType intType = new BasicType(int.class);

        // when
        boolean result = stringType.canUnify(intType);

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

    @Test
    void testSubsumesWithCompatibleTypes() throws TypeException {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(10);

        BasicType type1 = new BasicType(int.class);
        BasicType type2 = new BasicType(value1, value2);

        // when
        boolean result = type1.subsumes(type2);

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

    @Test
    void testSubsumesWithIncompatibleTypes() {
        // given
        BasicType stringType = new BasicType(String.class);
        BasicType intType = new BasicType(int.class);

        // when
        boolean result = stringType.subsumes(intType);

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

    @Test
    void testUnifyWithCompatibleTypes() throws UnificationFailure {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(10);
        Value value3 = new Value(5);
        Value value4 = new Value(15);

        BasicType type1 = new BasicType(value1, value2);
        BasicType type2 = new BasicType(value3, value4);

        // when
        Type result = type1.unify(type2);

        // then
        assertThat(result).isNotNull();
        assertThat(result).isInstanceOf(BasicType.class);
    }

    @Test
    void testUnifyFailsWithIncompatibleTypes() {
        // given
        BasicType stringType = new BasicType(String.class);
        BasicType intType = new BasicType(int.class);

        // when & then
        assertThatThrownBy(() -> stringType.unify(intType)).isInstanceOf(UnificationFailure.class);
    }

    @Test
    void testUnifyFailsWithNonOverlappingRanges() throws TypeException {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(5);
        Value value3 = new Value(10);
        Value value4 = new Value(15);

        BasicType range1 = new BasicType(value1, value2);
        BasicType range2 = new BasicType(value3, value4);

        // when & then
        assertThatThrownBy(() -> range1.unify(range2)).isInstanceOf(UnificationFailure.class);
    }

    @Test
    void testGetInstanceType() throws TypeException {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(10);

        BasicType basicType = new BasicType(value1, value2);

        // when
        Type instanceType = basicType.getInstanceType();

        // then
        assertThat(instanceType).isInstanceOf(BasicType.class);
        assertThat(instanceType.isInstanceType()).isTrue();
    }

    @Test
    void testGetJavaObjectForSingleValue() {
        // given
        String testValue = "test";
        BasicType basicType = new BasicType(testValue);

        // when
        Object result = basicType.getJavaObject();

        // then
        assertThat(result).isEqualTo(testValue);
    }

    @Test
    void testGetJavaObjectForRange() throws TypeException {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(10);

        BasicType basicType = new BasicType(value1, value2);

        // when & then
        assertThatThrownBy(basicType::getJavaObject).isInstanceOf(RuntimeException.class);
    }

    @Test
    void testCompareWithNegInfAndPosInf() {
        // given
        Object negInf = BasicType.NEGINF;
        Object posInf = BasicType.POSINF;
        BasicType type = new BasicType(int.class);

        // when
        int result1 = type.compare(negInf, posInf);
        int result2 = type.compare(posInf, negInf);

        // then
        assertThat(result1).isLessThan(0);
        assertThat(result2).isGreaterThan(0);
    }

    @Test
    void testEqualsAndHashCode() throws TypeException {
        // given
        Value value1 = new Value(1);
        Value value2 = new Value(10);

        BasicType type1 = new BasicType(value1, value2);
        BasicType type2 = new BasicType(value1, value2);
        BasicType type3 = new BasicType(new Value(1), new Value(5));

        // when & then
        assertThat(type1).isEqualTo(type2);
        assertThat(type1.hashCode()).isEqualTo(type2.hashCode());
        assertThat(type1).isNotEqualTo(type3);
    }
}