package com.avg.zaap.crashlibrary;

import android.content.ComponentName;
import android.os.IBinder;
import android.os.RemoteException;

import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.publish.ICrashPublisher;

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 java.util.ArrayList;
import java.util.List;

import static junit.framework.Assert.fail;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * A test class for {@link CrashReporterServiceConnection}.
 * <p>
 * Created by marek.osvald on 11.7.2016.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({ICrashServiceCommunicator.Stub.class})
public class CrashReporterServiceConnectionTests {

    private static final int PUBLISHER_COUNT = 3;

    private static final String CLASS_NAME = "mockClassName";
    private static final String PACKAGE_NAME = "mockPackageName";

    private CrashReporterServiceConnection mCrashReporterServiceConnection;
    private ReportData mReportData;
    private IBinder mBinder;
    private IServiceConnectionCallback mCallback;
    private ComponentName mComponentName;
    private ICrashServiceCommunicator mCrashService;

    @Before
    public void setUp() throws Exception {
        mCrashService = mock(ICrashServiceCommunicator.class);

        mReportData = mock(ReportData.class);
        mBinder = mock(IBinder.class);

        PowerMockito.mockStatic(ICrashServiceCommunicator.Stub.class);
        PowerMockito.when(ICrashServiceCommunicator.Stub.asInterface(mBinder)).thenReturn(mCrashService);

        mCallback = mock(IServiceConnectionCallback.class);

        mComponentName = new ComponentName(PACKAGE_NAME, CLASS_NAME);
        initCrashLibConfig();
        initializeCrashService(Result.OK);

        mCrashReporterServiceConnection = new CrashReporterServiceConnection(mReportData, mCallback);
    }

    @Test
    public void onServiceConnected_everythingOK_callsCallback() throws Exception {
        // act
        mCrashReporterServiceConnection.onServiceConnected(mComponentName, mBinder);

        // assert
        verify(mCallback, never()).onCrashPublished();
        verify(mCallback, never()).onCrashPublishingFailed();
    }

    @Test
    public void onServiceConnected_remoteException_callsCallback() throws Exception {
        // arrange
        initializeCrashService(Result.EXCEPTION);

        // act
        mCrashReporterServiceConnection.onServiceConnected(mComponentName, mBinder);

        // assert
        verify(mCallback, never()).onCrashPublished();
        verify(mCallback, times(1)).onCrashPublishingFailed();
    }

    @Test
    public void onServiceDisconnected_nothing_doesNotCrash() throws Exception {
        // arrange
        ComponentName componentName = new ComponentName("package", "class");

        // act
        mCrashReporterServiceConnection.onServiceDisconnected(componentName);
    }

    @Test
    public void onPublisherResult_nothing_callsCallback() throws Exception {
        // act
        mCrashReporterServiceConnection.onPublisherResult(3, 0);

        // assert
        verify(mCallback, times(1)).onCrashPublished();
        verify(mCallback, never()).onCrashPublishingFailed();
    }

    @Test(expected = UnsupportedOperationException.class)
    public void asBinder_nothing_throwsUnsupportedOperationException() throws Exception {
        // act
        mCrashReporterServiceConnection.asBinder();
    }


    private void initCrashLibConfig() {
        List<ICrashPublisher> publishers = new ArrayList<>();

        for (int i = 0; i < PUBLISHER_COUNT; i++) {
            publishers.add(mock(ICrashPublisher.class));
        }

        CrashLib.setConfig(new CrashLibConfig.Builder()
                .publishers(publishers)
                .build());
    }


    private void initializeCrashService(Result result) throws RemoteException {
        switch (result) {
            case OK:
                doNothing().when(mCrashService).publishAndroidCrash(mReportData, mCrashReporterServiceConnection);
                break;

            case EXCEPTION:
                doThrow(new RemoteException()).when(mCrashService).publishAndroidCrash(mReportData, mCrashReporterServiceConnection);
                break;

            default:
                fail();
        }
    }

    enum Result {
        OK, EXCEPTION
    }

}
