package com.njackson.test.activityrecognition;

import android.app.PendingIntent;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.DetectedActivity;
import com.njackson.activityrecognition.ActivityRecognitionServiceCommand;
import com.njackson.application.MainThreadBus;
import com.njackson.application.modules.AndroidModule;
import com.njackson.application.modules.ForApplication;
import com.njackson.events.ActivityRecognitionCommand.ActivityRecognitionChangeState;
import com.njackson.events.ActivityRecognitionCommand.ActivityRecognitionStatus;
import com.njackson.events.ActivityRecognitionCommand.NewActivityEvent;
import com.njackson.events.base.BaseChangeState;
import com.njackson.events.base.BaseStatus;
import com.njackson.gps.IForegroundServiceStarter;
import com.njackson.test.application.TestApplication;
import com.njackson.utils.googleplay.IGooglePlayServices;
import com.njackson.utils.services.IServiceStarter;
import com.njackson.utils.time.ITimer;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import com.squareup.otto.ThreadEnforcer;

import org.mockito.ArgumentCaptor;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Created by njackson on 01/01/15.
 */
public class ActivityRecognitionServiceCommandTest extends AndroidTestCase {

    @Inject Bus _bus;
    @Inject @Named("GoogleActivity") GoogleApiClient _googleApiClient;
    @Inject IServiceStarter _serviceStarter;
    @Inject SharedPreferences _sharedPreferences;

    static IGooglePlayServices _playServices;

    private ActivityRecognitionServiceCommand _command;
    private static ITimer _mockTimer;
    private static IForegroundServiceStarter _mockServiceStarter;
    private TestApplication _app;

    @Module(
            includes = AndroidModule.class,
            injects = ActivityRecognitionServiceCommandTest.class,
            overrides = true,
            complete = false
    )
    class TestModule {
        @Provides IGooglePlayServices providesGooglePlayServices() { return _playServices; }
        @Provides @Singleton @Named("GoogleActivity") GoogleApiClient provideActivityRecognitionClient() { return mock(GoogleApiClient.class); }
        @Provides @Singleton IServiceStarter provideServiceStarter() { return mock(IServiceStarter.class); }
        @Provides ITimer providesTimer() { return _mockTimer; }
        @Provides @Singleton SharedPreferences provideSharedPreferences() { return mock(SharedPreferences.class); };
        @Provides
        IForegroundServiceStarter providesForegroundServiceStarter() { return _mockServiceStarter; }
        @Provides @Singleton @ForApplication
        Context provideApplicationContext() {
            return getContext();
        }
        @Provides @Singleton
        Bus providesBus() { return mock(Bus.class); }
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();

        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath());

        _playServices = mock(IGooglePlayServices.class);
        _mockTimer = mock(ITimer.class);
        _mockServiceStarter = mock(IForegroundServiceStarter.class);

        _app = new TestApplication();
        _app.setObjectGraph(ObjectGraph.create(new TestModule()));
        _app.inject(this);

