package com.ricardo.casarez.flightsearch;

import android.app.Activity;
import android.app.Instrumentation.ActivityResult;
import android.content.Intent;
import android.support.test.espresso.contrib.PickerActions;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.widget.RecyclerView;
import android.widget.DatePicker;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.ricardo.casarez.flighsearch.domain.entity.search.response.Solution;
import com.ricardo.casarez.flightsearch.util.SearchFlightsIdlingResource;
import com.ricardo.casarez.flightsearch.util.ViewActionUtils;
import com.ricardo.casarez.flightsearch.view.activities.AutocompleteActivity;
import com.ricardo.casarez.flightsearch.view.activities.SearchFlightsActivity;
import com.ricardo.casarez.flightsearch.view.adapters.FlightsAdapter;

import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.registerIdlingResources;
import static android.support.test.espresso.Espresso.unregisterIdlingResources;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intending;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.RootMatchers.isDialog;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withHint;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withSpinnerText;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

/**
 * Created by Ricardo Casarez on 4/21/17.
 */
@LargeTest
@RunWith(AndroidJUnit4.class)
public class SearchFlightsTest {
    // formater to convert date to ui label
    private SimpleDateFormat mSimpleDateFormat;

    @Before
    public void setUp() {
        mSimpleDateFormat = new SimpleDateFormat("EEE, MMM d", Locale.US);
    }

    @Rule
    public IntentsTestRule<SearchFlightsActivity> mActivityRule = new IntentsTestRule<SearchFlightsActivity>(
            SearchFlightsActivity.class);

    @Test
    public void testInitialState() {
        // assert spinner default value to round trip
        onView(withId(R.id.tripSpinner)).check(matches(isDisplayed()));
        onView(withId(R.id.tripSpinner)).check(matches(withSpinnerText(R.string.round_trip)));

        onView(withId(R.id.passengersImageView)).check(matches(isDisplayed()));
        onView(withId(R.id.passengersCountTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.passengersCountTextView)).check(matches(withText(R.string.default_passenger_number)));

        onView(withId(R.id.place_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.originTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.originTextView)).check(matches(withText("")));
        onView(withId(R.id.originTextView)).check(matches(withHint(R.string.origin)));
        onView(withId(R.id.destinationTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.destinationTextView)).check(matches(withText("")));
        onView(withId(R.id.destinationTextView)).check(matches(withHint(R.string.destination)));

        onView(withId(R.id.event_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.departureDateTextView)).check(matches(isDisplayed()));

        onView(withId(R.id.departureDateTextView)).check(matches(isDisplayed()));
        Calendar calendar = Calendar.getInstance();
        onView(withId(R.id.departureDateTextView)).check(matches(withText(mSimpleDateFormat.format(calendar.getTime()))));

        onView(withId(R.id.returnDateTextView)).check(matches(isDisplayed()));
        calendar.add(Calendar.DATE, 3);
        onView(withId(R.id.returnDateTextView)).check(matches(withText(mSimpleDateFormat.format(calendar.getTime()))));

        // assert no progress bar is showed
        onView(withId(R.id.progress_view_layout)).check(matches(not(isDisplayed())));
    }

    @Test
    public void testSelectingOneWayTravel() {
        // click on trip spinner
        onView(withId(R.id.tripSpinner)).perform(click());

        // select one way
        onData(allOf(is(instanceOf(String.class)), is("One way"))).perform(click());
        onView(withId(R.id.tripSpinner)).check(matches(withSpinnerText(R.string.one_way)));

        // assert origin/destination textview is still displayed
        onView(withId(R.id.originTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.destinationTextView)).check(matches(isDisplayed()));

        // assert departure date textview is still displayed and return date is not.
        onView(withId(R.id.departureDateTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.returnDateTextView)).check(matches(not(isDisplayed())));
    }

    @Test
    public void testSelectRoundTripTravel() {
        onView(withId(R.id.tripSpinner)).perform(click());
        onData(allOf(is(instanceOf(String.class)), is("Round trip"))).perform(click());
        onView(withId(R.id.tripSpinner)).check(matches(withSpinnerText(R.string.round_trip)));

        onView(withId(R.id.originTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.destinationTextView)).check(matches(isDisplayed()));

        onView(withId(R.id.departureDateTextView)).check(matches(isDisplayed()));
        onView(withId(R.id.returnDateTextView)).check(matches(isDisplayed()));
    }

