package de.accso.flexinale.common.shared_kernel;

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

import java.util.Set;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

class SynchronizedSetTest {
    private SynchronizedSet<String> set;

    @BeforeEach
    public void setUp() {
        set = new SynchronizedSet<>();
    }

    @Test
    public void testAdd() {
        // arrange, act, assert
        assertThat(set.add("Hello")).isTrue();
        assertThat(set.add("Hello")).isFalse();
        assertThat(set.add("World")).isTrue();
    }

    @Test
    public void testAddAll() {
        // arrange
        Set<String> collection = Set.of("Hello", "World");

        // act, assert
        assertThat(set.addAll(collection)).isTrue();
        assertThat(set.contains("Hello")).isTrue();
        assertThat(set.contains("World")).isTrue();
        assertThat(set.contains("NonExistent")).isFalse();
    }

    @Test
    public void testRemove() {
        // arrange
        set.add("Hello");
        set.add("World");

        // act, assert
        assertThat(set.remove("Hello")).isTrue();
        assertThat(set.remove("Hello")).isFalse();
        assertThat(set.remove("NonExistent")).isFalse();
    }

    @Test
    public void testRemoveAll() {
        // arrange
        set.add("Hello");
        set.add("World");
        set.add("Something");

        Set<String> collection = Set.of("Hello", "World");

        // act, assert
        assertThat(set.removeAll(collection)).isTrue();
        assertThat(set.contains("Hello")).isFalse();
        assertThat(set.contains("World")).isFalse();
        assertThat(set.remove("Something")).isTrue();
    }

    @Test
    public void testClear() {
        // arrange
        set.add("Hello");
        set.add("World");

        // act
        set.clear();

        // assert
        assertThat(set.contains("Hello")).isFalse();
        assertThat(set.contains("World")).isFalse();
    }

    @Test
    public void testContains() {
        // arrange
        set.add("Hello");

        // act, assert
        assertThat(set.contains("Hello")).isTrue();
        assertThat(set.contains("NonExistent")).isFalse();
    }

    @Test
    public void testContainsAll() {
        // arrange
        set.add("Hello");
        set.add("World");

        Set<String> collection = Set.of("Hello", "World");

        // act, assert
        assertThat(set.containsAll(collection)).isTrue();
    }

    @Test
    public void testIsEmpty() {
        // arrange
        set.add("Hello");
        set.add("World");

        // act, assert
        assertThat(set.isEmpty()).isFalse();

        // act, assert
        set.clear();
        assertThat(set.isEmpty()).isTrue();
    }

    @Test
    public void testRetainsAll() {
        // arrange
        set.add("Hello");
        set.add("World");

        Set<String> collection = Set.of("Hello");

        // act
        assertThat(set.retainAll(collection)).isTrue();

        // assert
        assertThat(set.contains("Hello")).isTrue();
        assertThat(set.contains("World")).isFalse();
        assertThat(set.add("NonExistent")).isTrue();
    }

    @Test
    public void testSize() {
        // act, assert
        set.add("Hello");
        assertThat(set.size()).isEqualTo(1);

        // act, assert
        set.add("Hello");
        assertThat(set.size()).isEqualTo(1);

        // act, assert
        set.add("World");
        assertThat(set.size()).isEqualTo(2);

        // act, assert
        set.remove("World");
        assertThat(set.size()).isEqualTo(1);

        // act, assert
        set.clear();
        assertThat(set.size()).isEqualTo(0);
    }

    @Test
    public void testEqualsAndHashCodeAndToString() {
        // arrange
        SynchronizedSet<String> anotherSet = new SynchronizedSet<>();

        // act
        set.add("Hello");
        anotherSet.add("Hello");

        // assert
        assertThat(set.equals(anotherSet)).isTrue();
        assertThat(set.hashCode()).isEqualTo(anotherSet.hashCode());
        assertThat(set.toString()).isEqualTo(anotherSet.toString());
    }

    @Test
    public void testToArray() {
        // arrange
        set.add("Hello");
        set.add("World");

        // act
        String[] array = set.toArray(new String[0]);

        // assert
        assertThat(new String[] { "Hello", "World" }).isEqualTo(array);
    }

    @Test
    public void testToArrayWithProvidedTooLargeArray() {
        // arrange
        set.add("Hello");
        set.add("World");

        String[] largerArray = new String[5];

        // act - Scenario 1: Provided array is larger than the set size
        String[] resultArray = set.toArray(largerArray);

        // assert
        assertThat(largerArray == resultArray).isTrue();
        assertThat("Hello").isEqualTo(resultArray[0]);
        assertThat("World").isEqualTo(resultArray[1]);
        assertThat(resultArray[2]).isNull();
    }

    @Test
    public void testToArrayWithProvidedArray() {
        // arrange
        set.add("Hello");
        set.add("World");

        String[] exactSizeArray = new String[2];

        // act - Scenario 2: Provided array is exactly the set size
        String[] resultArray = set.toArray(exactSizeArray);

        // assert
        assertThat(exactSizeArray == resultArray).isTrue();
        assertThat(new String[]{ "Hello", "World" }).isEqualTo(resultArray);
    }

    @Test
    public void testToArrayWithProvidedTooSmallArray() {
        // arrange
        set.add("Hello");
        set.add("World");

        String[] smallerArray = new String[1];

        // act - Scenario 3: Provided array is smaller than the set size
        String[] resultArray = set.toArray(smallerArray);

        // assert
        assertThat(smallerArray == resultArray).isFalse();
        assertThat(new String[]{ "Hello", "World" }).isEqualTo(resultArray);
    }
}
