package completer;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import jline.console.completer.Completer;

import de.renew.console.completer.CompleterComposition;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class CompleterCompositionTest {


    @Test
    void testCompleteWithSingleCompleter() {
        //given
        Completer mockCompleter = mock(Completer.class);
        //when
        when(mockCompleter.complete(eq("Re"), eq(2), anyList())).thenAnswer(invocation -> {
            List<CharSequence> list = invocation.getArgument(2);
            list.add("Renew");
            return 2;
        });

        CompleterComposition mockComposition = new CompleterComposition(mockCompleter);
        List<CharSequence> resultCandidates = new ArrayList<>();
        int result = mockComposition.complete("Re", 2, resultCandidates);

        //then
        assertThat(result).isEqualTo(2);
        assertThat(resultCandidates.size()).isEqualTo(1);
        assertThat(resultCandidates.get(0)).isEqualTo("Renew");

        verify(mockCompleter, times(1)).complete("Re", 2, resultCandidates);
    }

    @Test
    void testCompleteWithNullCompleter() {
        //given
        CompleterComposition mockComposition = new CompleterComposition(null, null);
        //then
        assertThrows(
            NullPointerException.class, () -> mockComposition.complete("Re", 2, List.of()));

    }

    @Test
    void testCompleteWithMultipleCompleters() {
        // Given
        Completer mockCompleter1 = mock(Completer.class);
        Completer mockCompleter2 = mock(Completer.class);
        Completer mockCompleter3 = mock(Completer.class);

        // When
        when(mockCompleter1.complete(eq("Re"), eq(2), anyList())).thenAnswer(Invocation -> {
            List<CharSequence> list = Invocation.getArgument(2);
            list.add("Regenerate");
            return 1;
        });
        when(mockCompleter2.complete(eq("Re"), eq(2), anyList())).thenAnswer(Invocation -> {
            List<CharSequence> list = Invocation.getArgument(2);
            list.add("Regret");
            return 2;
        });
        when(mockCompleter3.complete(eq("Re"), eq(2), anyList())).thenAnswer(Invocation -> {
            List<CharSequence> list = Invocation.getArgument(2);
            list.add("Renew");
            return 3;
        });

        CompleterComposition mockComposition =
            new CompleterComposition(mockCompleter1, mockCompleter2, mockCompleter3);

        // Test
        List<CharSequence> resultCandidates = new ArrayList<>();
        int result = mockComposition.complete("Re", 2, resultCandidates);

        // Then
        assertThat(result).isEqualTo(3);
        assertThat(resultCandidates.size()).isEqualTo(1);
        assertThat(resultCandidates.get(0)).isEqualTo("Renew");
    }

    @Test
    void testCompleteWithMultipleCompletersSameResult() {
        // Given
        Completer mockCompleter1 = mock(Completer.class);
        Completer mockCompleter2 = mock(Completer.class);

        when(mockCompleter1.complete(eq("Re"), eq(2), anyList())).thenAnswer(Invocation -> {
            List<CharSequence> list = Invocation.getArgument(2);
            list.add("Regret");
            return 3;
        });
        when(mockCompleter2.complete(eq("Re"), eq(2), anyList())).thenAnswer(Invocation -> {
            List<CharSequence> list = Invocation.getArgument(2);
            list.add("Renew");
            return 3;
        });

        CompleterComposition mockComposition =
            new CompleterComposition(mockCompleter1, mockCompleter2);

        // Test
        List<CharSequence> resultCandidates = new ArrayList<>();
        int result = mockComposition.complete("Re", 2, resultCandidates);

        // Then
        assertThat(result).isEqualTo(3);
        assertThat(resultCandidates.size()).isEqualTo(2);
        assertThat(resultCandidates).isEqualTo(List.of("Regret", "Renew"));

    }

    @Test
    public void testCompleteWithNoCompleters() {
        // given
        CompleterComposition composition = new CompleterComposition();

        // when
        List<CharSequence> candidates = new ArrayList<>();
        int result = composition.complete("test", 0, candidates);

        // then
        assertThat(result).isEqualTo(-1);
        assertThat(candidates).isEmpty();
    }

    @Test
    void testToStringOneCompleter() {
        //given
        Completer mockCompleter = mock(Completer.class);
        //when
        CompleterComposition composition = new CompleterComposition(mockCompleter);
        String result = composition.toString();
        //then
        assertThat(result).isEqualTo(
            "CompleterComposition{completers=" + List.of(mockCompleter.toString()) + "}");


    }

    @Test
    void testToStringMultipleCompleters() {
        //given
        Completer mockCompleter = mock(Completer.class);
        Completer mockCompleter2 = mock(Completer.class);
        //when
        CompleterComposition composition = new CompleterComposition(mockCompleter, mockCompleter2);
        String result = composition.toString();
        //then
        assertThat(result).isEqualTo(
            "CompleterComposition{completers="
                + List.of(mockCompleter.toString(), mockCompleter2.toString()) + "}");

    }

    @Test
    void testToStringNoCompleter() {
        //given
        //when
        CompleterComposition composition = new CompleterComposition();
        String result = composition.toString();
        //then
        assertThat(result).isEqualTo("CompleterComposition{completers=" + List.of() + "}");
    }

    @Test
    void testAddCompleter() {
        //given
        Completer mockCompleter = mock(Completer.class);
        CompleterComposition composition = new CompleterComposition();
        //when
        composition.addCompleter(mockCompleter);
        //then
        assertThat(composition.getCompleters().size()).isEqualTo(1);
        assertThat(composition.getCompleters()).contains(mockCompleter);
    }

    @Test
    void testAddCompleterWithNullCompleter() {
        //given
        CompleterComposition composition = new CompleterComposition();
        //when
        composition.addCompleter(null);
        //then
        assertThat(composition.getCompleters().size()).isEqualTo(1);
        assertThat(composition.getCompleters()).contains((Completer) null);
    }

    @Test
    void testRemoveCompleter() {
        //given
        Completer mockCompleter = mock(Completer.class);
        CompleterComposition composition = new CompleterComposition(mockCompleter);
        //when
        composition.removeCompleter(mockCompleter);
        //then
        assertThat(composition.getCompleters()).isEmpty();
    }



}
