package com.avg.zaap.crashlibrary.offline.publisher;

import android.content.Context;

import com.avg.zaap.crashlibrary.CrashLib;
import com.avg.zaap.crashlibrary.ReportData;
import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.offline.publisher.metadata.ICrashMetadataWriter;

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.File;

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
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 OfflinePublisher}.
 * <p/>
 * Created by marek.osvald on 8.8.2016.
 */
@RunWith(MockitoJUnitRunner.class)
public class OfflinePublisherTests {

    private static final String MOCK_CRASH_FILE_PATH = "mockCrashFilePath";
    private static final String MOCK_CRASH_FILE_NAME = "mockCrashFileName";

    private static final String OFFLINE_REPORT_DIRECTORY_NAME = "offline";

    private IOfflinePublisher mOfflinePublisher;
    private IOfflinePublisherCallback mCallback;
    private File mDirectory;
    private File mCrashFile;
    private File mContextFilesDirectory;
    private File mOfflineFile;
    private ICrashMetadataWriter mCrashMetadataWriter;
    private Context mContext;
    private ReportData mReportData;

    @Captor
    private ArgumentCaptor<String> mMessageCaptor;

    @Before
    public void setUp() throws Exception {
        mCallback = mock(IOfflinePublisherCallback.class);
        mContextFilesDirectory = mock(File.class);
        mDirectory = mock(File.class);
        mCrashFile = mock(File.class);
        mOfflineFile = mock(File.class);
        mCrashMetadataWriter = mock(ICrashMetadataWriter.class);

        mContext = mock(Context.class);
        when(mContext.getFilesDir()).thenReturn(mContextFilesDirectory);

        mReportData = mock(ReportData.class);

        mOfflinePublisher = new OfflinePublisher(mCallback, new IOfflinePublisherFactory() {

            @Override
            public File openFile(String filePath) {
                assertThat(filePath, is(MOCK_CRASH_FILE_PATH));
                return mCrashFile;
            }

            @Override
            public File openFile(File directory, String fileName) {
                if (directory == mContextFilesDirectory && fileName.equals(OFFLINE_REPORT_DIRECTORY_NAME)) {
                    return mDirectory;
                }

                if (directory == mDirectory && fileName.equals(MOCK_CRASH_FILE_NAME)) {
                    return mOfflineFile;
                }

                fail();
                return null;
            }

            @Override
            public ICrashMetadataWriter getCrashMetadataWriter() {
                return mCrashMetadataWriter;
            }

        });

        initializeCrashLibConfig();
        initializeCrashFile(true, false, true, true);
        initializeCrashMetadataWriter(true);
        initializeOfflineCrashDirectory(false, false, true);
    }

    @Test
    public void publish_everythingOK_callsOnSuccess() throws Exception {
        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onSuccess();
        verify(mCallback, never()).onFailure(anyString());
    }

    @Test
    public void publish_maxAttemptsReached_deletesFileCallOnSuccess() throws Exception {
        // arrange
        when(mReportData.getPublishAttempts()).thenReturn(CrashLib.getConfig().getMaxAttempts() + 1);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onSuccess();
        verify(mCallback, never()).onFailure(anyString());
    }

    @Test
    public void publish_maxAttemptsReached_deletesFile() throws Exception {
        // arrange
        initializeCrashFile(true, false, true, false);
        when(mReportData.getPublishAttempts()).thenReturn(CrashLib.getConfig().getMaxAttempts() + 1);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, never()).onSuccess();
        verify(mCallback, times(1)).onFailure(mMessageCaptor.capture());
        assertThat(mMessageCaptor.getValue(), not(nullValue()));
    }

    @Test
    public void publish_everythingOKDirectoryAlreadyExists_callsOnSuccess() throws Exception {
        // arrange
        initializeOfflineCrashDirectory(true, false, false);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onSuccess();
        verify(mCallback, never()).onFailure(anyString());
    }

    @Test
    public void publish_sourceFileDoesNotExist_callsOnFailure() throws Exception {
        // arrange
        initializeCrashFile(false, true, true, true);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    @Test
    public void publish_sourceDirectory_callsOnFailure() throws Exception {
        // arrange
        initializeCrashFile(true, true, true, true);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    @Test
    public void publish_sourceCannotMove_callsOnFailure() throws Exception {
        // arrange
        initializeCrashFile(true, false, false, true);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    @Test
    public void publish_metadataWriterError_callsOnFailure() throws Exception {
        // arrange
        initializeCrashMetadataWriter(false);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    @Test
    public void publish_offlineDirectoryIsAFileCannotBeDeleted_callsOnFailure() throws Exception {
        // arrange
        initializeOfflineCrashDirectory(true, true, false);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    @Test
    public void publish_offlineDirectoryCannotBeCreated_callsOnFailure() throws Exception {
        // arrange
        initializeOfflineCrashDirectory(false, false, false);

        // act
        mOfflinePublisher.publish(mContext, mReportData, MOCK_CRASH_FILE_PATH);

        // assert
        verify(mCallback, times(1)).onFailure(anyString());
        verify(mCallback, never()).onSuccess();
    }

    private void initializeCrashFile(boolean exists, boolean isDirectory, boolean renameTo, boolean delete) {
        when(mCrashFile.exists()).thenReturn(exists);
        when(mCrashFile.isDirectory()).thenReturn(isDirectory);
        when(mCrashFile.getName()).thenReturn(MOCK_CRASH_FILE_NAME);
        when(mCrashFile.renameTo(mOfflineFile)).thenReturn(renameTo);
        when(mCrashFile.delete()).thenReturn(delete);
    }

    private void initializeCrashMetadataWriter(boolean result) {
        when(mCrashMetadataWriter.writeMetadata(mOfflineFile, mReportData)).thenReturn(result);
    }

    private void initializeOfflineCrashDirectory(boolean exists, boolean isFile, boolean mkDirs) {
        when(mDirectory.exists()).thenReturn(exists);
        when(mDirectory.isFile()).thenReturn(isFile);
        when(mDirectory.mkdirs()).thenReturn(mkDirs);
    }

    private void initializeCrashLibConfig() {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .build());
    }

}
