package com.novoda.bonfire.user;

import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.widget.RecyclerView;
import android.view.View;

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.channel.service.ChannelService;
import com.novoda.bonfire.database.DatabaseResult;
import com.novoda.bonfire.user.data.model.User;
import com.novoda.bonfire.user.data.model.Users;
import com.novoda.bonfire.user.service.UserService;

import java.util.ArrayList;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
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.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isSelected;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.not;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

@RunWith(AndroidJUnit4.class)
public class UsersActivityTest {

    private static final User AUTHENTICATED_USER = new User("logged_user_id", "Authenticated User", "http://invalid.photo/url");
    private static final ArrayList<User> ALL_USERS = new ArrayList<>();
    static {
        for (int i = 0; i < 5; i++) {
            ALL_USERS.add(new User("test_user_" + i, "Test User " + i, "http://non.existent.url/" + i));
        }
        ALL_USERS.add(AUTHENTICATED_USER);
    }

    private ArrayList<User> channelOwners;

    @Rule
    public final ActivityTestRule<UsersActivity> activity = new ActivityTestRule<>(UsersActivity.class, false, false);
    private ChannelService channelService;
    private PublishSubject<DatabaseResult<Users>> subject;

    @Before
    public void setUp() throws Exception {
        channelService = Mockito.mock(ChannelService.class);
        UserService userService = Mockito.mock(UserService.class);
        when(userService.getAllUsers()).thenReturn(Observable.just(new Users(ALL_USERS)));
        TestDependencies.init()
                .withChannelService(channelService)
                .withUserService(userService);

        channelOwners = new ArrayList<>();
        channelOwners.add(AUTHENTICATED_USER);
        channelOwnersUpdateAfterEveryChange();
    }

    @Test
    public void userIsAddedToOwnersWhenClicked() throws Exception {
        launchUsersActivity();

        clickOnUserAtPosition(1);

        onView(withId(R.id.users_recycler_view)).check(matches(atPosition(1, isSelected())));
    }

    @Test
    public void userIsRemovedFromOwnersIfHeWasOwner() throws Exception {
        channelOwners.add(ALL_USERS.get(2));
        launchUsersActivity();

        clickOnUserAtPosition(2);

        onView(withId(R.id.users_recycler_view)).check(matches(atPosition(2, not(isSelected()))));
    }

    private void launchUsersActivity() {
        activity.launchActivity(UsersActivity.createIntentFor(InstrumentationRegistry.getContext(), new Channel("test_name", Access.PRIVATE)));
        subject.onNext(new DatabaseResult<>(new Users(channelOwners)));
    }

    private void clickOnUserAtPosition(int position) {
        onView(withId(R.id.users_recycler_view)).perform(RecyclerViewActions.actionOnItemAtPosition(position, click()));
    }

    private Matcher<View> atPosition(final int position, final Matcher<View> itemMatcher) {
        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("has item at position " + position + ": ");
                itemMatcher.describeTo(description);
            }

            @Override
            protected boolean matchesSafely(RecyclerView view) {
                RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
                return viewHolder != null && itemMatcher.matches(viewHolder.itemView);
            }
        };
    }

    private void channelOwnersUpdateAfterEveryChange() {
        subject = PublishSubject.create();

        doAnswer(new Answer<Observable<DatabaseResult<Users>>>() {
            @Override
            public Observable<DatabaseResult<Users>> answer(InvocationOnMock invocation) throws Throwable {
                return subject.observeOn(AndroidSchedulers.mainThread());
            }
        }).when(channelService).getOwnersOfChannel(any(Channel.class));

        doAnswer(new Answer<Observable<DatabaseResult<User>>>() {
            @Override
            public Observable<DatabaseResult<User>> answer(InvocationOnMock invocation) throws Throwable {
                User user = (User) invocation.getArguments()[1];
                channelOwners.add(user);
                subject.onNext(new DatabaseResult<>(new Users(channelOwners)));
                return Observable.just(new DatabaseResult<>(user));
            }
        }).when(channelService).addOwnerToPrivateChannel(any(Channel.class), any(User.class));

        doAnswer(new Answer<Observable<DatabaseResult<User>>>() {
            @Override
            public Observable<DatabaseResult<User>> answer(InvocationOnMock invocation) throws Throwable {
                User user = (User) invocation.getArguments()[1];
                channelOwners.remove(user);
                subject.onNext(new DatabaseResult<>(new Users(channelOwners)));
                return Observable.just(new DatabaseResult<>(user));
            }
        }).when(channelService).removeOwnerFromPrivateChannel(any(Channel.class), any(User.class));
    }
}
