package com.avg.zaap.crashlibrary.provider.app;

import android.annotation.SuppressLint;

import com.avg.zaap.crashlibrary.BuildConfig;
import com.avg.zaap.crashlibrary.core.writer.DataWriterException;
import com.avg.zaap.crashlibrary.core.writer.IDataWriter;
import com.avg.zaap.crashlibrary.provider.CrashProviderException;
import com.avg.zaap.crashlibrary.provider.ICrashProviderCallback;

import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.File;
import java.lang.reflect.Field;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
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;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

/**
 * A test class for {@link BaseBuildConfigProvider}.
 * <p>
 * Created by marek.osvald on 29.4.2016.
 */
public class BuildConfigProviderTests {

    private static final String PROVIDER_NAME = BuildConfigProvider.class.getSimpleName();
    @SuppressLint("SdCardPath")
    private static final String DIRECTORY = "/data/data/app";
    private static final String FILE_NAME = "data.txt";
    private static final String FILE_PATH = DIRECTORY + File.separatorChar + FILE_NAME;

    //@formatter:off
    private static final String BUILD_CONFIG_INFO_DEBUG =
            "DEBUG               : true\n" +
            "APPLICATION_ID      : com.avg.zaap.crashlibrary\n" +
            "BUILD_TYPE          : debug\n" +
            "FLAVOR              : \n" +
            "VERSION_CODE        : 3\n" +
            "VERSION_NAME        : 0.0.3\n";

    private static final String BUILD_CONFIG_INFO_RELEASE =
            "DEBUG               : false\n" +
            "APPLICATION_ID      : com.avg.zaap.crashlibrary\n" +
            "BUILD_TYPE          : release\n" +
            "FLAVOR              : \n" +
            "VERSION_CODE        : 3\n" +
            "VERSION_NAME        : 0.0.3\n";
    //@formatter:on

    private BuildConfigProvider mBuildConfigProvider;
    private IDataWriter mDataWriter;
    private IClassFieldHelper mClassFieldHelper;
    private ICrashProviderCallback mCallback;
    private Result mFieldsResult;
    private ArgumentCaptor<String> mProviderNameCaptor;
    private ArgumentCaptor<Throwable> mThrowableCaptor;

    @Before
    public void setUp() throws Exception {
        mDataWriter = mock(IDataWriter.class);
        mClassFieldHelper = mock(IClassFieldHelper.class);
        mCallback = mock(ICrashProviderCallback.class);

        when(mClassFieldHelper.loadFields(any(Class.class))).thenAnswer(new Answer<Field[]>() {
            @Override
            public Field[] answer(InvocationOnMock invocation) throws Throwable {
                Class clazz = (Class) invocation.getArguments()[0];

                assertThat(clazz, CoreMatchers.<Class>sameInstance(BuildConfig.class));

                switch (mFieldsResult) {
                    case OK:
                        return clazz.getFields();

                    case EXCEPTION:
                        return PrivateFieldClass.class.getDeclaredFields();

                    default:
                        fail();

                }

                return null;
            }
        });

        final Class clazz = BuildConfig.class;

        mBuildConfigProvider = new BaseBuildConfigProvider(FILE_NAME, clazz, new IBuildConfigProviderFactory() {

            @Override
            public IDataWriter getDataWriter() {
                return mDataWriter;
            }

            @Override
            public IClassFieldHelper getClassFieldHelper() {
                return mClassFieldHelper;
            }

        });

        mFieldsResult = Result.OK;
        initializeDataWriter(Result.OK);
        mProviderNameCaptor = ArgumentCaptor.forClass(String.class);
        mThrowableCaptor = ArgumentCaptor.forClass(Throwable.class);
    }

    @Test
    public void provideData_dataWriterOKFieldsOK_callsOnSuccess() throws Exception {
        // act
        mBuildConfigProvider.provideData(DIRECTORY, mCallback);

        // assert
        if (BuildConfig.DEBUG) {
            verify(mDataWriter, times(1)).write(FILE_PATH, BUILD_CONFIG_INFO_DEBUG);
        } else {
            verify(mDataWriter, times(1)).write(FILE_PATH, BUILD_CONFIG_INFO_RELEASE);
        }

        verify(mCallback, times(1)).onSuccess(FILE_PATH);
        verify(mCallback, never()).onFailure(anyString(), any(Throwable.class));
        verifyNoMoreInteractions(mCallback);
    }

    @Test
    public void provideData_dataWriterFailsFieldsOK_callsOnFailure() throws Exception {
        // arrange
        initializeDataWriter(Result.EXCEPTION);

        // act
        mBuildConfigProvider.provideData(DIRECTORY, mCallback);

        // assert
        if (BuildConfig.DEBUG) {
            verify(mDataWriter, times(1)).write(FILE_PATH, BUILD_CONFIG_INFO_DEBUG);
        } else {
            verify(mDataWriter, times(1)).write(FILE_PATH, BUILD_CONFIG_INFO_RELEASE);
        }

        verify(mCallback, times(1)).onFailure(mProviderNameCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mProviderNameCaptor.getValue(), is(PROVIDER_NAME));
        assertThat(mThrowableCaptor.getValue(), instanceOf(CrashProviderException.class));
        verify(mCallback, never()).onSuccess(anyString());
        verifyNoMoreInteractions(mCallback);
    }

    @Test
    public void provideData_dataWriterOKInaccessibleField_callsOnFailure() throws Exception {
        // arrange
        mFieldsResult = Result.EXCEPTION;

        // act
        mBuildConfigProvider.provideData(DIRECTORY, mCallback);

        // assert
        verify(mDataWriter, never()).write(anyString(), anyString());
        verify(mCallback, times(1)).onFailure(anyString(), mThrowableCaptor.capture());
        assertThat(mThrowableCaptor.getValue(), instanceOf(CrashProviderException.class));
        verify(mCallback, never()).onSuccess(anyString());
        verifyNoMoreInteractions(mCallback);
    }

    private void initializeDataWriter(Result result) throws Exception {
        switch (result) {
            case OK:
                doNothing().when(mDataWriter).write(FILE_PATH, BUILD_CONFIG_INFO_DEBUG);
                doNothing().when(mDataWriter).write(FILE_PATH, BUILD_CONFIG_INFO_RELEASE);
                break;

            case EXCEPTION:
                doThrow(new DataWriterException()).when(mDataWriter).write(FILE_PATH, BUILD_CONFIG_INFO_DEBUG);
                doThrow(new DataWriterException()).when(mDataWriter).write(FILE_PATH, BUILD_CONFIG_INFO_RELEASE);
                break;

            default:
                fail();
        }
    }

    enum Result {
        OK, EXCEPTION
    }

}
