package com.reactnativenavigation.viewcontrollers.bottomtabs;

import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;

import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
import com.reactnativenavigation.BaseTest;
import com.reactnativenavigation.TestUtils;
import com.reactnativenavigation.mocks.ImageLoaderMock;
import com.reactnativenavigation.mocks.SimpleViewController;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.parse.params.Bool;
import com.reactnativenavigation.parse.params.Colour;
import com.reactnativenavigation.parse.params.NullText;
import com.reactnativenavigation.parse.params.Number;
import com.reactnativenavigation.parse.params.Text;
import com.reactnativenavigation.presentation.BottomTabPresenter;
import com.reactnativenavigation.presentation.BottomTabsPresenter;
import com.reactnativenavigation.presentation.Presenter;
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.utils.CommandListenerAdapter;
import com.reactnativenavigation.utils.ImageLoader;
import com.reactnativenavigation.utils.OptionHelper;
import com.reactnativenavigation.utils.StatusBarUtils;
import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.ParentController;
import com.reactnativenavigation.viewcontrollers.ViewController;
import com.reactnativenavigation.viewcontrollers.stack.StackController;
import com.reactnativenavigation.views.BottomTabs;
import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout;

import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

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

import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import edu.emory.mathcs.backport.java.util.Collections;

import static com.reactnativenavigation.TestUtils.hideBackButton;
import static com.reactnativenavigation.utils.ObjectUtils.perform;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class BottomTabsControllerTest extends BaseTest {

    private Activity activity;
    private BottomTabs bottomTabs;
    private BottomTabsController uut;
    private Options initialOptions = new Options();
    private ViewController child1;
    private ViewController child2;
    private ViewController child3;
    private StackController child4;
    private ViewController child5;
    private Options tabOptions = OptionHelper.createBottomTabOptions();
    private ImageLoader imageLoaderMock = ImageLoaderMock.mock();
    private EventEmitter eventEmitter;
    private ChildControllersRegistry childRegistry;
    private List<ViewController> tabs;
    private BottomTabsPresenter presenter;
    private BottomTabPresenter bottomTabPresenter;
    private BottomTabsAttacher tabsAttacher;

    @Override
    public void beforeEach() {
        activity = newActivity();
        childRegistry = new ChildControllersRegistry();
        eventEmitter = Mockito.mock(EventEmitter.class);
        prepareViewsForTests();
        StatusBarUtils.saveStatusBarHeight(63);
    }

    @Test
    public void createView_checkProperStructure() {
        assertThat(uut.getView()).isInstanceOf(CoordinatorLayout.class);
        assertThat(uut.getView().getChildAt(0)).isInstanceOf(BottomTabs.class);
        assertThat(((CoordinatorLayout.LayoutParams) uut.getBottomTabs().getLayoutParams()).gravity).isEqualTo(Gravity.BOTTOM);
    }

    @Test
    public void createView_tabsWithoutIconsAreAccepted() {
        tabOptions.bottomTabOptions.icon = new NullText();
        prepareViewsForTests();
        assertThat(uut.getBottomTabs().getItemsCount()).isEqualTo(tabs.size());
    }

    @Test
    public void createView_showTitlesWhenAllTabsDontHaveIcons() {
        tabOptions.bottomTabOptions.icon = new NullText();
        assertThat(tabOptions.bottomTabsOptions.titleDisplayMode.hasValue()).isFalse();
        prepareViewsForTests();
        presenter.applyOptions(Options.EMPTY);
        assertThat(bottomTabs.getTitleState()).isEqualTo(AHBottomNavigation.TitleState.ALWAYS_SHOW);
    }

    @Test(expected = RuntimeException.class)
    public void setTabs_ThrowWhenMoreThan5() {
        tabs.add(new SimpleViewController(activity, childRegistry, "6", tabOptions));
        createBottomTabs();
    }

    @Test
    public void parentControllerIsSet() {
        uut = createBottomTabs();
        for (ViewController tab : tabs) {
            assertThat(tab.getParentController()).isEqualTo(uut);
        }
    }

    @Test
    public void setTabs_allChildViewsAreAttachedToHierarchy() {
        uut.onViewAppeared();
        assertThat(uut.getView().getChildCount()).isEqualTo(6);
        for (ViewController child : uut.getChildControllers()) {
            assertThat(child.getView().getParent()).isNotNull();
        }
    }

    @Test
    public void setTabs_firstChildIsVisibleOtherAreGone() {
        uut.onViewAppeared();
        for (int i = 0; i < uut.getChildControllers().size(); i++) {
            assertThat(uut.getView().getChildAt(i + 1)).isEqualTo(tabs.get(i).getView());
            assertThat(uut.getView().getChildAt(i + 1).getVisibility()).isEqualTo(i == 0 ? View.VISIBLE : View.INVISIBLE);
        }
    }

    @Test
    public void onTabSelected() {
        uut.ensureViewIsCreated();
        assertThat(uut.getSelectedIndex()).isZero();
        assertThat(((ViewController) ((List) uut.getChildControllers()).get(0)).getView().getVisibility()).isEqualTo(View.VISIBLE);

        uut.onTabSelected(3, false);

        assertThat(uut.getSelectedIndex()).isEqualTo(3);
        assertThat(((ViewController) ((List) uut.getChildControllers()).get(0)).getView().getVisibility()).isEqualTo(View.INVISIBLE);
        assertThat(((ViewController) ((List) uut.getChildControllers()).get(3)).getView().getVisibility()).isEqualTo(View.VISIBLE);
        verify(eventEmitter, times(1)).emitBottomTabSelected(0, 3);
    }

    @Test
    public void onTabReSelected() {
        uut.ensureViewIsCreated();
        assertThat(uut.getSelectedIndex()).isZero();

        uut.onTabSelected(0, true);

        assertThat(uut.getSelectedIndex()).isEqualTo(0);
        assertThat(((ViewController) ((List) uut.getChildControllers()).get(0)).getView().getParent()).isNotNull();
        verify(eventEmitter, times(1)).emitBottomTabSelected(0, 0);
    }

    @Test
    public void handleBack_DelegatesToSelectedChild() {
        uut.ensureViewIsCreated();
        assertThat(uut.handleBack(new CommandListenerAdapter())).isFalse();
        uut.selectTab(4);
        assertThat(uut.handleBack(new CommandListenerAdapter())).isTrue();
        verify(child5, times(1)).handleBack(any());
    }

    @Test
    public void applyChildOptions_bottomTabsOptionsAreClearedAfterApply() {
        ParentController parent = Mockito.mock(ParentController.class);
        uut.setParentController(parent);

        child1.options.bottomTabsOptions.backgroundColor = new Colour(Color.RED);
        child1.onViewAppeared();

        ArgumentCaptor<Options> optionsCaptor = ArgumentCaptor.forClass(Options.class);
        verify(parent).applyChildOptions(optionsCaptor.capture(), any());
        assertThat(optionsCaptor.getValue().bottomTabsOptions.backgroundColor.hasValue()).isFalse();
    }

    @Test
    public void applyOptions_bottomTabsCreateViewOnlyOnce() {
        verify(presenter).applyOptions(any());
        verify(bottomTabs, times(2)).superCreateItems(); // first time when view is created, second time when options are applied
    }

    @Test
    public void onSizeChanged_recreateItemsIfSizeHasChanged() {
        int numberOfPreviousInvocations = 2;
        bottomTabs.onSizeChanged(0, 0, 0, 0);
        verify(bottomTabs, times(numberOfPreviousInvocations)).superCreateItems();

        bottomTabs.onSizeChanged(100, 0, 0, 0);
        verify(bottomTabs, times(numberOfPreviousInvocations)).superCreateItems();

        bottomTabs.onSizeChanged(1080, 147, 0, 0);
        verify(bottomTabs, times(numberOfPreviousInvocations + 1)).superCreateItems();

        bottomTabs.onSizeChanged(1920, 147, 0, 0);
        verify(bottomTabs, times(numberOfPreviousInvocations + 2)).superCreateItems();

        when(bottomTabs.getItemsCount()).thenReturn(0);
        bottomTabs.onSizeChanged(1080, 147, 0, 0);
        verify(bottomTabs, times(numberOfPreviousInvocations + 2)).superCreateItems();
    }

    @Test
    public void mergeOptions_currentTabIndex() {
        uut.ensureViewIsCreated();
        assertThat(uut.getSelectedIndex()).isZero();

        Options options = new Options();
        options.bottomTabsOptions.currentTabIndex = new Number(1);
        uut.mergeOptions(options);
        assertThat(uut.getSelectedIndex()).isOne();
        verify(eventEmitter, times(0)).emitBottomTabSelected(any(Integer.class), any(Integer.class));
    }

    @Test
    public void mergeOptions_drawBehind() {
        assertThat(uut.getBottomInset(child1)).isEqualTo(uut.getBottomTabs().getHeight());

        Options o1 = new Options();
        o1.bottomTabsOptions.drawBehind = new Bool(true);
        child1.mergeOptions(o1);
        assertThat(uut.getBottomInset(child1)).isEqualTo(0);

        Options o2 = new Options();
        o2.topBar.title.text = new Text("Some text");
        child1.mergeOptions(o1);
        assertThat(uut.getBottomInset(child1)).isEqualTo(0);
    }

    @Test
    public void mergeOptions_drawBehind_stack() {
        uut.selectTab(3);

        SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options());
        disablePushAnimation(stackChild);
        child4.push(stackChild, new CommandListenerAdapter());

        assertThat(((MarginLayoutParams) stackChild.getView().getLayoutParams()).bottomMargin).isEqualTo(bottomTabs.getHeight());

        Options o1 = new Options();
        o1.bottomTabsOptions.drawBehind = new Bool(true);
        stackChild.mergeOptions(o1);

        assertThat(((MarginLayoutParams) stackChild.getView().getLayoutParams()).bottomMargin).isEqualTo(0);
    }

    @Test
    public void mergeOptions_mergesBottomTabOptions() {
        Options options = new Options();
        uut.mergeOptions(options);
        verify(bottomTabPresenter).mergeOptions(options);
    }

    @Test
    public void applyChildOptions_resolvedOptionsAreUsed() {
        Options childOptions = new Options();
        SimpleViewController pushedScreen = new SimpleViewController(activity, childRegistry, "child4.1", childOptions);
        disablePushAnimation(pushedScreen);
        child4 = createStack(pushedScreen);

        tabs = new ArrayList<>(Collections.singletonList(child4));
        tabsAttacher = new BottomTabsAttacher(tabs, presenter, Options.EMPTY);

        initialOptions.bottomTabsOptions.currentTabIndex = new Number(0);
        Options resolvedOptions = new Options();
        uut = new BottomTabsController(activity,
                tabs,
                childRegistry,
                eventEmitter,
                imageLoaderMock,
                "uut",
                initialOptions,
                new Presenter(activity, new Options()),
                tabsAttacher,
                presenter,
                new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())) {
            @Override
            public Options resolveCurrentOptions() {
                return resolvedOptions;
            }

            @NonNull
            @Override
            protected BottomTabs createBottomTabs() {
                return new BottomTabs(activity) {
                    @Override
                    protected void createItems() {

                    }
                };
            }
        };

        activity.setContentView(uut.getView());
        verify(presenter, times(2)).applyChildOptions(eq(resolvedOptions), any());
    }

    @Test
    public void child_mergeOptions_currentTabIndex() {
        uut.ensureViewIsCreated();

        assertThat(uut.getSelectedIndex()).isZero();

        Options options = new Options();
        options.bottomTabsOptions.currentTabIndex = new Number(1);
        child1.mergeOptions(options);

        assertThat(uut.getSelectedIndex()).isOne();
    }

    @Test
    public void resolveCurrentOptions_returnsFirstTabIfInvokedBeforeViewIsCreated() {
        uut = createBottomTabs();
        assertThat(uut.getCurrentChild()).isEqualTo(tabs.get(0));
    }

    @Test
    public void buttonPressInvokedOnCurrentTab() {
        uut.ensureViewIsCreated();
        uut.selectTab(4);

        uut.sendOnNavigationButtonPressed("btn1");
        verify(child5, times(1)).sendOnNavigationButtonPressed("btn1");
    }

    @Test
    public void push() {
        uut.ensureViewIsCreated();
        uut.selectTab(3);

        SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options());
        SimpleViewController stackChild2 = new SimpleViewController(activity, childRegistry, "stackChild2", new Options());

        disablePushAnimation(stackChild, stackChild2);
        hideBackButton(stackChild2);

        child4.push(stackChild, new CommandListenerAdapter());
        assertThat(child4.size()).isOne();
        child4.push(stackChild2, new CommandListenerAdapter());
        assertThat(child4.size()).isEqualTo(2);
    }

    @Test
    public void oneTimeOptionsAreAppliedOnce() {
        Options options = new Options();
        options.bottomTabsOptions.currentTabIndex = new Number(1);

        assertThat(uut.getSelectedIndex()).isZero();
        uut.mergeOptions(options);
        assertThat(uut.getSelectedIndex()).isOne();
        assertThat(uut.options.bottomTabsOptions.currentTabIndex.hasValue()).isFalse();
        assertThat(uut.initialOptions.bottomTabsOptions.currentTabIndex.hasValue()).isFalse();
    }

    @Test
    public void selectTab() {
        uut.selectTab(1);
        verify(tabsAttacher).onTabSelected(tabs.get(1));
    }

    @Test
    public void getTopInset() {
        assertThat(child1.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity));
        assertThat(child2.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity));

        child1.options.statusBar.drawBehind = new Bool(true);
        assertThat(child1.getTopInset()).isEqualTo(0);
        assertThat(child2.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity));

        SimpleViewController stackChild = new SimpleViewController(activity, childRegistry, "stackChild", new Options());
        disablePushAnimation(stackChild);
        child4.push(stackChild, new CommandListenerAdapter());

        assertThat(stackChild.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity) + child4.getTopBar().getHeight());

        uut.options.statusBar.drawBehind = new Bool(true);
        stackChild.options.topBar.drawBehind = new Bool(true);
        assertThat(uut.getTopInset()).isEqualTo(0);
        assertThat(child4.getTopInset()).isEqualTo(0);
        assertThat(child4.getTopBar().getY()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity));
        assertThat(stackChild.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity));
    }

    @Test
    public void getBottomInset_defaultOptionsAreTakenIntoAccount() {
        Options defaultOptions = new Options();
        defaultOptions.bottomTabsOptions.visible = new Bool(false);

        assertThat(uut.getBottomInset(child1)).isEqualTo(bottomTabs.getHeight());
        uut.setDefaultOptions(defaultOptions);
        assertThat(uut.getBottomInset(child1)).isZero();
    }

    @Test
    public void destroy() {
        uut.destroy();
        verify(tabsAttacher).destroy();
    }

    private void prepareViewsForTests() {
        perform(uut, ViewController::destroy);
        bottomTabs = spy(new BottomTabs(activity) {
            @Override
            public void superCreateItems() {

            }
        });
        createChildren();
        tabs = createTabs();
        presenter = spy(new BottomTabsPresenter(tabs, new Options()));
        bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options()));
        tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter, Options.EMPTY));
        uut = createBottomTabs();

        uut.setParentController(Mockito.mock(ParentController.class));
        CoordinatorLayout parent = new CoordinatorLayout(activity);
        parent.addView(uut.getView());
        activity.setContentView(parent);
    }

    private void createChildren() {
        child1 = spy(new SimpleViewController(activity, childRegistry, "child1", tabOptions));
        child2 = spy(new SimpleViewController(activity, childRegistry, "child2", tabOptions));
        child3 = spy(new SimpleViewController(activity, childRegistry, "child3", tabOptions));
        child4 = spy(createStack());
        child5 = spy(new SimpleViewController(activity, childRegistry, "child5", tabOptions));
        when(child5.handleBack(any())).thenReturn(true);
    }

    @NonNull
    private List<ViewController> createTabs() {
        return Arrays.asList(child1, child2, child3, child4, child5);
    }

    private StackController createStack() {
        return TestUtils.newStackController(activity)
                .setId("someStack")
                .setInitialOptions(tabOptions)
                .build();
    }

    private StackController createStack(ViewController initialChild) {
        return TestUtils.newStackController(activity)
                .setInitialOptions(tabOptions)
                .setChildren(initialChild)
                .build();
    }

    private BottomTabsController createBottomTabs() {
        return new BottomTabsController(activity,
                tabs,
                childRegistry,
                eventEmitter,
                imageLoaderMock,
                "uut",
                initialOptions,
                new Presenter(activity, new Options()),
                tabsAttacher,
                presenter,
                bottomTabPresenter) {
            @Override
            public void ensureViewIsCreated() {
                super.ensureViewIsCreated();
                uut.getView().layout(0, 0, 1000, 1000);
            }

            @NonNull
            @Override
            protected BottomTabsLayout createView() {
                BottomTabsLayout view = super.createView();
                bottomTabs.getLayoutParams().height = 100;
                return view;
            }

            @NonNull
            @Override
            protected BottomTabs createBottomTabs() {
                return bottomTabs;
            }
        };
    }
}