package com.avg.zaap.crashlibrary.show.error.activity;

import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import com.avg.zaap.crashlibrary.BuildConfig;
import com.avg.zaap.crashlibrary.CrashLib;
import com.avg.zaap.crashlibrary.R;
import com.avg.zaap.crashlibrary.ReportData;
import com.avg.zaap.crashlibrary.config.CrashLibConfig;

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.support.membermodification.MemberMatcher.method;
import static org.powermock.api.support.membermodification.MemberMatcher.methods;

/**
 * Tests for {@link ErrorActivity}.
 * <p/>
 * Created by marek.osvald on 11. 5. 2016.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({ErrorActivity.class, BuildConfig.class})
public class ErrorActivityTests {

    private static final int UNDEFINED = 0;
    private static final int ERROR_NAME_TEXT = 1;
    private static final int ERROR_MESSAGE_TEXT = 2;

    private static final String MOCK_CRASH_CODE = "crashCode";
    private static final String MOCK_ERROR_NAME = "errorName";
    private static final String MOCK_ERROR_MESSAGE = "errorDetails";
    private static final String MOCK_ERROR_DETAILS = "errorDetail";
    private static final String MOCK_CAP_GUID = "guid";

    private static final String INTENT_KEY_REPORT_DATA = "reportData";

    private TextView mCrashCodeView;
    private TextView mCapMessageView;
    private TextView mErrorMessageView;
    private TextView mErrorDetailsView;
    private ErrorActivity mErrorActivity;
    private Resources mResources;
    private Intent mIntent;
    @SuppressWarnings("FieldCanBeLocal")
    private Bundle mExtras;
    private ReportData mReportData;

    @Before
    public void setUp() throws Exception {
        // default setting tests release mode
        PowerMockito.suppress(methods(AppCompatActivity.class, "onCreate"));
        PowerMockito.suppress(methods(AppCompatActivity.class, "setContentView"));

        mResources = mock(Resources.class);
        PowerMockito.replace(method(AppCompatActivity.class, "getResources")).with(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return mResources;
            }
        });

        mReportData = mock(ReportData.class);

        mIntent = mock(Intent.class);
        mExtras = mock(Bundle.class);
        when(mIntent.getExtras()).thenReturn(mExtras);
        when(mExtras.getParcelable(anyString())).thenAnswer(new Answer<Parcelable>() {
            @Override
            public Parcelable answer(InvocationOnMock invocation) throws Throwable {
                String key = (String) invocation.getArguments()[0];

                switch (key) {
                    case INTENT_KEY_REPORT_DATA:
                        return mReportData;

                    default:
                        fail();
                        return null;
                }
            }
        });
        when(mExtras.containsKey(anyString())).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                String key = (String) invocation.getArguments()[0];

                switch (key) {
                    case INTENT_KEY_REPORT_DATA:
                        return true;

                    default:
                        fail();
                        return null;
                }
            }
        });
        PowerMockito.replace(method(AppCompatActivity.class, "getIntent")).with(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return mIntent;
            }
        });

        mCrashCodeView = mock(TextView.class);
        mCapMessageView = mock(TextView.class);
        mErrorMessageView = mock(TextView.class);
        mErrorDetailsView = mock(TextView.class);

        final Method[] findViewByIds = PowerMockito.methods(AppCompatActivity.class, "findViewById");
        for (Method method : findViewByIds) {
            PowerMockito.replace(method).with(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    int id = (int) args[0];

                    if (id == R.id.crashlib_crashCode) {
                        return mCrashCodeView;
                    }

                    if (id == R.id.crashlib_capExceptionCode) {
                        return mCapMessageView;
                    }

                    if (id == R.id.crashlib_errorMessage) {
                        return mErrorMessageView;
                    }

                    if (id == R.id.crashlib_errorDetails) {
                        return mErrorDetailsView;
                    }

                    fail();
                    return null;
                }
            });
        }

        initializeReportData();
        initCrashLibConfig(ERROR_NAME_TEXT, ERROR_MESSAGE_TEXT);
        mErrorActivity = new ErrorActivity();
    }

    @Test
    public void onCreate_errorMessageOKErrorStackTraceOK_setsText() throws Exception {
        // act
        mErrorActivity.onCreate(null);

        //verify
        if (BuildConfig.DEBUG) {
            verify(mCrashCodeView, times(1)).setText(MOCK_CRASH_CODE);
            verify(mCapMessageView, times(1)).setText(MOCK_CAP_GUID);
            verify(mErrorMessageView, times(1)).setText(String.format("%s: %s", MOCK_ERROR_NAME, MOCK_ERROR_MESSAGE));
            verify(mErrorDetailsView, times(1)).setText(MOCK_ERROR_DETAILS);
        } else {
            verify(mCrashCodeView, never()).setText(anyString());
            verify(mCrashCodeView, never()).setText(anyInt());
            verify(mCapMessageView, never()).setText(anyString());
            verify(mCapMessageView, never()).setText(anyInt());
            //noinspection ResourceType
            verify(mErrorMessageView, times(1)).setText(ERROR_NAME_TEXT);
            //noinspection ResourceType
            verify(mErrorDetailsView, times(1)).setText(ERROR_MESSAGE_TEXT);
        }
    }

    @Test
    public void onCreate_viewIdsNotFound_doesNotCrash() throws Exception {
        //prepare
        final Method[] findViewByIds = PowerMockito.methods(AppCompatActivity.class, "findViewById");
        for (Method method : findViewByIds) {
            PowerMockito.replace(method).with(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    int id = (int) args[0];

                    if (id == R.id.crashlib_crashCode) {
                        return null;
                    }

                    if (id == R.id.crashlib_capExceptionCode) {
                        return null;
                    }

                    if (id == R.id.crashlib_errorMessage) {
                        return null;
                    }

                    if (id == R.id.crashlib_errorDetails) {
                        return null;
                    }

                    fail();
                    return null;
                }
            });
        }

        mErrorActivity = new ErrorActivity();

        //test
        mErrorActivity.onCreate(null);

        //verify
        verify(mCrashCodeView, never()).setText(anyInt());
        verify(mCrashCodeView, never()).setText(anyString());
        verify(mCapMessageView, never()).setText(anyInt());
        verify(mCapMessageView, never()).setText(anyString());
        verify(mErrorMessageView, never()).setText(anyInt());
        verify(mErrorMessageView, never()).setText(anyString());
        verify(mErrorDetailsView, never()).setText(anyString());
        verify(mErrorDetailsView, never()).setText(anyInt());
    }

    @Test
    public void onCreate_debugOrRelease_dependentlySetsCrashCodeText() throws Exception {
        // act
        mErrorActivity.onCreate(null);

        //verify
        if (BuildConfig.DEBUG) {
            verify(mCrashCodeView, times(1)).setText(mReportData.getCrashCode());
        } else {
            verify(mCrashCodeView, never()).setText(anyString());
            verify(mCrashCodeView, never()).setText(anyInt());
        }
    }

    @Test
    public void onCreate_errorNameNull_doesNotWriteFromReportData() throws Exception {
        // arrange
        when(mReportData.getErrorName()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        verify(mErrorMessageView, times(1)).setText(ERROR_NAME_TEXT);
    }

    @Test
    public void onCreate_errorNameNullUndefined_doesNotWriteFromReportData() throws Exception {
        // arrange
        initCrashLibConfig(UNDEFINED, UNDEFINED);
        when(mReportData.getErrorName()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        verify(mErrorMessageView, never()).setText(anyString());
        verify(mErrorMessageView, never()).setText(anyInt());
    }

    @Test
    public void onCreate_errorMessageNull_doesNotWriteFromReportData() throws Exception {
        // arrange
        when(mReportData.getErrorMessage()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        verify(mErrorMessageView, times(1)).setText(ERROR_NAME_TEXT);
    }

    @Test
    public void onCreate_errorMessageNullUndefined_doesNotWriteFromReportData() throws Exception {
        // arrange
        initCrashLibConfig(UNDEFINED, UNDEFINED);
        when(mReportData.getErrorMessage()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        verify(mErrorMessageView, never()).setText(anyString());
        verify(mErrorMessageView, never()).setText(anyInt());
    }

    @Test
    public void onCreate_debugOrRelease_dependentlySetsCapExceptionCodeText() throws Exception {
        // act
        mErrorActivity.onCreate(null);

        // assert
        if (BuildConfig.DEBUG) {
            //noinspection ResourceType
            verify(mCapMessageView, times(1)).setText(MOCK_CAP_GUID);
        } else {
            verify(mErrorMessageView, never()).setText(anyString());
            //noinspection ResourceType
            verify(mErrorMessageView, times(1)).setText(ERROR_NAME_TEXT);
        }
    }

    @Test
    public void onCreate_errorDetailsNull_doesNotWriteFromReportData() throws Exception {
        // arrange
        when(mReportData.getErrorDetails()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        if (BuildConfig.DEBUG) {
            //noinspection ResourceType
            verify(mErrorDetailsView, times(1)).setText(ERROR_MESSAGE_TEXT);
        } else {
            verify(mErrorDetailsView, never()).setText(anyString());
            verify(mErrorDetailsView, times(1)).setText(CrashLib.getConfig().getErrorDetails());
        }
    }

    @Test
    public void onCreate_errorDetailsNullUndefined_doesNotWriteFromReportData() throws Exception {
        // arrange
        initCrashLibConfig(UNDEFINED, UNDEFINED);
        when(mReportData.getErrorDetails()).thenReturn(null);

        // act
        mErrorActivity.onCreate(null);

        // assert
        //noinspection ResourceType
        verify(mErrorDetailsView, never()).setText(anyString());
        verify(mErrorDetailsView, never()).setText(anyInt());
    }

    @Test
    public void onCreate_reportDataNull_doesNotDisplayError() throws Exception {
        //prepare
        when(mExtras.containsKey(INTENT_KEY_REPORT_DATA)).thenReturn(true);
        when(mExtras.getParcelable(INTENT_KEY_REPORT_DATA)).thenReturn(null);

        //test
        mErrorActivity.onCreate(null);

        //verify
        verify(mCrashCodeView, never()).setText(anyString());
        verify(mCrashCodeView, never()).setText(anyInt());
        verify(mCapMessageView, never()).setText(anyString());
        verify(mCapMessageView, never()).setText(anyInt());
        verify(mErrorMessageView, never()).setText(anyString());
        verify(mErrorMessageView, never()).setText(anyInt());
        verify(mErrorDetailsView, never()).setText(anyString());
        verify(mErrorDetailsView, never()).setText(anyInt());
    }

    @Test
    public void onCreate_reportDataMissing_doesNotDisplayError() throws Exception {
        // prepare
        when(mExtras.containsKey(INTENT_KEY_REPORT_DATA)).thenReturn(false);

        // test
        mErrorActivity.onCreate(null);

        //verify
        verify(mCrashCodeView, never()).setText(anyString());
        verify(mCrashCodeView, never()).setText(anyInt());
        verify(mCapMessageView, never()).setText(anyString());
        verify(mCapMessageView, never()).setText(anyInt());
        verify(mErrorMessageView, never()).setText(anyString());
        verify(mErrorMessageView, never()).setText(anyInt());
        verify(mErrorDetailsView, never()).setText(anyString());
        verify(mErrorDetailsView, never()).setText(anyInt());
    }

    private void initCrashLibConfig(int errorTitle, int errorMessage) {
        final CrashLibConfig crashLibConfig = new CrashLibConfig.Builder()
                .errorMessage(errorTitle)
                .errorDetails(errorMessage)
                .build();

        CrashLib.setConfig(crashLibConfig);
    }

    private void initializeReportData() {
        when(mReportData.getCrashCode()).thenReturn(MOCK_CRASH_CODE);
        when(mReportData.getErrorName()).thenReturn(MOCK_ERROR_NAME);
        when(mReportData.getErrorMessage()).thenReturn(MOCK_ERROR_MESSAGE);
        when(mReportData.getErrorDetails()).thenReturn(MOCK_ERROR_DETAILS);
        when(mReportData.getCapGUID()).thenReturn(MOCK_CAP_GUID);
    }

}
