package com.avg.zaap.crashlibrary;

import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.provider.ICrashAsyncProvider;
import com.avg.zaap.crashlibrary.provider.ICrashProviderCallback;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * A test class for {@link CrashPublisherCallbackHandler} class.
 * <p>
 * Created by marek.osvald on 4.5.2016.
 */
@RunWith(MockitoJUnitRunner.class)
public class CrashPublisherCallbackHandlerTests {

    private static final String PROVIDER_NAME = "mockProviderName";
    private static final int ASYNC_PROVIDER_COUNT = 3;

    private static final String ASYNC_PROVIDER_FILEPATH_1 = "/foo/bar/async1.txt";
    private static final String ASYNC_PROVIDER_FILEPATH_2 = "/foo/bar/async2.txt";
    private static final String ASYNC_PROVIDER_FILEPATH_3 = "/foo/bar/async3.txt";

    private ICrashProviderCallback mCrashPublisherCallbackHandler;
    private ReportData mReportData;
    private List<String> mFilesToPack;
    private ICrashReporterCallback mCallback;

    @Captor
    private ArgumentCaptor<String> mCrashCodeCaptor;
    @Captor
    private ArgumentCaptor<List<String>> mFilePathCaptor;

    @Before
    public void setUp() throws Exception {
        mFilesToPack = new ArrayList<>();
        mReportData = mock(ReportData.class);
        when(mReportData.getFilesToPack()).thenReturn(mFilesToPack);

        mCallback = mock(ICrashReporterCallback.class);

        initCrashLibConfig();
        mCrashPublisherCallbackHandler = new CrashPublisherCallbackHandler(mReportData, mCallback);
    }

    @Test
    public void onSuccess_countNotReached_doesNothing() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_1);

        // assert
        verify(mCallback, never()).publishResults(any(ReportData.class));
    }

    @Test
    public void onSuccess_countReached_publishesResults() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_1);
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_2);
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_3);

        // assert
        verify(mCallback, times(1)).publishResults(mReportData);
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_1), is(true));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_2), is(true));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_3), is(true));
    }

    @Test
    public void onFailure_countNotReached_doesNothing() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new IOException());

        // assert
        verify(mCallback, never()).publishResults(any(ReportData.class));
    }

    @Test
    public void onFailure_countReached_publishesResults() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new IOException());
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, null);
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new FileNotFoundException());

        // assert
        verify(mCallback, times(1)).publishResults(mReportData);
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_1), is(false));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_2), is(false));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_3), is(false));
    }

    @Test
    public void onFailure_mixedSuccessAndFailureCountNotReached_doesNothing() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_1);
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new IOException());

        // assert
        verify(mCallback, never()).publishResults(any(ReportData.class));
    }

    @Test
    public void onFailure_mixedSuccessAndFailureCountReached_publishesResults() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_1);
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new IOException());
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_3);

        // assert
        verify(mCallback, times(1)).publishResults(mReportData);
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_1), is(true));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_2), is(false));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_3), is(true));
    }

    @Test
    public void onFailure_mixedSuccessAndFailureFailureAtTheEndCountReached_publishesResults() throws Exception {
        // act
        mCrashPublisherCallbackHandler.onSuccess(ASYNC_PROVIDER_FILEPATH_1);
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new IOException());
        mCrashPublisherCallbackHandler.onFailure(PROVIDER_NAME, new FileNotFoundException());

        // assert
        verify(mCallback, times(1)).publishResults(mReportData);
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_1), is(true));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_2), is(false));
        assertThat(mFilesToPack.contains(ASYNC_PROVIDER_FILEPATH_3), is(false));
    }

    private void initCrashLibConfig() {
        List<ICrashAsyncProvider> asyncProviders = new ArrayList<>();

        for (int i = 0; i < ASYNC_PROVIDER_COUNT; i++) {
            asyncProviders.add(mock(ICrashAsyncProvider.class));
        }

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

}