        _command = new ActivityRecognitionServiceCommand();
    }

    @SmallTest
    public void testGooglePlayDisableMessageReceived() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.API_UNAVAILABLE);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        ArgumentCaptor<ActivityRecognitionStatus> captor = ArgumentCaptor.forClass(ActivityRecognitionStatus.class);
        verify(_bus,timeout(1000).times(1)).post(captor.capture());

        assertEquals(BaseStatus.Status.UNABLE_TO_START, captor.getValue().getStatus());
        assertFalse(captor.getValue().playServicesAvailable());
    }

    @SmallTest
    public void testServiceStartedMessageReceived() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        ArgumentCaptor<ActivityRecognitionStatus> captor = ArgumentCaptor.forClass(ActivityRecognitionStatus.class);
        verify(_bus,timeout(1000).times(1)).post(captor.capture());

        assertEquals(BaseStatus.Status.STARTED, captor.getValue().getStatus());
    }

    @SmallTest
    public void testServiceStartedMessageReceivedOnlyOnce() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        ArgumentCaptor<ActivityRecognitionStatus> captor = ArgumentCaptor.forClass(ActivityRecognitionStatus.class);
        verify(_bus,timeout(1000).times(1)).post(captor.capture());

        assertEquals(BaseStatus.Status.STARTED, captor.getValue().getStatus());
    }

    @SmallTest
    public void testServiceStoppedMessageReceivedOnlyOnce() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));

        ArgumentCaptor<ActivityRecognitionStatus> captor = ArgumentCaptor.forClass(ActivityRecognitionStatus.class);
        verify(_bus,timeout(1000).times(1)).post(captor.capture());

        assertEquals(BaseStatus.Status.STOPPED, captor.getValue().getStatus());
    }

    @SmallTest
    public void testServiceStoppedMessageReceived() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));

        ArgumentCaptor<ActivityRecognitionStatus> captor = ArgumentCaptor.forClass(ActivityRecognitionStatus.class);
        verify(_bus,timeout(1000).times(1)).post(captor.capture());

        assertEquals(BaseStatus.Status.STOPPED, captor.getValue().getStatus());
    }

    @SmallTest
    public void testServiceConnectsToGooglePlayOnStart() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        verify(_googleApiClient,timeout(1000).times(1)).connect();
    }

    @SmallTest
    public void testRegistersActivityRecogntionConnectedEvent() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        verify(_googleApiClient,timeout(1000).times(1)).registerConnectionCallbacks(any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testRegistersActivityRecogntionFailedEvent() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        verify(_googleApiClient,timeout(1000).times(1)).registerConnectionFailedListener(any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testUnRegistersActivityRecogntionConnectedEvent() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));

        verify(_googleApiClient,timeout(1000).times(1)).unregisterConnectionCallbacks(any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testUnRegistersActivityRecogntionFailedEvent() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));

        verify(_googleApiClient,timeout(1000).times(1)).unregisterConnectionFailedListener(any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testRegistersActivityRecognitionUpdatesOnConnect() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        _command.onConnected(new Bundle());

        verify(_playServices,timeout(1000).times(1)).requestActivityUpdates(any(GoogleApiClient.class), anyLong(), any(PendingIntent.class));
    }

    @SmallTest
    public void testRegistersActivityRecognitionUpdatesOnDestroy() throws Exception {
        when(_playServices.isGooglePlayServicesAvailable(any(Context.class))).thenReturn(ConnectionResult.SUCCESS);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.STOP));

        verify(_playServices,timeout(1000).times(1)).removeActivityUpdates(any(GoogleApiClient.class), any(PendingIntent.class));
    }

    @SmallTest
    public void testRespondsToNewActivityEventStartsLocationWhenActivityRecognitonPreferenceSet() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(true);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.ON_FOOT));

        verify(_serviceStarter,timeout(2000).times(1)).startLocationServices();
    }

    @SmallTest
    public void testRespondsToNewActivityEventDoesNotStartsLocationWhenActivityRecognitonPreferenceNotSet() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(false);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.ON_FOOT));

        verify(_serviceStarter,timeout(2000).times(0)).startLocationServices();
    }

    @SmallTest
    public void testRespondsToNewSTILLEventAndActivityRecognitionSetStartsTimer() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(true);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.STILL));

        verify(_mockTimer,timeout(2000).times(1)).setTimer(anyLong(),any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testRespondsToNewSTILLAndActivityRecognitionSetDoesNotStartsTimer() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(false);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.STILL));

        verify(_mockTimer,timeout(2000).times(0)).setTimer(anyLong(),any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testRespondsToNewSTILLActivityEventDoesNotStartTimerIfTimerActive() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(true);

        _command.execute(_app);
        _command.onChangeState(new ActivityRecognitionChangeState(BaseChangeState.State.START));

        when(_mockTimer.getActive()).thenReturn(true);
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.STILL));

        verify(_mockTimer,timeout(2000).times(0)).setTimer(anyLong(),any(ActivityRecognitionServiceCommand.class));
    }

    @SmallTest
    public void testCancelsTimerWhenActivityDetected() throws Exception {
        when(_sharedPreferences.getBoolean("ACTIVITY_RECOGNITION",false)).thenReturn(true);

        _command.execute(_app);
        _command.onNewActivityEvent(new NewActivityEvent(DetectedActivity.ON_FOOT));

        verify(_mockTimer,timeout(2000).times(1)).cancel();
    }

    @SmallTest
    public void testTimeoutHandlerStopsLocation() throws Exception {
        _command.execute(_app);
        _command.handleTimeout();

        verify(_serviceStarter,timeout(2000).times(1)).stopLocationServices();
    }

}