package com.avg.zaap.crashlibrary.offline;

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.NonNull;

import com.avg.zaap.crashlibrary.ConnectionStatus;
import com.avg.zaap.crashlibrary.CrashLib;
import com.avg.zaap.crashlibrary.config.CrashLibConfig;

import org.junit.Before;
import org.junit.Test;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * A test class for {@link ConnectivityHelper}.
 * <p/>
 * Created by marek.osvald on 2.9.2016.
 */
public class ConnectivityHelperTests {

    private static final boolean[] BOOLEAN_VALUES = new boolean[]{true, false};
    @SuppressLint("InlinedApi")
    @SuppressWarnings("deprecation")
    private static final Integer[] CONNECTION_TYPE_VALUES = new Integer[]{
            null,
            ConnectivityManager.TYPE_MOBILE,
            ConnectivityManager.TYPE_MOBILE_DUN,
            ConnectivityManager.TYPE_MOBILE_HIPRI,
            ConnectivityManager.TYPE_MOBILE_MMS,
            ConnectivityManager.TYPE_MOBILE_SUPL,
            ConnectivityManager.TYPE_WIFI,
            ConnectivityManager.TYPE_WIMAX,
            ConnectivityManager.TYPE_BLUETOOTH,
            ConnectivityManager.TYPE_DUMMY,
            ConnectivityManager.TYPE_ETHERNET,
            ConnectivityManager.TYPE_VPN
    };

    private IConnectivityHelper mConnectivityHelper;
    @SuppressWarnings("FieldCanBeLocal")
    private Context mContext;
    @SuppressWarnings("FieldCanBeLocal")
    private ConnectivityManager mConnectivityManager;
    private NetworkInfo mNetworkInfo;
    private ConnectionStatus mConnectionStatus;

    @Before
    public void setUp() throws Exception {
        mContext = mock(Context.class);
        mConnectivityManager = mock(ConnectivityManager.class);
        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityManager);
        mConnectionStatus = new ConnectionStatus();

        mConnectivityHelper = new ConnectivityHelper(mContext, new IConnectivityHelperFactory() {

            @Override
            public ConnectionStatus getConnectionStatus() {
                return mConnectionStatus;
            }

        });
    }

    @Test
    public void getConnectionStatus_nothing_readsActiveNetworkInfoCallsProcessNetworkInfo() throws Exception {
        // arrange
        IConnectivityHelper spy = spy(mConnectivityHelper);

        // act
        spy.getConnectionStatus();

        // assert
        verify(spy, times(1)).processNetworkInfo(mNetworkInfo);
    }

    @Test
    public void processNetworkInfo_allValues_returnsCorrectValue() throws Exception {
        for (boolean connected : BOOLEAN_VALUES) {
            for (boolean available : BOOLEAN_VALUES) {
                for (Integer connectionType : CONNECTION_TYPE_VALUES) {
                    for (boolean roaming : BOOLEAN_VALUES) {
                        // arrange
                        initializeConnectionManager(connected, available, connectionType, roaming);

                        // act
                        final ConnectionStatus connectionStatus = mConnectivityHelper.getConnectionStatus();

                        // assert
                        if (connectionType == null) {
                            assertThat(connectionStatus.isConnected(), is(false));
                            assertThat(connectionStatus.getConnection(), is(Connection.NONE));
                            assertThat(connectionStatus.isRoaming(), is(false));
                        } else {
                            assertThat(connectionStatus.isConnected(), is(connected && available));
                            assertThat(connectionStatus.getConnection(), is(translateConnectionType(connectionType)));
                            assertThat(connectionStatus.isRoaming(), is(roaming));
                        }
                    }
                }
            }
        }
    }

    @Test
    public void isConnectivitySuitable_allValues_returnsCorrectValue() throws Exception {
        for (boolean offlineSuitable : BOOLEAN_VALUES) {
            for (Connection connectionLevelSuitable : Connection.values()) {
                for (boolean roamingSuitable : BOOLEAN_VALUES) {
                    for (boolean connectionOffline : BOOLEAN_VALUES) {
                        for (Connection connectionLevel : Connection.values()) {
                            for (boolean connectionRoaming : BOOLEAN_VALUES) {
                                // arrange
                                initializeCrashLibConfig(offlineSuitable, connectionLevelSuitable, roamingSuitable);
                                initializeConnection(connectionOffline, connectionLevel, connectionRoaming);

                                // act
                                boolean result = mConnectivityHelper.isConnectivitySuitable(mConnectionStatus);

                                // assert
                                assertThat(result, is(checkConnectionStatus(mConnectionStatus)));
                            }
                        }
                    }
                }
            }
        }
    }

    private void initializeCrashLibConfig(boolean offlineSuitable, Connection connectionLevelSuitable, boolean roamingSuitable) {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .offlineSuitable(offlineSuitable)
                .connectionLevelSuitable(connectionLevelSuitable)
                .roamingSuitable(roamingSuitable)
                .build());
    }

    private void initializeConnection(boolean connected, Connection connection, boolean roaming) {
        mConnectionStatus.setConnected(connected);
        mConnectionStatus.setConnection(connection);
        mConnectionStatus.setRoaming(roaming);
    }

    private boolean checkConnectionStatus(ConnectionStatus connectionStatus) {
        //@formatter:off
        return checkOffline(connectionStatus.isConnected())           &&
               checkConnectionLevel(connectionStatus.getConnection()) &&
               checkRoaming(connectionStatus.isRoaming());
        //@formatter:on
    }

    private boolean checkOffline(boolean connected) {
        return CrashLib.getConfig().isOfflineSuitable() || connected;
    }

    private boolean checkConnectionLevel(@NonNull Connection connection) {
        return connection.getLevel() >= CrashLib.getConfig().getConnectionLevelSuitable().getLevel();
    }

    private boolean checkRoaming(boolean roaming) {
        return CrashLib.getConfig().isRoamingSuitable() || !roaming;
    }

    private void initializeConnectionManager(boolean connected, boolean available, Integer connectionType, boolean roaming) {
        mNetworkInfo = mock(NetworkInfo.class);
        when(mNetworkInfo.isConnected()).thenReturn(connected);
        when(mNetworkInfo.isAvailable()).thenReturn(available);

        if (connectionType != null) {
            when(mNetworkInfo.getType()).thenReturn(connectionType);
        }

        when(mNetworkInfo.isRoaming()).thenReturn(roaming);

        if (connectionType == null) {
            when(mConnectivityManager.getActiveNetworkInfo()).thenReturn(null);
        } else {
            when(mConnectivityManager.getActiveNetworkInfo()).thenReturn(mNetworkInfo);
        }
    }

    private Connection translateConnectionType(int connectionType) {
        switch (connectionType) {
            case ConnectivityManager.TYPE_WIFI:
            case ConnectivityManager.TYPE_WIMAX:
                return Connection.WIFI;

            case ConnectivityManager.TYPE_MOBILE:
            case ConnectivityManager.TYPE_MOBILE_DUN:
                //noinspection deprecation
            case ConnectivityManager.TYPE_MOBILE_MMS:
                //noinspection deprecation
            case ConnectivityManager.TYPE_MOBILE_SUPL:
                //noinspection deprecation
            case ConnectivityManager.TYPE_MOBILE_HIPRI:
                return Connection.MOBILE;

            case ConnectivityManager.TYPE_BLUETOOTH:
            case ConnectivityManager.TYPE_ETHERNET:
            case ConnectivityManager.TYPE_VPN:
            case ConnectivityManager.TYPE_DUMMY:
                return Connection.OTHER;

            default:
                fail();
                return null;
        }
    }

}
