package com.avg.zaap.crashlibrary;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;

import com.avg.zaap.crashlibrary.offline.Connection;
import com.avg.zaap.crashlibrary.offline.IConnectivityHelper;
import com.avg.zaap.crashlibrary.offline.service.ICrashReportingServiceBroadcastReceiverCallback;
import com.avg.zaap.crashlibrary.offline.uploader.IOfflineUploader;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.support.membermodification.MemberMatcher.method;
import static org.powermock.api.support.membermodification.MemberMatcher.methods;
import static org.powermock.api.support.membermodification.MemberModifier.suppress;

/**
 * A test class for {@link CrashReportingService}.
 * <p/>
 * Created by marek.osvald on 3.5.2016.
 */
@SuppressWarnings("SameParameterValue")
@RunWith(PowerMockRunner.class)
@PrepareForTest({CrashReportingService.class, android.os.Process.class})
public class CrashReportingServiceTests {

    private static final int MOCK_PID = 1234;
    private static final int MOCK_FLAGS = 13;
    private static final int MOCK_START_ID = 42;

    private CrashReportingService mCrashReportingService;
    private ICrashReporter mCrashReporter;
    private ICrashServiceCommunicator.Stub mBinder;
    private Intent mIntent;
    private Context mContext;

    private BroadcastReceiver mBroadcastReceiver;
    private IConnectivityHelper mConnectivityHelper;
    private IOfflineUploader mOfflineUploader;
    private ConnectionStatus mConnectionStatus;

    @Before
    public void setUp() throws Exception {
        mCrashReporter = mock(ICrashReporter.class);
        mBinder = mock(ICrashServiceCommunicator.Stub.class);
        mIntent = mock(Intent.class);
        mBroadcastReceiver = mock(BroadcastReceiver.class);

        mBroadcastReceiver = mock(BroadcastReceiver.class);
        mConnectivityHelper = mock(IConnectivityHelper.class);
        mOfflineUploader = mock(IOfflineUploader.class);

        mConnectionStatus = mock(ConnectionStatus.class);

        mCrashReportingService = new CrashReportingService(new ICrashReportingServiceFactory() {

            @Override
            public ICrashReporter getCrashReporter(@NonNull Context context) {
                mContext = context;
                return mCrashReporter;
            }

            @Override
            public ICrashServiceCommunicator.Stub getCrashServiceBinder(Context context, ICrashReporter crashReporter) {
                assertThat((CrashReportingService) context, sameInstance(mCrashReportingService));
                assertThat(crashReporter, sameInstance(mCrashReporter));
                mContext = context;
                return mBinder;
            }

            @Override
            public BroadcastReceiver getBroadcastReceiver(ICrashReportingServiceBroadcastReceiverCallback callback) {
                assertThat((CrashReportingService) callback, sameInstance(mCrashReportingService));
                return mBroadcastReceiver;
            }

            @Override
            public IConnectivityHelper getConnectivityHelper(Context context) {
                assertThat((CrashReportingService) context, sameInstance(mCrashReportingService));
                mContext = context;
                return mConnectivityHelper;
            }

            @Override
            public IOfflineUploader getOfflineUploader(Context context) {
                assertThat((CrashReportingService) context, sameInstance(mCrashReportingService));
                mContext = context;
                return mOfflineUploader;
            }

        });

        PowerMockito.mockStatic(android.os.Process.class);
        PowerMockito.when(android.os.Process.myPid()).thenReturn(MOCK_PID);
        suppress(method(Service.class, "onDestroy"));
        suppress(methods(Service.class, "registerReceiver"));
        initializeConnectionState(true, Connection.MOBILE, false);
        initializeConnectionHelper(true);
    }

    @Test
    public void startCommand_nothing_startsWatchingForNativeCrashReports() throws Exception {
        // act
        final int result = mCrashReportingService.onStartCommand(mIntent, MOCK_FLAGS, MOCK_START_ID);

        // assert
        assertThat(result, is(Service.START_STICKY));
        assertThat(mContext, sameInstance((Context) mCrashReportingService));
    }

    @Test
    public void onBind_nothing_startsWatchingNativeCrashReports() throws Exception {
        // act
        mCrashReportingService.onBind(mIntent);

        // assert
        verify(mCrashReporter, times(1)).startWatchingNativeCrashReports();
        assertThat(mContext, sameInstance((Context) mCrashReportingService));
    }

    @Test
    public void onDestroy_started_stopsWatchingForNativeCrashReports() throws Exception {
        // arrange
        mCrashReportingService.onStartCommand(mIntent, MOCK_FLAGS, MOCK_START_ID);

        // act
        mCrashReportingService.onDestroy();

        // assert
        verify(mCrashReporter, times(1)).stopWatchingNativeCrashReports();
        assertThat(mContext, sameInstance((Context) mCrashReportingService));
    }

    @Test
    public void onDestroy_bound_stopsWatchingForNativeCrashReports() throws Exception {
        // arrange
        mCrashReportingService.onBind(mIntent);

        // act
        mCrashReportingService.onDestroy();

        // assert
        verify(mCrashReporter, times(1)).stopWatchingNativeCrashReports();
        assertThat(mContext, sameInstance((Context) mCrashReportingService));
    }

    @Test
    public void onConnectionChange_connectionSuitable_startsUploader() throws Exception {
        // arrange
        mCrashReportingService.onStartCommand(mIntent, MOCK_FLAGS, MOCK_START_ID);
        reset(mOfflineUploader);

        // act
        mCrashReportingService.onConnectionChange(mConnectionStatus);

        // assert
        verify(mOfflineUploader, times(1)).startUploader();
        verify(mOfflineUploader, never()).stopUploader();
    }

    @Test
    public void onConnectionChange_connectionNotSuitable_startsUploader() throws Exception {
        // arrange
        initializeConnectionHelper(false);
        mCrashReportingService.onStartCommand(mIntent, MOCK_FLAGS, MOCK_START_ID);

        // act
        mCrashReportingService.onConnectionChange(mConnectionStatus);

        // assert
        verify(mOfflineUploader, never()).startUploader();
        verify(mOfflineUploader, times(1)).stopUploader();
    }

    private void initializeConnectionState(boolean connected, Connection connection, boolean roaming) {
        when(mConnectionStatus.isConnected()).thenReturn(connected);
        when(mConnectionStatus.getConnection()).thenReturn(connection);
        when(mConnectionStatus.isRoaming()).thenReturn(roaming);

        when(mConnectivityHelper.getConnectionStatus()).thenReturn(mConnectionStatus);
    }

    private void initializeConnectionHelper(boolean suitable) {
        when(mConnectivityHelper.isConnectivitySuitable(mConnectionStatus)).thenReturn(suitable);
    }

}