    @Test
    public void testPassengersDialogCancel() {
        // get passengers count value
        String passengersValue = ((TextView) mActivityRule.getActivity().findViewById(R.id.passengersCountTextView)).getText().toString();
        onView(withId(R.id.passengersCountTextView)).check(matches(withText(passengersValue)));
        // click passengers icon and assert is visible
        onView(withId(R.id.passengersImageView)).perform(click());
        onView(withText(R.string.adults)).inRoot(isDialog()).check(matches(isDisplayed()));

        // add an adult and assert adult count label
        onView(withId(R.id.adultAddButton)).perform(click());
        onView(withId(R.id.adultCountTextView)).check(matches(withText("2")));

        // click cancel button on the passengers dialog
        onView(withId(R.id.passengersCancelButton)).perform(click());
        // assert value not changed
        onView(withId(R.id.passengersCountTextView)).check(matches(withText(passengersValue)));
    }

    @Test
    public void testPassengersDialogDone() {
        // get passengers count value
        String passengersValue = ((TextView) mActivityRule.getActivity().findViewById(R.id.passengersCountTextView)).getText().toString();
        onView(withId(R.id.passengersCountTextView)).check(matches(withText(passengersValue)));
        // click passengers icon and assert is visible
        onView(withId(R.id.passengersImageView)).perform(click());
        onView(withText(R.string.adults)).check(matches(isDisplayed()));

        // add an adult and assert adult count label
        onView(withId(R.id.adultAddButton)).perform(click());
        onView(withId(R.id.adultCountTextView)).check(matches(withText("2")));

        // click done button on the passengers dialog
        onView(withId(R.id.passengersDoneButton)).perform(click());
        // assert value changed
        onView(withId(R.id.passengersCountTextView)).check(matches(not(withText(passengersValue))));
        onView(withId(R.id.passengersCountTextView)).check(matches(withText("2")));
    }

    @Test
    public void testClickDepartureDate() {
        onView(withId(R.id.departureDateTextView)).perform(click());
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).check(matches(isDisplayed()));

