package com.novoda.bonfire.chat;

import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import com.novoda.bonfire.R;
import com.novoda.bonfire.TestDependencies;
import com.novoda.bonfire.channel.data.model.Channel;
import com.novoda.bonfire.channel.data.model.Channel.Access;
import com.novoda.bonfire.chat.data.model.Chat;
import com.novoda.bonfire.chat.data.model.Message;
import com.novoda.bonfire.chat.service.ChatService;
import com.novoda.bonfire.database.DatabaseResult;
import com.novoda.bonfire.login.data.model.Authentication;
import com.novoda.bonfire.login.service.LoginService;
import com.novoda.bonfire.user.data.model.User;

import java.util.ArrayList;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.PublishSubject;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

@RunWith(AndroidJUnit4.class)
public class ChatActivityTest {

    private static final User AUTHENTICATED_USER = new User("user_id", "Test User", "http://invalid.url");
    private static final ArrayList<Message> MESSAGES = new ArrayList<>();
    private static final DatabaseResult<Chat> CHAT = new DatabaseResult<>(new Chat(MESSAGES));
    private static final String CHANNEL_NAME = "test_channel";

    @Rule
    public final ActivityTestRule<ChatActivity> activity = new ActivityTestRule<>(ChatActivity.class, false, false);
    private ChatService chatService;

    @Before
    public void setUp() throws Exception {
        chatService = Mockito.mock(ChatService.class);
        LoginService loginService = Mockito.mock(LoginService.class);
        when(loginService.getAuthentication()).thenReturn(Observable.just(new Authentication(AUTHENTICATED_USER)));

        TestDependencies.init()
                .withChatService(chatService)
                .withLoginService(loginService);
    }

    @Test
    public void channelNameIsShownInToolbar() throws Exception {
        givenChatIsEmpty();
        launchChatActivityWithAccess(Access.PRIVATE);

        onView(allOf(isDescendantOfA(withId(R.id.toolbar)), withText(CHANNEL_NAME))).check(matches(isDisplayed()));
    }

    @Test
    public void cannotManageUsersInPublicChannel() throws Exception {
        givenChatIsEmpty();
        launchChatActivityWithAccess(Access.PUBLIC);

        onView(withId(R.id.toolbar)).check(matches(not(hasDescendant(withId(R.id.manageOwners)))));
    }

    @Test
    public void canManageUsersWhenChannelIsPrivate() throws Exception {
        givenChatIsEmpty();
        launchChatActivityWithAccess(Access.PRIVATE);

        onView(allOf(isDescendantOfA(withId(R.id.toolbar)), withId(R.id.manageOwners))).check(matches(isDisplayed()));
    }

    @Test
    public void submitButtonIsDisabledWhenMessageIsEmpty() throws Exception {
        givenChatIsEmpty();
        launchChatActivityWithAccess(Access.PUBLIC);

        onView(withId(R.id.submit_button)).check(matches(not(isEnabled())));
        onView(withId(R.id.message_edit)).perform(typeText("random message text"), closeSoftKeyboard(), clearText());
        onView(withId(R.id.submit_button)).check(matches(not(isEnabled())));
    }

    @Test
    public void submitButtonIsEnabledWhenThereIsAMessage() throws Exception {
        givenChatIsEmpty();
        launchChatActivityWithAccess(Access.PRIVATE);

        onView(withId(R.id.message_edit)).perform(typeText("not so random message text"), closeSoftKeyboard());
        onView(withId(R.id.submit_button)).check(matches(isEnabled()));
    }

    private void givenChatIsEmpty() {
        when(chatService.getChat(any(Channel.class))).thenReturn(Observable.just(CHAT));
    }

    @Test
    public void submittedMessageShowsInTheChat() throws Exception {
        givenChatUpdatesAfterEveryMessage();
        launchChatActivityWithAccess(Access.PRIVATE);

        String message = "test message";
        submitMessage(message);

        assertThatMessageFromUserIsShownInChat(AUTHENTICATED_USER.getName(), message);
    }

    private void launchChatActivityWithAccess(Access access) {
        activity.launchActivity(ChatActivity.createIntentFor(InstrumentationRegistry.getContext(), new Channel(CHANNEL_NAME, access)));
    }

    private void givenChatUpdatesAfterEveryMessage() {
        final PublishSubject<DatabaseResult<Chat>> subject = PublishSubject.create();

        doAnswer(new Answer<Observable<DatabaseResult<Chat>>>() {
            @Override
            public Observable<DatabaseResult<Chat>> answer(InvocationOnMock invocation) throws Throwable {
                return subject
                        .observeOn(AndroidSchedulers.mainThread())
                        .startWith(CHAT);
            }
        }).when(chatService).getChat(any(Channel.class));
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                MESSAGES.add((Message) invocation.getArguments()[1]);
                subject.onNext(CHAT);
                return null;
            }
        }).when(chatService).sendMessage(any(Channel.class), any(Message.class));
    }

    private void assertThatMessageFromUserIsShownInChat(String userName, String message) {
        onView(withId(R.id.message_author_image)).check(matches(isDisplayed()));
        onView(allOf(withId(R.id.message_body), withText(equalToIgnoringCase(message)))).check(matches(isDisplayed()));
    }

    private void submitMessage(String message) {
        onView(withId(R.id.message_edit)).perform(typeText(message), closeSoftKeyboard());
        onView(withId(R.id.submit_button)).perform(click());
    }
}
