package org.sufficientlysecure.keychain.remote;


import java.util.ArrayList;
import java.util.Arrays;

import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.MatrixCursor;
import android.net.Uri;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.KeychainTestRunner;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@SuppressWarnings("unchecked")
@RunWith(KeychainTestRunner.class)
public class OpenPgpServiceKeyIdExtractorTest {

    private static final long[] KEY_IDS = new long[] { 123L, 234L };
    private static final String[] USER_IDS = new String[] { "user1@example.org", "User 2 <user2@example.org>" };
    private OpenPgpServiceKeyIdExtractor openPgpServiceKeyIdExtractor;
    private ContentResolver contentResolver;
    private ApiPendingIntentFactory apiPendingIntentFactory;

    @Before
    public void setUp() throws Exception {
        contentResolver = mock(ContentResolver.class);
        apiPendingIntentFactory = mock(ApiPendingIntentFactory.class);

        openPgpServiceKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(contentResolver,
                apiPendingIntentFactory);
    }

    @Test
    public void returnKeyIdsFromIntent__withKeyIdsExtra() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, KEY_IDS);

        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);

        assertEquals(KeyIdResultStatus.NO_KEYS_ERROR, keyIdResult.getStatus());
        assertFalse(keyIdResult.hasKeySelectionPendingIntent());
        assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
    }

    @Test
    public void returnKeyIdsFromIntent__withKeyIdsSelectedExtra() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED, KEY_IDS);
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS); // should be ignored

        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);

        assertEquals(KeyIdResultStatus.OK, keyIdResult.getStatus());
        assertFalse(keyIdResult.hasKeySelectionPendingIntent());
        assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
    }

    @Test(expected = IllegalStateException.class)
    public void returnKeyIdsFromIntent__withUserIds__withEmptyQueryResult() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupSelectPubkeyPendingIntentFactoryResult(pendingIntent);


        openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false, BuildConfig.APPLICATION_ID);
    }

    @Test
    public void returnKeyIdsFromIntent__withNoData() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[] { });

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupSelectPubkeyPendingIntentFactoryResult(pendingIntent);


        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
        assertTrue(keyIdResult.hasKeySelectionPendingIntent());
        assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
    }

    @Test
    public void returnKeyIdsFromIntent__withEmptyUserId() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[0]);

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupSelectPubkeyPendingIntentFactoryResult(pendingIntent);

        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
        assertTrue(keyIdResult.hasKeySelectionPendingIntent());
        assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
    }

    @Test
    public void returnKeyIdsFromIntent__withNoData__askIfNoData() throws Exception {
        Intent intent = new Intent();

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupSelectPubkeyPendingIntentFactoryResult(pendingIntent);


        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, true,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.NO_KEYS, keyIdResult.getStatus());
        assertTrue(keyIdResult.hasKeySelectionPendingIntent());
        assertSame(pendingIntent, keyIdResult.getKeySelectionPendingIntent());
    }

    @Test
    public void returnKeyIdsFromIntent__withUserIds() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);

        setupContentResolverResult(USER_IDS, new Long[] { 123L, 234L }, new int[] { 0, 0 }, new int[] { 1, 1, 1 });


        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.OK, keyIdResult.getStatus());
        assertFalse(keyIdResult.hasKeySelectionPendingIntent());
        assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
    }

    @Test
    public void returnKeyIdsFromIntent__withUserIds__withDuplicate() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);

        setupContentResolverResult(new String[] {
                USER_IDS[0], USER_IDS[1]
        }, new Long[] { 123L, 234L }, new int[] { 0, 0 }, new int[] { 2, 1 });

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupDeduplicatePendingIntentFactoryResult(pendingIntent);


        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.DUPLICATE, keyIdResult.getStatus());
        assertTrue(keyIdResult.hasKeySelectionPendingIntent());
    }

    @Test
    public void returnKeyIdsFromIntent__withUserIds__withMissing() throws Exception {
        Intent intent = new Intent();
        intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);

        setupContentResolverResult(USER_IDS, new Long[] { null, 234L }, new int[] { 0, 0 }, new int[] { 0, 1 });

        PendingIntent pendingIntent = mock(PendingIntent.class);
        setupSelectPubkeyPendingIntentFactoryResult(pendingIntent);


        KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false,
                BuildConfig.APPLICATION_ID);


        assertEquals(KeyIdResultStatus.MISSING, keyIdResult.getStatus());
        assertTrue(keyIdResult.hasKeySelectionPendingIntent());
    }

    private void setupContentResolverResult(String[] userIds, Long[] resultKeyIds, int[] verified, int[] candidates) {
        MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.PROJECTION_MAIL_STATUS);
        for (int i = 0; i < userIds.length; i++) {
            resultCursor.addRow(new Object[] { userIds[i], resultKeyIds[i], verified[i], candidates[i], null, null, null });
        }

        when(contentResolver.query(
                any(Uri.class), any(String[].class), nullable(String.class), any(String[].class), nullable(String.class)))
                .thenReturn(resultCursor);
    }

    private void setupSelectPubkeyPendingIntentFactoryResult(PendingIntent pendingIntent) {
        when(apiPendingIntentFactory.createSelectPublicKeyPendingIntent(
                any(Intent.class), any(long[].class), any(ArrayList.class), any(ArrayList.class), any(Boolean.class)))
                .thenReturn(pendingIntent);
    }

    private void setupDeduplicatePendingIntentFactoryResult(PendingIntent pendingIntent) {
        when(apiPendingIntentFactory.createDeduplicatePendingIntent(
                any(String.class), any(Intent.class), any(ArrayList.class))).thenReturn(pendingIntent);
    }

    private static void assertArrayEqualsSorted(long[] a, long[] b) {
        long[] tmpA = Arrays.copyOf(a, a.length);
        long[] tmpB = Arrays.copyOf(b, b.length);
        Arrays.sort(tmpA);
        Arrays.sort(tmpB);

        assertArrayEquals(tmpA, tmpB);
    }
}