        // set date to 10 days ahead
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 10);

        // set date to datePicker dialog
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName())))
                .perform(PickerActions.setDate(
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH)+1,
                        calendar.get(Calendar.DAY_OF_MONTH)));

        // click ok on the datePicker dialog
        onView(withId(android.R.id.button1)).perform(click());

        // validate label is set same as date set
        onView(withId(R.id.departureDateTextView))
                .check(matches(withText(mSimpleDateFormat.format(calendar.getTime()))));
    }

    @Test
    public void testClickDepartureDateCancel() {
        onView(withId(R.id.departureDateTextView)).perform(click());
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).check(matches(isDisplayed()));

        // get departureDateTextView value
        String departure = ((TextView) mActivityRule.getActivity().findViewById(R.id.departureDateTextView)).getText().toString();

        // set date to 10 days ahead
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 10);

        // set date to datePicker dialog
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName())))
                .perform(PickerActions.setDate(
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH)+1,
                        calendar.get(Calendar.DAY_OF_MONTH)));

        // click cancel on the datePicker dialog
        onView(withId(android.R.id.button2)).perform(click());

        // validate label has not changed
        onView(withId(R.id.departureDateTextView))
                .check(matches(withText(departure)));
    }

    @Test
    public void testClickReturnDate() {
        onView(withId(R.id.returnDateTextView)).perform(click());
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).check(matches(isDisplayed()));

        // set date to 20 days ahead.
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 20);

        // set date to datePicker dialog
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName())))
                .perform(PickerActions.setDate(
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH)+1,
                        calendar.get(Calendar.DAY_OF_MONTH)));

        // click ok on the datePicker dialog
        onView(withId(android.R.id.button1)).perform(click());

        // validate label is set same as date set
        onView(withId(R.id.returnDateTextView))
                .check(matches(withText(mSimpleDateFormat.format(calendar.getTime()))));
    }

    @Test
    public void testClickReturnDateCancel() {
        onView(withId(R.id.returnDateTextView)).perform(click());
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).check(matches(isDisplayed()));

        // get return date textview value
        String textViewValue = ((TextView) mActivityRule.getActivity().findViewById(R.id.returnDateTextView)).getText().toString();

        // set date to 20 days ahead.
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 20);

        // set date to datePicker dialog
        onView(withClassName(Matchers.equalTo(DatePicker.class.getName())))
                .perform(PickerActions.setDate(
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH)+1,
                        calendar.get(Calendar.DAY_OF_MONTH)));

        // click cancel on the datePicker dialog
        onView(withId(android.R.id.button2)).perform(click());

        // validate label has not changed
        onView(withId(R.id.returnDateTextView))
                .check(matches(withText(textViewValue)));
    }

    @Test
    public void testOriginResultOk() {
        // Build a result to return when a particular AutocompleteActivity is launched.
        Intent resultData = new Intent();
        resultData.putExtra(AutocompleteActivity.AIRPORT_CODE_EXTRA, "DEN");
        ActivityResult activityResult = new ActivityResult(Activity.RESULT_OK, resultData);

        // Set up result stubbing when an intent sent to AutocompleteActivity is seen.
        intending(hasComponent(AutocompleteActivity.class.getName())).respondWith(activityResult);

        // Launching AutocompleteActivity expects airport code to be returned and displays it on the screen.
        onView(withId(R.id.originTextView)).perform(click());

        // test result
        onView(withId(R.id.originTextView)).check(matches(withText("DEN")));
    }

    @Test
    public void testOriginResultCanceled() {
        // Build a result to return when a particular AutocompleteActivity is launched.
        Intent resultData = new Intent();
        resultData.putExtra(AutocompleteActivity.AIRPORT_CODE_EXTRA, "DEN");
        ActivityResult activityResult = new ActivityResult(Activity.RESULT_CANCELED, resultData);

        // Set up result stubbing when an intent sent to AutocompleteActivity is seen.
        intending(hasComponent(AutocompleteActivity.class.getName())).respondWith(activityResult);

        // Launching AutocompleteActivity expects airport code to be returned and displays it on the screen.
        onView(withId(R.id.originTextView)).perform(click());

        // test result
        onView(withId(R.id.originTextView)).check(matches(withText("")));
    }

    @Test
    public void testDestinationResultOK() {
        // Build a result to return when a particular AutocompleteActivity is launched.
        Intent resultData = new Intent();
        resultData.putExtra(AutocompleteActivity.AIRPORT_CODE_EXTRA, "LAX");
        ActivityResult activityResult = new ActivityResult(Activity.RESULT_OK, resultData);

        // Set up result stubbing when an intent sent to AutocompleteActivity is seen.
        intending(hasComponent(AutocompleteActivity.class.getName())).respondWith(activityResult);

        // Launching AutocompleteActivity expects airport code to be returned and displays it on the screen.
        onView(withId(R.id.destinationTextView)).perform(click());

        // test result
        onView(withId(R.id.destinationTextView)).check(matches(withText("LAX")));
    }

    @Test
    public void testDestinationResultCanceled() {
        // Build a result to return when a particular AutocompleteActivity is launched.
        Intent resultData = new Intent();
        resultData.putExtra(AutocompleteActivity.AIRPORT_CODE_EXTRA, "LAX");
        ActivityResult activityResult = new ActivityResult(Activity.RESULT_CANCELED, resultData);

        // Set up result stubbing when an intent sent to AutocompleteActivity is seen.
        intending(hasComponent(AutocompleteActivity.class.getName())).respondWith(activityResult);

        // Launching AutocompleteActivity expects airport code to be returned and displays it on the screen.
        onView(withId(R.id.destinationTextView)).perform(click());

        // test result
        onView(withId(R.id.destinationTextView)).check(matches(withText("")));
    }

    @Test
    public void testSearchFlightsOneWay() {
        // replace ProgressBar drawable while invisible since Espresso has issues with animations.
        onView(isAssignableFrom(ProgressBar.class)).perform(ViewActionUtils.replaceProgressBarDrawable());

        // click on trip spinner
        onView(withId(R.id.tripSpinner)).perform(click());

        // select one way
        onData(allOf(is(instanceOf(String.class)), is("One way"))).perform(click());
        onView(withId(R.id.tripSpinner)).check(matches(withSpinnerText(R.string.one_way)));

        testOriginResultOk();

        testDestinationResultOK();

        // assert progress is visible
        onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));

        // idle for data loaded
        SearchFlightsIdlingResource searchFlightsIdlingResource = new SearchFlightsIdlingResource(
                (RecyclerView) mActivityRule.getActivity().findViewById(R.id.flights_recycler_view)
        );

        // idle until data is loaded
        registerIdlingResources(searchFlightsIdlingResource);

        // assert progress is not visible
        onView(withId(R.id.progress_bar)).check(matches(not(isDisplayed())));

        // assert list is visible
        onView(withId(R.id.flights_recycler_view)).check(matches(isDisplayed()));

        // get data from the adapter to assert position 3
        Solution solution = ((FlightsAdapter)((RecyclerView) mActivityRule.getActivity()
                .findViewById(R.id.flights_recycler_view)).getAdapter()).getData().get(3);

        onView(ViewActionUtils.withRecyclerView(R.id.flights_recycler_view).atPosition(3))
                .check(matches(hasDescendant(withText(containsString(solution.getSaleTotal().substring(3))))));

        unregisterIdlingResources(searchFlightsIdlingResource);

    }
}